A terminal panel beside an agent chat
A real shell on the chat's own connection, opened and closed like the inspector and never beside it. The modes govern the model; what a person types is theirs, since they hold the credential and could open the same shell with an ssh client. The model cannot see the panel -- a button copies the output you choose into the composer. The session outlives the socket: closing the panel leaves a build running, and coming back reattaches with the scrollback. Two tabs share one shell and the smaller window decides the size. It ends on an idle timeout, on deleting the chat, on disabling, moving or deleting the connection, and on a restart -- which says why rather than quietly opening a fresh shell that has lost the working directory. The nginx template's `Connection ""` is right for SSE and fails every WebSocket handshake, so `location /` now uses a `map $http_upgrade`; update.sh grows a drift check for it, because the only symptom on a stale vhost is a panel that cannot connect. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
+32
-2
@@ -3,11 +3,12 @@
|
||||
|
||||
LLeMbas has no Node toolchain and loads nothing from a CDN at runtime -- a
|
||||
self-hosted tool should keep working without internet access, and should not
|
||||
report every user's page view to a third party. The three libraries it does use
|
||||
report every user's page view to a third party. The few libraries it does use
|
||||
are fetched once, here, and committed.
|
||||
|
||||
Integrity is enforced with vendor.lock.json. A mismatched hash aborts rather
|
||||
than overwriting: that is the whole point of pinning.
|
||||
than overwriting, and so does a name that is not in the lock at all: that is
|
||||
the whole point of pinning.
|
||||
|
||||
python scripts/fetch_vendor.py # fetch and verify against the lock
|
||||
python scripts/fetch_vendor.py --update # re-pin after a version bump
|
||||
@@ -44,6 +45,21 @@ PACKAGES = {
|
||||
"url": "https://unpkg.com/alpinejs@3.15.12/dist/cdn.min.js",
|
||||
"why": "Small client-only state: menus, theme toggle, composer autosize.",
|
||||
},
|
||||
"xterm.js": {
|
||||
"version": "5.5.0",
|
||||
"url": "https://unpkg.com/@xterm/xterm@5.5.0/lib/xterm.js",
|
||||
"why": "The terminal panel. Loaded only on a chat that has an SSH connection.",
|
||||
},
|
||||
"xterm.css": {
|
||||
"version": "5.5.0",
|
||||
"url": "https://unpkg.com/@xterm/xterm@5.5.0/css/xterm.css",
|
||||
"why": "Terminal layout. Its colours are overridden from tokens.css at runtime.",
|
||||
},
|
||||
"xterm-addon-fit.js": {
|
||||
"version": "0.10.0",
|
||||
"url": "https://unpkg.com/@xterm/addon-fit@0.10.0/lib/addon-fit.js",
|
||||
"why": "Sizes the terminal to the panel; without it a resize is 80x24 forever.",
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@@ -83,6 +99,20 @@ def main() -> int:
|
||||
digest = sha256(payload)
|
||||
expected = lock.get(filename, {}).get("sha256")
|
||||
|
||||
if lock and not expected and not args.update:
|
||||
# A name added to PACKAGES but absent from the lock has nothing to
|
||||
# compare against, so the mismatch branch below never fires and the
|
||||
# file lands unpinned -- which is the one thing this script exists
|
||||
# to prevent. Adding a library is a --update, like bumping one.
|
||||
print(
|
||||
f" FAIL {filename}: not in {LOCKFILE.name}\n"
|
||||
f" Nothing to verify this download against. If the "
|
||||
f"library was added deliberately, re-run with --update.",
|
||||
file=sys.stderr,
|
||||
)
|
||||
failed = True
|
||||
continue
|
||||
|
||||
if expected and digest != expected and not args.update:
|
||||
print(
|
||||
f" FAIL {filename}: hash mismatch\n"
|
||||
|
||||
Reference in New Issue
Block a user