- POST /api/run/drop-file pipes the body to its file as it arrives in every
case — a member's (S3) through `write-as … replace` with the bytes on
stdin — instead of buffering up to 20 MB in memory for a remote browser.
- Remote limit (server mode, a LAN browser) 20 MB → 500 MB, as a Files upload.
- A loopback browser in local mode has no cap any more, only the data dir's
free space less a 1 GB reserve; ETOOBIG's max_bytes is what still fits, so
the UI's "at most N MB" stays true.
- drop-image keeps its 20 MB in-memory limit, now checked on its own.
- fake-sudo gains a spawn for run-as.__setSpawnImpl.
A terminal mounted on a hidden tab sits in a 0x0 box; its first fit shrank
xterm to 12x6 and the attach carried that size to tmux, so every background
pane was squeezed (Claude's TUI redrawn at 12 columns). It now neither fits
nor reports a size until shown, attaches with no size, and the server spawns
such an attach at the window's own size (tmux.windowSize). A shown tab
reports its size even when the fit did not change xterm's.
Settings > Agents shows the host's login profiles to staff only: a developer
got two 403s from reads that were never theirs.
Dependencies
- Security: body-parser and multer DoS advisories fixed (npm audit: 0).
- Server: Express 5 (query parser kept "extended", req.body defaults to {},
SPA catch-all /{*splat}, send-file helper restores Express 4's dot-folder
rule for sendFile/download, alerts ack route without inline regex),
better-sqlite3 13, js-yaml 5 (server/lib/yaml.js keeps js-yaml 4's default
schema), uuid dropped for crypto.randomUUID, node-pty beta.18, multer 2.5.
- Client: React 19, xterm 6 (WebGL renderer replaces the removed canvas one,
DOM fallback on context loss; Alt+arrow word moves re-mapped), lucide-react 1,
TypeScript 7, i18next/react-i18next/codemirror minors.
Workspace rename step 4
- `agenthub lanes` and `--lane` exit 1 with "renamed: use …"; client api.ts,
the CLI and the MCP server call /api/workspaces; /api/lanes stays mounted.
Server mode (S3): members manage their own Claude Code config
- server/lib/cc-target.js resolves whose config a /cc-config request touches
and gates the router by allowlist; /profiles* and /plugins/update-all stay
admin-only. Member writes go through write-as, CLI calls through sudo as the
member (never -y), reads through openSafe; cc_config_changed reaches only
its owner. Not verified live on an S3 install yet.
Other
- Chat: an HTML file is an artifact only when written whole and not a project
page (index.html, or under src/, public/, templates/, …).
- OpenAPI covers every /api route; openapi-coverage.test.js guards it.
- read-pool test keeps its event loop alive (its worker is unref'd).
- Docs: plan statuses, checklists, CLI.md, LANES.md, SERVER-MODE.md, API.md.
- Today's spend in the status bar and the Claude Code statusline is the
caller's own (`GET /api/pricing/cost?mine=1`, scope.ownSessionWhere), not
every user's sum for an admin or analyst.
- A screenshot pasted into a local claude pane is sent as Claude Code's own
image-paste key: Alt+V on Windows (Ctrl+V did nothing there), ^V elsewhere,
and only when the machine can read its clipboard (wl-paste/xclip + a
display on Linux), else it is uploaded (GET /api/run/native-paste {key}).
- A file manager's files pasted or dropped on a local browser go in by their
own paths, read from the OS clipboard or the file manager's selection
(server/lib/file-manager.js, POST /api/run/clipboard-files and
/selected-files): no upload, no size limit. A copied image file is pasted
by path, text that comes with a picture of it (spreadsheet cells) is
pasted as text, Windows paths go in as `C:/…` mentions.
- Tab titles for Snapshot, Artifacts, Files and Administration.
- An npm tool AgentHub installed under ~/.local is updated there, not by its
own updater into npm's global prefix.
A skill in the Hub was a snapshot taken once: the owner changing
~/.claude/skills/<name> changed nothing there, and an installer had to
uninstall and install again to get a newer version.
- syncSkillSources (on GET /api/artifacts, at most every 15 s): a skill row
taken from a folder — the owner's ~/.claude/skills/<name>, or the
workspace's .claude/skills/<name> — is brought up to it. Only a folder
whose names/sizes/mtimes changed is read again, with a pick's checks (no
symlink, no secret, <= 5 MB, a member's files as theirs); a change is a
NEW snapshot, the old one dropped, and an artifact_changed broadcast.
- artifacts.version (snapshot content hash) and source_hash (the folder's
when last taken, additive columns): an upload or a Hub edit records the
folder as it is then, so only a LATER folder change replaces it.
- A list row's updateAvailable: installed, and the viewer's copy differs
from version. The page shows Update (a pack: Update N); install with
{update: true} replaces the copies (old ones moved aside) and is not
counted as another install.
- opencode.json / grok.json leave the built-in catalog; opencode-usage.js,
its poller, transcript branch and table creation go with it (existing
tables and session rows stay as history).
- A Grok payload reaching the Claude hook route (Grok runs the Claude hooks
itself) is dropped instead of recorded as tool = 'grok'.
- quota.js no longer reads Grok or OpenCode Go logins.
- Redone on v2.8.22 from c6b2892 (feat/grok-usage, never merged).
`carry?.drop(messages.shift())` skips its argument when `carry` is null —
optional chaining short-circuits the whole call — so a Codex, omp, Cursor
or Antigravity page never shifted its sliding window and returned the
whole journal (codex-transcript.test.js: 8 messages for ?limit=2). Shift
first, then hand the dropped message to the carry.
The reported turn (/vp-auto, ~120 messages) showed "Edited 1 file": the
chat loads the newest 50 messages, and the turn's other Write
(rbac_routes_test.go, +176) was in the part before the page.
- turnCarry (agent-edits.js): every message the route's sliding window
drops goes through it; it keeps the file changes since the last prompt it
dropped (a dropped call whose result is on the page, and dropped
subagents, included).
- A Claude page that starts mid-turn answers them as `turn_edits`; the
chat keeps the one from the page that holds its oldest message and adds
it to that turn's card.
Files a turn changed through Bash (python/sed writing a file) are still not
listed: nothing in the transcript says which files a command wrote.
A skill that hands its work to subagents (vp-auto) showed "Edited 1 file"
for a turn that changed 4: a subagent's Write/Edit calls are in its own
agent-<id>.jsonl, so the card only saw the parent's.
- server/lib/agent-edits.js reads a subagent's transcript (readLines, a
member's only as theirs) for the files it wrote or edited with their +/-
lines, nested subagents folded in (3 levels), cached by size + mtime.
- The transcript route puts them on each Claude Agent/Task result as
`edits`; the card adds them to the turn's own edits. A file only a
subagent wrote is listed, never an artifact card (its versions are
rebuilt from this transcript's inputs, which a subagent's are not).
Workspace groups: named, foldable, ordered sidebar sections kept per
account (agenthub.workspace.groups); a workspace joins one from its menu,
a drop on the header, a drag under a row, or Alt+Up/Down past a group's
edge; headers drag to reorder; a folded header shows its most urgent
state; New workspace in this group; fold/unfold all.
Inner repos: GET /api/lanes/:id/repos + ?repo= on git/fs routes; the
Changes panel shows a parent folder's repos only when Settings -> Git ->
Changes panel turns it on (agenthub.changes.innerRepos, off by default).
Cost: the hook writer adds a session's sub-agent transcripts to its
tokens and hours (server/lib/subagent-usage.js) — a sub-agent on the
session's own model was never counted live (~8% of a week here); an
imported session is re-raised on every pass, so a claude -p run first
seen with an empty transcript no longer stays at $0 (~10%). A one-time
raise-only catch-up (user_version 5) re-reads ~/.claude/projects.
Also: omp install script may redirect to its exact install.redirect URL
(#85, repo-links test exempts it), native paste in the terminal.
A workspace record outlives its folder (deleted from the File Explorer or a
shell). Cloning the same repo into that path again ran `git clone`, then
createLane hit idx_lanes_cwd_tool and answered 500 "UNIQUE constraint failed:
lanes.cwd, lanes.tool", leaving the new clone with no workspace on it.
POST /api/lanes/clone now looks for the caller's own workspace at exactly
that folder and tool and answers 200 { lane, created: false, path }, like
POST /ensure. Another user's workspace there is still refused before the
clone (folderTakenByOther), and a nested path still makes its own workspace.
- Users: today / 7 / 30-day spend read only token_usage_hourly, which holds
Claude transcript hours only — omp/Codex/Antigravity/Cursor sessions,
sub-agent files, baselines and long-context pricing were missing. Now
booked by hourlyTokenRows + calculateCost like the cost routes.
- Update: a page adopts a plugin update the server reports as running
(after a reload, another tab, or one started from an older bundle), and
the progress chip shows whenever an update runs and the modal is not
visible — also when the status stops offering it mid-run.
- Plugin update reports its running step (plugin-update-progress.json →
plugin_update_progress); a hidden update or clone leaves a progress chip.
- Clone here can run in the background.
- Git identity: extra SSH keys are added inline per host (no More keys tab).
- Users: spend today (viewer's local day, ?tz_offset=).
- Prettier on the files touched since v2.8.20.
- A Claude Docs `batch` that creates a doc is captured like an `Artifact`
publish, titled by the doc's name and linked by its own frame URL.
- At Stop/SessionEnd, each pptx/docx/xlsx/pdf/html a workspace session
created or changed is snapshotted into its owner's library (`auto`);
an unchanged file is skipped, a changed one refreshes its snapshot.
- Server mode: a page with no owner carries a suggested owner (the
workspace or home its session ran in). The suggested user claims or
declines it; an admin claims or assigns any. New tab Unowned /
Is this yours? on the Artifacts page; all three actions audited.
- The update dialog can be closed while an update applies: it keeps running,
the page still reloads itself when the new server is up, and a failure
re-opens the dialog with its error.
- Account pool card: one tab per AI tool (Claude Code, Codex, Cursor CLI,
Oh My Pi, and every other installed tool, which says it has no pool),
accounts as cards with 5-hour and weekly quota shown as what is LEFT plus
their reset times, and the saved profiles of Settings → AI tools →
Profiles as cards with "Share to pool".
- Cursor account pool (server/lib/cursor-pool.js): Cursor API keys handed to
a cursor-agent pane only through tmux -e CURSOR_API_KEY;
`agenthub accounts add <name> --tool cursor`.
- Pool groups (server/lib/pool-groups.js, /api/accounts/groups): a group's
members run only on its accounts of each tool, a user in no group on the
accounts no group lists, a pin still wins. account_pins loses its tool
CHECK (rebuilt, pins kept); three new tables.
- Oh My Pi login sharing can be switched from its pool tab too.
- POST /api/me/avatar/url stores the https link (users.avatar_link) instead
of downloading it; GET /api/me/avatar and /api/users/:id/avatar answer 302
to it (no referrer). Link rules: https, no userinfo, <= 2048 chars, no
localhost or private/reserved IP literal. Upload and link replace each other.
- Settings -> Account first loads the link as an embedded image and refuses
one this browser cannot show: a picture behind a login (Mattermost's
/api/v4/users/<id>/image) gets no cookie cross-site and 401s.
- Files sidebar: each workspace under its owner's home, chevrons remembered
per browser (agenthub.files.homesOpen); ownerless ones stay flat.
- openapi-extra/run.js describes tmux pane runs only: the removed
POST /api/run/{id}/message and stream-json run_stream/run_input_ack are gone.
- 108 operations added to OpenAPI (lanes, run, settings, reports, cc-config,
pets, ai-proxies, tool-profiles, ai-tools, snapshots, locks, ...);
openapi.yaml regenerated.
- docs/API.md: 74 routes added, 4 endpoints that no longer exist removed.
- Role is `developer`, not `member` (CLI.md, DATABASE.md, OVERVIEW.md);
`agenthub update` -> `update-check`; help lists `server install-system|tools`
and `users provision`; ARCHITECTURE.md's stale paths and names fixed.
- cc-config/codex-config writes carry sameOriginGuard (an MCP server or plugin
is code Claude runs later), with a test; `command -v` takes its name as $1.
Settings → AI tools → Profiles holds one tab per tool: Claude Code (the
cc-profiles panel, moved out of the Claude Code page's modal — its button
now links here), Codex (the whole $CODEX_HOME/auth.json) and Oh My Pi
(`omp auth export` / `omp auth import -`, secrets on stdin; omp's credential
tables are emptied first because import merges, and a failed import puts
the outgoing set back). Antigravity is listed as not switchable: agy keeps
its login in the OS keyring.
server/lib/tool-profiles.js + /api/tool-profiles keep cc-profiles' rules
(one login = one profile, the outgoing login is re-saved before a switch,
an unsaved one refuses it, the live login's profile is not deletable) and
never return a token; /tool-profiles joins ADMIN_WRITE_PREFIXES and
STAFF_ONLY_PREFIXES, writes are same-origin only.
The account pool takes a saved Codex profile too (moved in like a Claude
one, GET /api/accounts/profiles?tool=codex). Each Claude/Codex profile has
a "Move to pool" link (/admin?poolAdd=&poolProfile= opens the pool's Add
dialog on it), Settings shows the pool's size, and the pool card links back.
omp's gateway re-reads its clients file only every 10 s, and an omp that
starts while its key is refused fails model discovery once and hangs for
good. omp-share.js now keeps four keys queued in the clients file (hashes
only; the keys in <data dir>/omp-share/tokens.json, 0600): a new pane takes
one already listed (AGENTHUB_OMP_KEY), a fresh one joins the queue, and a
gone pane's key leaves at the next sync.
- omp-share.js: an admin's switch (S3) runs omp's own auth broker + gateway as
the host on loopback; a member's omp pane reaches it as a scoped client keyed
by its pane token (clients file = live omp panes only), runs on
--model agenthub/<model>, never holds a credential.
- omp catalog: --allow-home, so a workspace at the account's home keeps its
sessions there (they were recorded under a temp dir and dropped).
- Settings → Appearance → Open Markdown as: Preview (default) or Raw, for
Files/Changes, the config viewer and a chat's file panel.
- First-login password change and password rules, avatar URLs, Artifacts
"Mine" includes installed shared skills/plugins, a member's own hook pages.
- Artifacts page laid out like an app store: hero search, category chips,
shelves by kind, app-icon cards with one primary pill and a detail sheet
- POST /api/artifacts/:id/uninstall: a skill's copies in ~/.claude/skills and
~/.agents/skills are moved to ~/.claude/agenthub-replaced-skills/
(-uninstalled-<time>), never deleted; a plugin runs
`claude plugin uninstall --scope user --keep-data` as the caller;
ENOTINSTALLED when nothing is there; allowed while installs are off
- Uninstall on the card, the ⋯ menu and the detail sheet, after a confirm
- The chat over a terminal pane now covers Claude, Codex, omp, Cursor and
Antigravity: Cursor's agent transcript and agy's step log
(brain/<id>/.system_generated/logs/transcript_full.jsonl) are read as
conversations; Cursor sessions are claimed by their workspace
- Replies appear as soon as they are written: the projects-tree watcher
broadcasts `transcript_changed` (at most every 250 ms per session) and the
chat re-reads the transcript while the terminal underneath draws
- Stop shows from the pane's own busy line (Claude/Codex `esc to interrupt`,
agy `… esc to cancel`), or fresh output for omp/Cursor — no hooks needed
- Permission prompts read off the terminal become a card with the tool, its
command and "Deny with a note"; Codex gets Permission mode / Model buttons
that open its own numbered pickers, answered from the menu card
- Artifacts: cards for pages/documents/diagrams/tables/React components, a
Preview | Code panel with versions, copy, download, save to the Artifacts
Hub, full screen; sandboxed iframes without allow-same-origin
- Claude panes get CLAUDE_CODE_ENABLE_TODO_TOOLS=1 so the Progress panel has a
plan; multi-question pane menus read their Next row and question tabs
- The realtime chat bridge (structured stdio, /api/chat-bridge, /ws-chat)
from the unreleased ad9d475 is not kept
- Changes panel: **Discard all** beside Commit discards every non-conflicted
file in one `discard`, after a dialog naming how many untracked files go.
- Terminal: a `claude` pane gets dropped/pasted images as bare paths (shown as
`[Image #n]`) and other files as `@` mentions (`claudePastes`); a local
browser's copied `file://` paths and text-drag file lists paste by path.
- `POST /api/run/drop-file`: a loopback browser outside server mode may drop
up to 2 GB, streamed to disk; remote stays 20 MB.
- cc-config skills/commands/plugins read the USER scope of the account the
pane runs as (S3 member home by `cwd`), never another user's or the host's
for a non-admin without an account; the run form passes its cwd to plugins.
- Chat: an Agent step shows its task, status and tool calls and opens its conversation in place; Windows paths (E:\…, C:/…) in replies open as files
- Prompt: attach a file on this computer by path (native picker), clearer too-large messages
- Artifacts: edit title/description and a skill's files (new snapshot), Reinstall, install history, Hub-only rows, skills installed into ~/.claude/skills and ~/.agents/skills
- Files: batch upload of small files in one extract
- AI Tools: npm install falls back to --prefix ~/.local when the global prefix is not writable
Whole tree from parallel sessions, plus the version bump:
- Clone: a queue with live progress (Add workspace and extra folders),
clones run under nice/ionice so the machine stays responsive.
- Settings → AI tools → Session defaults: a new session's default
permission mode / bypass per account.
- Chat lists the background subagents that have not reported back yet.
- A file pasted or dropped into a claude pane goes in as an @ mention.
- Changes panel: say why amend/undo is refused on a pushed commit.
- openapi.yaml regenerated (Artifacts, Host, resumeById).
The File Explorer's New menu (and its right-click menu) gains Clone
repository: it clones a URL into the folder it has open — of the user's own
home or of a workspace's own folder — through POST /api/lanes/clone with
that folder as `parent`, so the URL allowlist, the own-folder check and the
folder account's own git identity all apply, and opens a workspace on the
clone. ECREDENTIALS asks for a login, ESSHAUTH points to Settings → Git.
GET /api/homes now gives `me` its absolute `dir`. Keys typed in the dialog
never reach the explorer's shortcuts (Backspace / Delete).
- omp-usage.js / opencode-usage.js: poll each CLI's own session store into
sessions (tool omp/opencode), token_usage per model and subagent agents;
transcript routes read them (omp journals, OpenCode's opencode.db).
- Grok runs the Claude hooks in ~/.claude/settings.json: its hooks are now
recognised (hookEventName + workspaceRoot), tagged tool=grok, and their
transcript_path dropped instead of being parsed as a Claude transcript.
- Catalog `resumeById`: omp/Grok/Cursor `--resume <id>`, OpenCode
`--session <id>`; session ids may carry `_`. The client offers Resume for
any tool that resumes by id.
- Kimi Code and GitHub Copilot leave the catalog (their session stores could
not be verified).
The wheel puts a pane into tmux's copy-mode, which took every typed key as
a scrollback command and painted a yellow [0/0] box in the corner. In
AgentHub's own panes (every other session on the server keeps tmux's
defaults, restored verbatim):
- the copy-mode position box is off (copy-mode-position-format "");
- the first keystroke after a mouse report leaves copy-mode
(`copy-mode -q`, pty-attach) before it is written, once per scroll;
- copy-mode keys that open a tmux prompt (g, :, /, f, digits…) leave
copy-mode and reach the program;
- the wheel scrolls 10 lines a notch instead of 5, and the first notch
already scrolls instead of only entering copy-mode.
Each account row in the Account pool card has a pencil: tick the users
whose sessions run on it (one moves off any other account, unticking sends
one back to the balancer). PUT /api/accounts/:name/users saves the set in one
transaction, audits each change and switches a Claude login into each added
user's home at once. The card-level switch button and pinned list are gone.
PUT /api/accounts/pins (and agenthub accounts pin) now writes a pinned
Claude login into the user's home right away (claude-pool.switchHome):
every home's newer pair is copied back first, the member's own login is
never written over, and no pane is restarted — running sessions take the
new pair at their next credentials read. A live home stays on the login it
holds, so unpinning does not flip it back. The Users page calls it
"Switch a user's account" and says what happened.
An admin pins a server-mode user to one pool account per tool
(account_pins; PUT /api/accounts/pins with the password step-up and audit,
the Users page's Pin a user, agenthub accounts pin|unpin). The workspace
owner's pin wins over the balancer whenever the pane may use the account;
a pinned login still waits while another pool login holds the home.
v2.8.9 crashed at start on every plugin install with "Cannot find module
'js-yaml'": server/lib/lane-detect.js requires it, but it was only a
devDependency that redoc used to pull in, and v2.8.8 dropped redoc. The
plugin installs with --omit=dev. This box hid it: Debian's global
/usr/share/nodejs has js-yaml. Reproduced with a pruned install and the
global paths off; fixed by moving js-yaml (and argparse in the lock) to
dependencies. New runtime-deps.test.js fails on any server/ require that is
not a dependency.
Claude refused `/tui` ("Cannot switch renderers in this session — it has
restrictions a restart can't carry over") in every pane started with
--append-system-prompt-file, i.e. any workspace with instructions (member
panes usually have the owner's). The instructions are now written as the only
CLAUDE.md of a folder of their own (<data dir>/workspace-prompts/<id>/, a
member's prompt-<id>/ in the handoff dir via write-as), passed as one more
--add-dir with CLAUDE_CODE_ADDITIONAL_DIRECTORIES_CLAUDE_MD=1. Verified with
Claude Code 2.1.301: the file is listed in /memory and /tui switches. Codex
is unchanged (its pointer line names the same file).
- Sagi: clicking the pet opens its chat window directly (PetChat + SagiInfo
status strip, Needs you, Mute / Clear alerts); SagiPanel removed.
- Chat view answers Claude's unnumbered folder-trust prompt (parseBareMenu).
- pty-attach: resize/write after a Windows attach exited, and node-pty's pipe
errors, no longer throw out of the WebSocket handler.
- restart-helper: the server started by an update logs to
data/agenthub-server.log instead of discarding its output.
- update-scheduler: default interval 1 h (DASHBOARD_UPDATE_CHECK_INTERVAL_MS and its 60 s floor kept);
a scheduled tick with no /ws client is skipped, and the next browser to connect gets one catch-up
check when the last one is stale (websocket.js onConnection). Manual check unchanged.
- db-retention.js: shared session purge (used by POST /api/settings/cleanup too), opt-in
AGENTHUB_RETENTION_DAYS applied by the hourly maintenance in 25-session transactions with yields
(max 5000/run), bounded incremental_vacuum when the freelist is large, and a manual Compact
(VACUUM in a worker, converting to auto_vacuum=INCREMENTAL).
- db.js: new databases are INCREMENTAL; an existing one up to 32 MB is converted at startup.
- GET/PUT /api/settings/retention, POST /api/settings/compact (confirm: true); Settings → Data →
Session cleanup gets the Automatic cleanup control and Compact now (en + vi).
- Session sync leaves a transcript alone while hooks feed its session live
(active, newest event a hook's, touched within DASHBOARD_SYNC_LIVE_HOOK_MS,
default 5 min) and catches it up once quiet; a hook-created session (which
importSession skips anyway) is only snapshotted, never parsed.
- Hook writer keeps a per-transcript mark (applied after commit): an
unchanged TranscriptCache result skips token/compaction/API-error/turn
writes, a grown one walks only the entries after the last one handled.
- Codex usage: one stamp query per pass instead of one row per journal;
between full passes (first + every 5 min) only the newest day folders and
journals written in the last hour are statted; fork parents resolved from
the table or a full walk.
- Cursor/Antigravity polls back off to 60 s after idle passes
(server/lib/backoff-poll.js); lane sync runs only while another dashboard
shares the data dir (checked every 30 s).
- Plugin bootstrap: a failed npm install, a Node older than 22.5 or a failed UI
build is recorded in runtime/last-failure.json; the next SessionStart prints
it once as a systemMessage naming the log and /agenthub-doctor (a different
failure is reported again; a successful bootstrap clears it). The foreground
pass checks the Node version itself and spawns no worker on an old Node.
- ReDoc: the standalone bundle (redoc 2.5.4, MIT, licence files kept) is
vendored under server/vendor/redoc/ and the `redoc` dependency dropped —
a runtime install (npm ci --omit=dev --ignore-scripts) goes 126 MB -> 45 MB.
vendor/ is excluded from the header audit and prettier.
- git missing from PATH: lane git actions answer 503 EGITMISSING with the
install command instead of a bare ENOENT.
- package.json engines: node >=22.5.0 (node:sqlite), matching MIN_NODE.
- Docs: INSTALL.md is the one install/setup guide (SETUP.md merged in and now a
pointer); no VS Code extension claim, one client-install hint, no
windows-build-tools, better-sqlite3 notes scoped to checkouts, new
"Plugin bootstrap never comes up" troubleshooting entry.
- Chat view: an attached image's path is pasted on its own after the text
(promptForPane). claude turns a paste that is only an image path into
[Image #n], but left a Windows path inside a paste of text as a plain path.
Other files and @-mentions stay in the text as before.
- Plugin bootstrap: depsHash covers only what npm installs (no release
version), so a version bump no longer reinstalls runtime deps; the client's
installed tree is kept while its manifests are unchanged (no npm ci per
update); the user-side build runs vite only (tsc -b cost ~25 s / ~900 MB);
clientBuild is recorded only after a build that did not fail, so a failed
one is retried; npm output goes to runtime/npm-install.log.
- Server: a port held by another program ends with a clear message and exit 1
instead of an uncaught 'error' stack trace.
- Client: the onboarding wizard is lazy and rendered only before onboarding;
usePrefersReducedMotion moved to hooks/ so PetSettings no longer pulls the
Sagi chat/pane stack into the entry chunk (884 KB -> 431 KB, gzip 268 -> 133 KB).
- Settings → Git → Your git identity: SSH keys are one tab per host kind
(GitHub · GitLab, AWS CodeCommit, More keys, Other hosts); opens on the
AWS tab when only an RSA key exists.
- Artifacts: a file type the sandboxed preview cannot show (Office, zip,
PDF) offers Download only, never a blank Preview.
- Artifacts search: `%`/`_`/`\` in a query match literally (LIKE ... ESCAPE).
- Skill frontmatter rewrite replaces a key with its continuation lines and
quotes values YAML could misread.
- Plugin marketplace.json is read with a 1 MB streaming cap.
- Artifact lists look each owner up once.
- Files: right-click a sidebar workspace (Open, Copy path) or home (Open,
Open Trash), a path crumb (Open, Download as zip, Paste into, Copy path),
a Trash row (Restore, Copy path, Delete forever), the Trash area (Refresh,
Empty Trash) and an admin's per-user Trash folder (Open, Empty Trash).
- Users: row menu (Edit + Open home in Files for an admin, Copy username/email).
- Artifacts: card menu repeating its buttons, plus Copy link for a page.
- Git identity: named SSH keys beside the default ones (id_<type>_<name>,
max 10, DELETE ?id=); SSH logins per alias (`ssh://<alias>/` →
`ssh://<user>@<host>/`, so several AWS CodeCommit accounts share a host),
each optionally tied to one key (`agenthub.<from>.sshKey`) — a clone of
that URL offers only that key and persists it. Terminal sshCommand follows
the keys. Settings → Git: "More SSH keys" + account name/key in the AWS and
other-host forms. Docs/API.md, OpenAPI updated.
/users becomes /admin (redirected), a tab shell over the existing Users view
and a new Server tab backed by GET /api/host (server/lib/host-stats.js):
hostname/OS/uptime, CPU and load, memory and swap, every real disk with
HDD/SSD, NVIDIA GPUs, and each user's live share from two /proc samples —
attributed by the member's Linux uid (S3) or, for host processes, by the
inherited AGENTHUB_LANE_ID's workspace owner. Read-only, staff-only (/host
in STAFF_ONLY_PREFIXES), comm names only. Members' tmux card moves to the
Server tab.
- Files → Trash: ONE sidebar entry. An admin's opens on a folder per user's
Trash (My Trash first, item counts from GET /api/homes/trash, an unreadable
one showing its error); a folder opens that user's Trash (path bar
Trash › user, Up back to the folders). An admin may restore/purge in
another user's Trash (audited); purging asks first. The merged "All users"
list is gone from the UI.
- A home's Trash is its user's whole recycle bin (home store + host store,
items tagged `store`).
- `agenthub server setup` installs one Linux user per account on Linux by
default (`--no-system` opts out); install-system messages drop `sudo`.
- Settings → Git: the author email suggests <username>@<AGENTHUB_EMAIL_DOMAIN>.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A member's tmux server keeps the version it started with until their last
pane closes, but the dashboard asked it with TMUX_BIN, the newest tmux: a
3.8 client on a 3.6 server spun it at 100% CPU (three members needed a
manual kill on 2026-10-15).
- /opt/agenthub/libexec/tmux-member (install-system, in sudoers): runs a
member's tmux call with the binary of THEIR running server
(/proc/<pid>/exe, found from the socket in /proc/net/unix), else
TMUX_BIN. It runs as the member and execs only a process they own.
sudoArgv uses it once installed (linux-accounts readConf), TMUX_BIN
until then.
- Users → Members' tmux (GET /api/users/tmux): each member server's version
beside the installed one, sessions, agents working. An admin restarts one
(POST /api/users/:id/tmux-restart) or every idle outdated one
(POST /api/users/tmux-restart-idle), with their password again, audited
per member; a server that will not stop answers 409 ETMUXSTUCK naming
its pid for root.
- server-s3-system.test.js: `delete` is a provision action since Delete
user, so the "unknown action" case now uses `purge`.
Operators: run `sudo agenthub server install-system` once.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A tmux 3.8 installed under a running 3.6 server left PATH's 3.8 client
asking the 3.6 server, which spun at 100% CPU; the dashboard's
synchronous `has-session` on it had no limit and froze every page, hook
and terminal for five minutes (14:07–14:12 on the shared server).
- Every call on our own server now has a 5 s limit and the member's
one-minute back-off (`host: tmux server not answering` meanwhile), so a
stuck server costs the event loop one timeout per minute, not one per
poll.
- Every call on a running server runs that server's own binary
(`/proc/<pid>/exe`, found from its listening socket in /proc — asking
tmux would be the very call that hangs), re-read every 10 s; PATH's
tmux only starts a server or answers `-V`.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>