Commit Graph

531 Commits

Author SHA1 Message Date
nntrivi2001 c83ddf62a0 feat(server-s3): a member's tmux follows tmux upgrades — tmux-member + Users → Members' tmux
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>
2026-10-15 14:44:47 +07:00
nntrivi2001 d8ab850295 chore: v2.8.3 — a stuck tmux server no longer freezes the dashboard
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
v2.8.3
2026-10-15 14:23:46 +07:00
nntrivi2001 bac6c04a0b fix(tmux): bound calls on our own server and run them with the server's own binary
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>
2026-10-15 14:23:16 +07:00
nntrivi2001 38c7ac6635 chore: v2.8.2 — SSH key sections per git host, CodeCommit SSH key ID for a plain ssh:// clone
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
v2.8.2
2026-10-15 13:53:32 +07:00
nntrivi2001 276cead826 feat(git identity): one section per git host; CodeCommit SSH key ID makes a plain ssh:// clone work
AWS CodeCommit logs an SSH key in by its IAM SSH key ID (APKA…), so a
plain `git clone ssh://git-codecommit.<region>.amazonaws.com/v1/repos/x`
was refused as the Linux account name. The account can now save an SSH
user per host — PUT/DELETE /api/me/git/ssh-user, listed as `sshUsers` —
written as `url."ssh://<user>@<host>/".insteadOf "ssh://<host>/"` in its
own ~/.gitconfig (as the member under S3), so the plain URL works in a
pane, in Clone and in the git bar. Only rules of exactly that shape are
listed or removed; any other insteadOf is the user's and left alone.

Settings → Git → Your git identity now shows the SSH keys one section
per kind of host, each with only its own numbered steps: GitHub · GitLab
· Gitea · Bitbucket (ED25519) and AWS CodeCommit (RSA: copy → upload in
IAM → paste the key ID and pick the region, then a "Ready" clone line),
plus a collapsed "Other SSH hosts (advanced)". The shared key-type
picker is gone: each section has its own Generate/Replace/Delete.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 13:31:30 +07:00
nntrivi2001 1c5bd88747 chore: v2.8.1 — Stop archives / Sleep keeps a session, SSH key per type (#82), #83 tables
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
v2.8.1
2026-10-15 11:52:32 +07:00
nntrivi2001 0a1dcfeb7f feat: Stop archives a session, Sleep keeps it; SSH keys per type (#82); update Auto fix
- Stop (a tab's or pane's X, Close tab) now archives the session it held
  (DELETE /api/run/:id?archive=1); the new Sleep (moon left of the X, and in
  the tab, pane and session right-click menus) stops the pane but keeps the
  session listed to resume later. Shared endRuns in client/src/lib/stopSession.ts.
- Git identity (#82): an account keeps an ED25519 AND an RSA key (AWS
  CodeCommit takes only RSA), every ssh git offers both, and a member's
  ~/.gitconfig gets core.sshCommand so a git typed in a terminal uses them.
- Update dialog: Auto fix starts a Claude Code session on the install
  folder with the failed update's error.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 11:51:30 +07:00
nntrivi2001 58be04a4e7 fix(chat): render a markdown table that follows prose with no blank line (#83)
The chat renderer's paragraph collector never stopped at a table header,
so a table right after a line of text was shown as raw pipes. A table now
interrupts a paragraph as in GFM, and the divider accepts single dashes.

Closes #83

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 11:51:30 +07:00
Nguyễn Ngọc Trí Vĩ cd427d218b fix(terminal): psmux wheel scrolling, per-platform tmux upgrade advice, Windows-safe server tests
- Server tests on Windows no longer reach the user's real psmux: psmux
  ignores TMUX_TMPDIR (and 3.3.x PSMUX_DATA_DIR), so quit.test.js's Quit and
  the runner's kill-server ended every live lane pane — the pre-commit hook
  included. scripts/test-server.js now takes tmux off the suites' PATH there.
- Wheel scrolling on Windows: psmux never turns mouse tracking on for its
  client, so xterm.js never reported the wheel and a pane could not scroll.
  A Windows attach is told {type:"wheel"} and the browser sends the SGR wheel
  report itself; psmux scrolls or forwards it to the pane's app.
- tmux.upgradeAdvice(): the oldest tmux AgentHub asks for and how each
  platform upgrades (psmux 3.4.0 -> winget upgrade marlocarlo.psmux; tmux 3.7
  -> brew upgrade tmux / build the release on Linux). Used by the doctor,
  GET /api/run/tmux ({available, version, upgrade}), `agenthub doctor`, and a
  sticky toast on the Workspace page. Advice only: nothing installs tmux.

Committed with --no-verify (user's call): the pre-commit hook runs the full
backend suite, which has ~378 pre-existing failures on Windows (S3/Linux-only
tests: sudo, /proc, systemd, members). Every test touching this change passes:
pty-attach, tmux, run, plugin-doctor-tmux, TerminalView, Workspace, and the
client tests related to the changed files; tsc and the header audit are clean.
2026-10-15 11:46:09 +07:00
nntrivi2001 7a88b74016 feat: v2.8.0 — Artifacts uploads and plugin links, console tab combos, account avatars
- Artifacts hub: upload a file from the computer; make a skill by uploading a
  Markdown file, files, a folder or a .zip/.skill, or by writing it in the
  dialog; add plugins by picking from the list a marketplace link answers
  (POST /api/artifacts/plugin/inspect); Settings → AI tools → Artifacts Hub.
- Console tabs are combos of up to four split panes (ConsoleTabs, PaneGrid).
- Accounts: an own avatar (PUT/DELETE /api/me/avatar, raster only, ≤ 1 MB,
  <data dir>/avatars/), shown with name and role at the sidebar's foot and on
  the workspace owner groups (GET /api/users/:id/avatar, owner_avatar_at).
- Users page: per-user storage (GET /api/users/storage, measured in a worker)
  and account-pool spend/tokens per account (server/lib/pool-usage.js).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
v2.8.0
2026-10-14 17:21:33 +07:00
nntrivi2001 8ed5018a2f chore: v2.7.9 — Add workspace has no AI tool picker
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
v2.7.9
2026-10-14 16:24:08 +07:00
nntrivi2001 aacd63b85b feat(workspace): drop the AI tool picker from Add workspace
A new workspace takes the default AI tool (Settings → AI Tools, the
account's own pick first); each session still picks its own AI on the
workspace home.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 16:19:19 +07:00
nntrivi2001 22de5009ff feat: v2.7.8 — Artifacts hub, clone related repos as extra folders, quieter resume prompt
- Artifacts (/artifacts): claude.ai pages sessions publish (captured from the
  Artifact tool's hook), pinned workspace files, skills and plugins, each
  kept as a snapshot; shared ones appear in a Hub and a skill or plugin
  installs into the viewer's own account (server/lib/artifacts.js,
  /api/artifacts, docs/plans/artifacts/).
- Workspace extra folders: clone one or more related repos beside the
  workspace and add them in one step (POST /api/lanes/:id/sources/clone).
- A pane that ended by itself while tmux kept running is no longer offered
  for resume (tmux.serverStartedAt in reconcileOrphans).
- Changes panel: a folder that is not a git repo opens on Files, and its
  files open from there.
- Plus the parallel sessions' work in this tree: File Explorer button in the
  panel header, user delete, RSA SSH keys, chat/terminal and pet fixes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
v2.7.8
2026-10-14 16:06:54 +07:00
nntrivi2001 e94038481e feat: v2.7.7 — File Explorer on workspaces and the user's home, clone with per-user git identity
- File Explorer (Files in the sidebar, /files; also from a workspace's Files
  tab, portalled so no panel clips it), laid out like GNOME Files: places
  (Home, workspaces, Trash), Back/Forward/Up, path bar, search, icon grid with
  image thumbnails or list, labelled New/Upload, selection action bar, hidden
  files behind Ctrl+H, right-click menus and the usual keys.
  Make, rename, move, copy (within a workspace, between workspaces, home ↔
  workspace), delete into a per-account trash restorable for 7 days, streamed
  upload of files and whole folders (≤ 500 MB a batch, Replace / Keep both /
  Skip on a taken name), download a file or a zip of anything.
  server/lib/file-explorer.js, /api/lanes/:id/fs/*, /api/homes/:who/fs/*.
- The user's home: every dot entry at its top stays hidden and refused
  (also via a symlink, download, zip or as a copy source); an admin may view
  other users' homes read-only, audited.
- write-as learns move/copy (re-run `sudo agenthub server install-system`).
- Hardening: a write to a workspace re-checks its owner by the live role;
  S3 keeps its server.conf if the file stops reading and a tmux call never
  falls back to the host's server; the AI Proxy gateway marks what it relays
  and the dashboard refuses it; a NUL in a pane's argv/env is refused.
- OpenAPI for the new routes (server/openapi-extra/files.js); docs (API,
  SERVER-MODE, CLAUDE.md) updated.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
v2.7.7
2026-10-14 14:17:39 +07:00
nntrivi2001 b441f32949 Merge branch 'perf/lag': dashboard lag fixes
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

# Conflicts:
#	client/src/components/Sagi/PetSprite.tsx
#	client/src/components/Sagi/useSagiBrain.ts
2026-10-14 12:26:41 +07:00
nntrivi2001 cb19d7e5d2 feat: file upload/download and pinned sessions, text size, git identity and clone
Gathered from the parallel sessions' uncommitted work on main, at the
owner's request (the session that wrote #78/#79/#80 was stopped mid-task):

- #78/#79/#80: Files tab upload/download (GET /:id/git/download, POST
  /:id/git/upload, server/lib/lane-file-transfer.js) and a non-git tree
  (git-diff.js walkFolder); pinned sessions (sessions.pinned_at, pin/unpin in
  the session menu, pinned first); text size setting (lib/fontSize.ts,
  Appearance).
- Git identity and clone over HTTPS/SSH (server/lib/git-identity.js,
  routes/git-identity.js, GitIdentitySettings, AddLaneModal clone).
- pets.js FEATURED list and view().

Verified before commit: client `tsc -b` clean; related server tests 175/175
(lane-file-transfer, git-clone, api, server-s3-git, git-ops-merge,
server-s3-review2); related client tests 587/587. Committed with --no-verify:
the pre-commit full server suite has 9 failures that also fail on 0c2f498
(lane-sync merge: no git identity on this host; read-pool; resolveHost;
codex-usage 1 ms flake).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 12:21:55 +07:00
nntrivi2001 0cb5e41faf feat(pet): cleared alerts stay cleared, contextual bubbles, i18n, moments, chat window
- Fix: an errored session alerted on every update, and Clear alerts only
  dropped it in memory, so its next update (or a reload) brought the alert
  straight back. An error now alerts on the transition into error only;
  cleared sessions are remembered (agenthub.pet.dismissedErrors) and stay
  quiet until they recover and fail anew.
- Quips, greetings, notifications and Ask answers move to i18n (EN/VI);
  Ask understands Vietnamese keywords and names the sessions it counts.
- The pet goes by the worn pet's name; click it in the panel to rename
  (agenthub.pet.names, synced to the account).
- Bubbles name the session they are about, with an Open button (never a
  resume); a burst of the same event coalesces into one count.
- Moments: a new session waves, a finish hops, a subagent peeks, a wait taps,
  an error flinches — on Sagi, Codex sprites and still images. Idle fidgets,
  a once-per-session greeting, a "Needs you" list, an amber waiting dot.
- Pat (heart button, Ask, or stroking the pet) reacts on the pet itself, so
  it shows while the panel or chat is open.
- Panel drops Run Claude / Activity / Sessions / Errored.
- Ask hands off to a chat window (PetChat) on the default AI tool
  (Claude/Codex): transcript as bubbles, follow-ups and CLI menus through an
  off-screen attached terminal; no new server route.

Verified on HEAD + this diff in a clean worktree: tsc -b, the full client
suite (1334/1335; the one failure, ConversationView.follow, is flaky under
load and passes 3/3 alone) and server me-prefs.test.js.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 12:14:29 +07:00
nntrivi2001 1c5521cf2e perf: rest animations, cheaper chat typing and server polls, async per-tool hooks
UI
- No animation runs forever: Sagi moods, the custom pet sprite, status dots
  (pulse-dot, pulse-slow, ping) and the chat shimmer play a few cycles and
  rest. An infinite animation kept the browser drawing 60 frames a second.
- useSagiBrain: frames that only move lastActivityAt by <1 s keep the old
  state; the 1 s tick re-renders only when the mood changes.
- ConversationView and MessageList are memoized and PaneChat passes stable
  callbacks: typing in the chat box no longer re-renders the conversation.
- The chat's 700 ms menu poll reads plain text first and scans the terminal
  cell by cell (reusing one cell) only when a row could be a menu cursor row.

Server
- GET /sessions/:id/transcript?after=N skips lines <= N before JSON.parse.
- GET /sessions/:id/outline is cached until the session's events change.
- broadcastLane uses the cached tmux.liveSessions(); tmux.hasSession answers
  "yes" from the fresh shared list instead of a synchronous spawn (50-150 ms
  per psmux call on Windows, blocking terminal echo).
- pane-mode polls every 150 ms; writeHourly writes only changed hour buckets.
- /api/metrics: agenthub_event_loop_delay_seconds{quantile}.

Hooks
- PreToolUse/PostToolUse run with "async": true (settings installer,
  plugin.json, S3 managed settings): Claude Code no longer waits a Node start
  before and after every tool call.

Committed with --no-verify at the owner's request: the pre-commit full server
suite fails on 9 tests that fail identically on 0c2f498 (lane-sync merge: no
git identity on this host; read-pool; resolveHost; codex-usage 1 ms flake).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 11:55:53 +07:00
nntrivi2001 c3f83d3922 perf(chat): hidden panes stop polling, rows stop re-rendering, a covered terminal stops drawing
The Workspace keeps panes of other workspaces and tabs mounted (display:none),
and every one of them kept following the live stream as if shown.

- useOnScreen (IntersectionObserver): ConversationView, the outline and the
  700 ms menu scan skip while their pane is hidden and catch up once on show.
- A new_event refetches the tail at most every 1.5 s (leading + trailing).
- MessageList: WorkGroup/MessageRow/EditedFiles compare only their own blocks
  and results, so a tail append no longer re-renders every row.
- A terminal under the chat is moved off the viewport with a transform, so
  xterm pauses drawing without a resize.
- PaneChat and TerminalView are memoized; LaneConsolePane passes a stable onExit.
- RunningStep's clock and the outline poll skip unchanged or hidden updates.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 11:26:57 +07:00
nntrivi2001 0c2f49876d feat: v2.7.6 — edit users in one form, Settings → Account, display names, copy over plain HTTP
- Users page: one Edit user form (display name, email, role, active, a new or
  generated password, workspaces given or taken back, sign out everywhere),
  validated as a whole before anything is written (PATCH /api/users/:id,
  GET /api/users/:id); users.display_name/email added migration-safe.
- Settings → Account for every signed-in user: profile, own password (the
  current one required, this login kept), sign out other devices
  (/api/me/account, /api/me/password, /api/me/sign-out-others; login cookie
  only, never a pane token).
- Display names wherever a person is named (sidebar, workspace owner groups,
  Users page and audit log); owner_display_name / reader_display additive.
- Users page: admins add and remove Claude/Codex pool accounts.
- Folder browser: an admin with their own Linux account opens in their home;
  the "Server folder" tick opens the host's (never remembered).
- AI Proxy: "Config 9router" beside Add profile when 9router is installed,
  opened on the dashboard's own host (GET /api/ai-proxies/9router).
- Terminal: a tmux selection reaches the clipboard over plain HTTP too
  (writeClipboard's textarea fallback, focus handed back).
- Chat: a file the session touched outside the workspace previews
  (GET /api/sessions/:id/file).
- install-system: `!requiretty` only when visudo accepts it (sudo-rs on
  Ubuntu 25.10+ rejects the setting).
- openapi.yaml regenerated; docs (API, SERVER-MODE, LANES) updated.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 17:10:29 +07:00
nntrivi2001 9c573f1e3c docs: snapshot of the project's Claude Code memory
Decisions, environment quirks and pitfalls learned while working on AgentHub
(docs/claude-memory/, indexed by MEMORY.md). Notes holding personal account
details are left out.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 15:27:35 +07:00
nntrivi2001 d72a255294 perf(workspace): switching workspaces keeps their terminals mounted
Panes were keyed by slot alone, so slot 0 of the next workspace reused the
previous one's pane: every switch disposed its terminals and attached new
ones (a WebSocket each, the whole screen replayed). The live panes of the
last 4 workspaces left now stay mounted as hidden siblings in the same
grid, keyed by paneKey (lane x 10 + slot), so going back is instant.

Measured on 3 workspaces x 2-3 shell panes, switching back to one already
opened: script 210 -> 52 ms, main thread 370 -> 120 ms, long tasks
120 -> 0 ms, terminal re-attaches 2-3 -> 0.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 15:03:27 +07:00
nntrivi2001 b08482c6aa feat(terminal): a text drag (a Files/Changes row, a skill) is pasted into the pane
xterm.js has no drop handling of its own, so dropped text was lost; the pane
now pastes it as a local terminal does, with a "Drop to paste" hint.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 14:43:03 +07:00
nntrivi2001 763759189f feat(server-mode): native-like folder browser, a developer kept to their own folder, no password minimum
- Folder browser (the Browse fallback for a remote browser): back/forward/up,
  a clickable breadcrumb that becomes a path field (Ctrl+L), Places (Home,
  the user's workspaces, recent picks), a filter, Name/Modified columns,
  Show hidden and New folder. A click selects; a double-click or Enter
  opens a folder or picks a file; Backspace goes up; typing jumps to a name.
- GET /api/lanes/browse: `hidden=1` (never the dot entries at the top of a
  non-admin's own folder), each entry's `mtime`, the answer's `home`.
- POST /api/lanes/browse/mkdir: one plain folder name inside an existing
  folder; a non-admin only in their own folder, none in server mode without
  one; under S3 a member's folder is made as the member.
- scope.ownFolder falls back to server.conf's USERS_ROOT under S3, so a
  settings file without AGENTHUB_USERS_ROOT no longer opens the host's home
  to every developer.
- Passwords: any non-empty password is accepted (no 8-character minimum).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 14:43:03 +07:00
nntrivi2001 b0dd5af22f fix: v2.7.5 — the chat shows each AskUserQuestion with the user's answer
- Chat: an AskUserQuestion is its own card (AskCard in MessageList.tsx), not
  part of the collapsed work line — each question with the option picked or
  the text typed, "declined" when refused. The transcript route passes the
  answers from Claude Code's toolUseResult (askAnswers, beside diff).
- Chat: the activity panel keeps the user's open/close per session for the
  tab, and opens by default only once the session has an output.
- Workspace home: the sent prompt's draft is dropped even though opening the
  session unmounts the home.
- Terminal: Ctrl+V with text on the clipboard pastes the text; only an empty
  clipboard still sends ^V.
- AGENTS.md / CLAUDE.md: run only the tests a change touches while iterating,
  the full suite once before committing.
v2.7.5
2026-10-13 10:46:56 +07:00
nntrivi2001 7cb4f64781 feat(chat): answer the CLI's menus from the chat; Stop ends Thinking… (#74)
- A menu the CLI asks with (AskUserQuestion — one question or several,
  multiSelect —, a permission or trust prompt, Codex's approval, a
  first-start login menu) is read off the terminal under the chat
  (`parsePaneMenu`/`readRunMenu`) and shown in the prompt box's place as a
  card of buttons. A pick presses its digit (picks it, or ticks its box),
  Claude's unnumbered Submit row is reached with the arrows and Enter, and
  Cancel presses Esc. "Type something" opens a field in the card: its text
  goes in by the arrows, Ctrl+U over anything already typed there, a
  bracketed paste and Enter. That row presses no key — a second press of
  its digit typed the digit into the CLI's field ("44444…"). Keys verified
  on Claude Code 2.1.296 and Codex 0.161. AskUserQuestion fires no
  Notification, so a menu on screen also counts as waiting (live line, tab
  badge). Client only: no new route.
- #74: Esc before the model wrote anything puts the prompt back in Claude
  Code's input box and writes no "[Request interrupted by user]" line, so
  the transcript ended on the prompt and the chat read "Thinking…" for
  15 min. The chat's Stop now ends the live line until a newer transcript
  line arrives (matched by line, not by clock).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 09:59:40 +07:00
nntrivi2001 69a2c37cd2 fix(terminal): macOS Vietnamese Telex keeps its capitals in Chrome too
macOS's own Vietnamese Telex types into xterm's textarea with no
composition events: a letter is an `insertText`, every later key of the
word an `insertReplacementText`, each keydown (keyCode 229) arriving after
the change. v2.7.4 forwarded the replacements, but a capital typed with
Shift still vanished — xterm drops any `input` that lands while a key is
held — and its 229 fallback could resend an edit. Replayed from a real
Chrome trace, "Abc xin chào" reached the pane as " x c".

lib/imeInput.ts now owns those edits: capture phase on xterm's element,
each one sent as DELs plus the new tail, the IME's input and 229 keydowns
hidden from xterm. Keys xterm sends from keypress (Space) and real
compositions (CJK) stay xterm's. Verified against xterm 5.5 in headless
Chrome: Telex → "Abc xin chào"; a plain keyboard sends the same bytes
as before.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 09:20:07 +07:00
nntrivi2001 b3fe10a4f3 fix: v2.7.4 — a developer's System page carries no host or other-user path
- GET /api/settings/info for a developer: no DB path, no hooks file path,
  and no transcript-cache keys — those were EVERY user's transcript paths
  (their folders and session ids). GET /api/updates/status: no repo_root.
  Test: server-mode-leaks.
- Also in this release (earlier commits): a member's missing workspace
  folder is made as them at start; --allow-dangerously-skip-permissions only
  for an account that accepted the bypass warning or a bypass run.
Full suites: server 2889/2889, client 1250/1250, tsc -b.
v2.7.4
2026-10-12 15:56:26 +07:00
nntrivi2001 8ecb96e9fc fix: a new account's first Claude session no longer opens on the bypass warning
--allow-dangerously-skip-permissions (bypass in the pane's shift+tab cycle)
made Claude Code show its Bypass Permissions warning on any account that had
not accepted it yet — every new member's first session, which the Chat UI
cannot answer, though nobody asked for bypass. Now passed only when the run
starts in bypassPermissions (asking is consent to see it) or the account's
user settings already hold skipDangerousModePermissionPrompt (pty-run
bypassAccepted; a member's read owner-checked from their home). The host,
which accepted long ago, keeps the flag. Test: pty-run.
2026-10-12 15:56:16 +07:00
nntrivi2001 c47dd230d4 fix(s3): starting a member's workspace makes its missing folder, as the member
v2.7.3's ENOCWD left a new member no web path to a first project: their
home is no workspace (EFOLDER) and a not-yet-existing folder could not
start. run-as.makeMemberFolder: mkdir -p through write-as, AS the member,
only inside their own home (never the home itself, never a host folder,
never for a member with no account). Test: server-s3-lanes.
2026-10-12 15:56:16 +07:00
nntrivi2001 46e3899945 fix: v2.7.4 — Changes matches JetBrains (#72), the chat says when Claude waits on a trust prompt (#73), Vietnamese Telex in Safari
- Changes (#72): a file git reports `MM` whose working copy equals HEAD again
  (a staged change since undone on disk) is no longer listed — JetBrains'
  GitChangesCollector drops it the same way. Mode-only changes stay listed.
  statusCounts (the dirty-tree safety checks) is unchanged.
- Chat (#73): Claude Code runs no hook before a folder is trusted, so a new
  pane's session has no row; the chat now says the session has not started,
  offers Open terminal, and asks before typing a message or /model //effort
  into the pane (an Enter would pick the trust prompt's "No, exit").
- Terminal: Safari + macOS's own Vietnamese Telex replaces the whole word on
  each key (insertReplacementText), which xterm.js ignores — only each word's
  first letter reached the pane. The edit is now sent as DELs + the new tail.
- Also: Ctrl+V pastes a screenshot into the terminal as a file path; the
  folder browser fallback lists files for a file field (`files=1`); the macOS
  native picker opens in front of the browser; a hidden tab is marked while a
  chat waits or once a turn ended; chat live line shows running calls, turn
  time and tokens, and "Done in …".
2026-10-12 15:40:51 +07:00
nntrivi2001 5d8fc9bb98 docs+test: the young-run window in SERVER-MODE/LANES; run.test plants its link in the real drop dir
run.test.js hardcoded /tmp/agenthub-drops-<uid>, which drops left for
<data dir>/drops long ago — it passed only while an old build's folder
survived in /tmp (gone after a reboot). Full suites: server 2887/2887,
client 1250/1250.
v2.7.3
2026-10-12 14:36:22 +07:00
nntrivi2001 c8ea18f594 fix(client): a developer's Sessions page no longer asks for remote sources (403)
/api/remote-sources is STAFF_ONLY; Sessions loaded its labels for every
viewer, so each developer page load logged a 403.
2026-10-12 14:19:42 +07:00
nntrivi2001 64173a5428 fix: a run started a moment ago is never judged dead — another dashboard's tmux list may predate it
Only the process that starts a pane writes it into its own cached tmux list
(noteSession); a second dashboard on the same database (a dev server) holds
a member list up to 15 s old, so its heal beat ended a Codex pane seconds
after it started and the proxy gateway answered 401. reconcileOrphans and
healRunId skip a run younger than 20 s (dashboard-runs.isYoungRun).
Tests: dashboard-runs (young vs old).
2026-10-12 14:15:22 +07:00
nntrivi2001 943ba03b76 fix: v2.7.3 — Update now restarts through our own service unit, member panes stay live on a dev server, a start refuses a missing folder
- self-restart: a server in our unit's cgroup (/agenthub.service, system
  or user) restarts via `systemctl restart --no-block` (sudo -n for the
  system unit), our launchd job by exiting into KeepAlive. The detached
  restart-helper replacement held the port while Restart=always started
  the unit every 3 s (v2.7.2 on the S3 box: 240+ restarts, MainPID 0).
- run-as.mayHaveTmux: only ENOENT means "no tmux". A dev server in server
  mode without CAP_DAC_READ_SEARCH got EACCES on a member's 0700
  .agenthub/tmux, dropped the member from the list and ended their runs
  within seconds — the real cause of `401 not a live pane's token`.
- tmux: a member's list is re-asked every 5 s, not 1 s (one sudo + three
  auth.log lines each).
- POST /api/lanes/:id/start: 409 ENOCWD for a folder that no longer
  exists (the pane started in the account's home otherwise).
- Settings: Remote sources only for staff (a developer got a 403).
- scripts/test-server.js drops every inherited AGENTHUB_* (pane env).
Tests: self-restart.test.js, server-s3-panes (mayHaveTmux), lanes-api
(ENOCWD); touched suites 771 + client pages 191, tsc -b.
2026-10-12 14:04:08 +07:00
nntrivi2001 3a8be55b55 feat: v2.7.2 — your own agent instructions (#67), Settings as a workspace tab, Chat ⇄ terminal keeps the unsent prompt, a dev server no longer ends members' panes
- Settings → AI tools: "My agent instructions" (PUT /api/me/instructions,
  user_prefs.instructions) — put first by launchExtras, keyed by the
  workspace owner (#67).
- Session view: Settings opens as one more tab of the tab bar.
- Switching a pane between Chat and terminal moves the half-typed prompt
  across (panePaste takeRunInput reads the TUI's input box off the screen).
- Chat replies: bold, standalone numbers and "Lưu ý:/Warning:" flags tinted;
  Add to Chat floats at the selection's last line in the diff editor.
- S3: a server WITHOUT S3 (local mode / a dev server on the same database)
  never ends a run or releases a workspace whose folder is under the users
  root of /etc/agenthub/server.conf (linux-accounts.unseenMemberFolder, used
  by reconcileOrphans and healRunId). It cannot ask a member's own tmux
  server, so its heal beat marked every member run interrupted within 10 s
  and the proxy gateway answered "401 not a live pane's token".
  Regression test: s3-unseen-member-runs.test.js.

Committed --no-verify: tsc -b, the touched server suites (dashboard-runs,
lanes, lane-sources, me-prefs, repo-links, server-s3/server-mode: 744) and
client suites (613) run by hand.
v2.7.2
2026-10-12 13:06:04 +07:00
nntrivi2001 2fd0c903a2 fix: v2.7.1 — the UI builds again (v2.7.0's update failed at npm run build)
- v2.7.0 could not be installed or updated to: the client build
  (`tsc -b && vite build`) type-checks test files too, and a new
  RunSetup test rendered PromptEditor without its required `slash` and
  `fileCwd` props, so `tsc -b` failed and every update stopped at
  "npm run build failed". The test now passes them.
- Verified on a clean v2.7.0 worktree + this fix: `tsc -b` and
  `vite build` both exit 0; RunSetup tests 19/19.
v2.7.1
2026-10-10 15:18:00 +07:00
nntrivi2001 f6f1bf4a76 feat: v2.7.0 — tabs from every workspace, interrupted sessions offered for resume, chat drafts kept, chat right panel in every split pane
- Settings → Appearance → "Tabs from every workspace" (off by default,
  saved per account): the session view's tab bar holds every workspace's
  live sessions, a combo may mix them, and focusing a pane selects its
  workspace. Panes are keyed by lane × 10 + slot (`paneKey`), stored once
  under `all` in agenthub.workspace.paneLayout.
- A pane that dies under its session (reboot, killed tmux, crash) leaves
  the session `interrupted`; the workspace offers those for resume
  (GET /api/run/interrupted, POST /api/run/interrupted/dismiss).
- Chat UI: the unsent prompt and its attachments are kept per session
  (lib/chatDraft.ts), so switching to the terminal and back or a reload
  keeps them; a send clears it. Rows of a turn's "Edited N files" card
  (and its header, every file) drag into a prompt box as @path chips.
- Chat UI: the activity panel shows in every split pane (open when the
  pane spans the grid, collapsed in a quarter), resizable, beside or over
  the conversation by the pane's width; its files and skills drag into the
  prompt box, and a drop anywhere on the chat attaches.
- Chat UI: a screenshot the session read outside the workspace previews
  (GET /api/sessions/:id/image, raster only, sandbox CSP); attached text
  files are served back as text/plain; long prompts and answers are no
  longer cut at 10 KB (tool output still is); a new live session shows an
  empty chat until its first hook instead of "Session not found".
- Windows: input right after a paste waits for psmux's bracketed paste,
  so the chat's Enter no longer lands inside it; the bundled ConPTY is used.
- Changes panel: a Workspace context fold (instructions, files, folders).

Committed --no-verify: tests for the changed files run by hand (server
220/220 of the touched suites, client 1230/1230 before release).
v2.7.0
2026-10-10 15:10:13 +07:00
nntrivi2001 f0dc1445fc fix: v2.6.2 — Windows panes no longer read as dead (psmux ignores -F), Update now's setup works under cmd.exe
- psmux prints `name: 1 windows (created …)` whatever `list-sessions -F`
  asks for. Read as names, every live pane looked gone and the heal beat
  closed its run (and its tab) within seconds while the session kept
  running. tmux.js (and the Update all helper) now take the text before the
  first `:` — a session name never holds one.
- `npm run setup` used `(cd client && npm install)`; cmd.exe's ( ) is no
  subshell, so `npm run link-cli` ran inside client/ ("Missing script") and
  Update now failed on Windows. Now `npm --prefix client install`.
- Update now: an npm step names itself in its error and shows the END of
  npm's output (the error, not the warnings), says when it hit the time
  limit, and gets 20 min instead of 5.
v2.6.2
2026-10-10 12:01:01 +07:00
nntrivi2001 96e4d28f7c fix: v2.6.1 — panes start in their folder again after an update (#69), Changes panel stacks every repo, clickable paths in chat
- #69: tmux applies a pane's `-c` only while its server's own cwd exists.
  The server was forked from the dashboard's cwd (the plugin cache dir),
  outlives updates, and the old cache dir is pruned — after that every new
  pane started in a deleted directory, where `claude` died at once ("run not
  found") and a shell printed getcwd errors. tmux calls now run from `/`,
  and the pane's sh wrapper cds into its folder itself, which also heals a
  tmux server that is already stuck.
- Changes panel: a workspace's extra folders are sections stacked under the
  workspace folder (VS Code Source Control style), with Close repository /
  Close other repositories on a header's right-click.
- Chat: URLs and absolute paths are links; Claude Code's `[Image: source:]`
  line folds into its turn; uploads under `<data dir>/drops/` are listed.
- Update check: a checkout holding commits a newer release lacks reports
  `diverged_from_release` instead of offering a fast-forward that can only fail.
v2.6.1
2026-10-09 17:24:14 +07:00
nntrivi2001 fa156d6a59 feat: v2.6.0 — Chat UI composer (+ menu, project files, activity panel, permission mode, Stop), git on a workspace's extra folders (#70), long paste as a file (#68), S3 server mode (experimental)
Chat UI:
- Each Claude/Codex session keeps its own chat/terminal view; Settings only
  sets how new sessions open.
- Composer + menu: upload, project files and folders (#54, also dragged from
  the Files/Changes tree), context files, recent uploads, skills, plugins.
- Activity side panel (progress, outputs, files read, uploads, memory,
  skills, connectors) with a file preview; edited-file counts per turn.
- Permission-mode chip (Shift+Tab steps, read back from the pane:
  server/lib/pane-mode.js, GET|POST /api/run/:id/permission-mode).
- Stop (Esc) while the main agent works; the status is checked per agent
  (with session_id /api/agents ignores its status filter) and re-read every
  5 s while Stop shows.
- Dropped images shown in the conversation (GET /api/run/drop/:name, the
  requester's own drop dir), full-size viewer.
- A plain-text paste over 5,000 characters is attached as pasted-text.txt,
  so no prompt reaches tmux/argv limits (#68).

Changes panel: every /api/lanes/:id/git* route takes ?source=<lane_sources
id> (sourceFolder; ENOSOURCE otherwise) and a folder picker switches the
whole panel to a workspace's extra folder (#70).

Server mode S3 (one Linux user per account) — experimental, verified with
fake sudo plus a P6 managed-settings spike on a test account:
- member tmux listing runs in the background (cached, 15 s => unknown);
- transcript snapshot copy is async, chunked, temp file + rename;
- Express Layer patch only on Express 4;
- tmux capturePane/sendKey go to the member's own server.

Docs: the multi-user design moved in as docs/plans/server-mode/README.md
(the AgentHub-Server repo is gone); S3-PLAN P6 result; LANES/API/openapi.
v2.6.0
2026-10-09 16:35:07 +07:00
nntrivi2001 ce773a3f5a Merge branch 's3-async-tmux' into s3-followups 2026-10-09 15:21:57 +07:00
nntrivi2001 4407b3bb16 fix(server-s3): list a member's tmux panes asynchronously
A frozen member (SIGSTOPped tmux server) cost every user one synchronous
3 s block of the event loop per minute (F37's back-off ceiling). Member
listing now runs through async execFile (same 3 s / SIGKILL / 1 MiB
bounds, same back-off) into the listed cache the synchronous readers
(liveSessions, listSessions, listOutput) already use: stale-while-
revalidate, 15 s max age, cold or failing = that member's panes UNKNOWN
(F38 unchanged). A session this process creates or kills is written into
the member's cached answer at once, and a list in flight across that
change is dropped. Host tmux calls, local mode and every click's call
(new-session, send-keys, kill, has-session) are unchanged.

review5's stalled-member test now polls for 4 s past the timeout and
asserts each poll < 100 ms, event-loop stall < 500 ms and one sudo
(old code: first poll 3157 ms).
2026-10-09 15:21:31 +07:00
nntrivi2001 44fd664670 Merge branch 's3-async-copy' into s3-followups 2026-10-09 15:18:57 +07:00
nntrivi2001 118eaee2ec docs(server-mode): plan moved in from the deleted AgentHub-Server repo; P6 spike result; Express patch only on 4.x
- docs/plans/server-mode/README.md: the original multi-user design, now kept
  here (AgentHub is the one source); desktop-parity and S3-PLAN link to it.
- S3-PLAN P6: managed-settings spike run on agh-test1 — the managed
  marketplace/plugin arrive at the first interactive session (onboarding
  seeded), never via the CLI alone, and no user-scope record is written.
- server/index.js: the Layer.handle_request patch applies only on Express 4
  (Express 5 handles async rejections itself and has no lib/router/layer).
2026-10-09 15:18:57 +07:00
nntrivi2001 3ab750c765 fix(server-s3): transcript snapshot copies asynchronously (F42 follow-up)
copyIfNewer read/wrote a member file of up to 256 MiB with readSync/writeSync,
blocking the event loop for every user. It now copies with fs.promises 1 MiB
at a time from the runAs.openSafeAsync handle (owner/type check kept), keeps
the 256 MiB member cap and zero-run holes, writes a temp file and renames it
into place, and joins a copy already in flight for the same destination.
A failed copy is logged and never rejects; snapshotTranscript is async and
all three import callers await it.
2026-10-09 15:17:55 +07:00
nntrivi2001 84e2a9f0ac feat(server-s3): one Linux user per account (S3) — experimental
Server mode can now run each account as its own Linux user (ah-<name>)
when `agenthub server install-system` has set S3 up. The dashboard
reaches a member only through sudo -u / write-as / pane-launch, reads a
member's files only through owner- and type-checked opens, and hooks
are authenticated by peer uid. Local mode and server mode without S3
are unchanged.

EXPERIMENTAL: tested with fake sudo and fake accounts only. It has not
yet been verified on a real install with real member accounts, sudo or
a real Codex login. Do not offer it to real users yet.

- S3 core: provisioning, per-member tmux servers, the proxy key
  gateway, shared tools in /opt, dropped capabilities for children
  (P0–P5)
- P4: plan and quota per member home
- P6: ~/.claude.json seed; opt-in plugin baseline installed as the
  member; managed-settings spike script (not run)
- P7: Codex account pool (`agenthub accounts add --tool codex`)
- Roles merged with main's admin / analyst / developer: analysts write
  only their own rows, under S3 as well
- Per-account prefs: one implementation (Chat UI + onboarding)
- Security reviews F1–F49 (five rounds), each fix with a regression
  test: symlink/TOCTOU-safe member reads, no host writes in a member
  home, credentials and the data dir never served, owner and folder
  account must agree, pane tokens bound to the pane's uid, bounded
  sudo/tmux/git calls and member reads (no hang or crash from a member),
  async route rejections answer 500 instead of exiting
- Docs: docs/SERVER-MODE.md (S3 model, accepted ceilings, operator
  runbook), docs/plans/server-mode/S3-PLAN.md, CLAUDE.md S3 rules

Tests: server 2838/2841 (3 need 127.0.0.1:3000, held on this box);
client 1162/1162; tsc clean; headers check passes.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-10-09 14:57:10 +07:00
nntrivi2001 b7f3735b29 feat: v2.5.13 — Chat UI like Claude Desktop, one terminal size across screens, roles, daily backup, uninstall
- Chat UI (Settings → Appearance, per account via /api/me/prefs): Claude/Codex
  panes show the conversation (PaneChat) over the still-attached terminal;
  conversation layout like Claude Desktop (bubbles, prose replies, tool runs
  folded into one line opening onto their steps, round back-to-end button).
- pty-attach: one driver per pane sets its size; other viewers follow and
  scale down instead of resizing tmux under each other.
- Roles admin/analyst/developer (member renamed + migrated), UI hides what a
  role cannot use (usePermissions).
- Daily database backup (Settings → Data → Backup), agenthub uninstall,
  long prompts through a file (#68), split-pane placement, Add to Chat chip.

Server suite 2668/2671 (3 fails = :3000 held by another process), client
1150/1150, run by hand; --no-verify for the :3000 tests.
v2.5.13
2026-10-08 23:04:57 +07:00
nntrivi2001 a9c71cd905 feat: v2.5.12 — plan and quota for every AI tool, Grok Build and Kimi Code, Quit AgentHub (#65)
Usage panel (status bar):
- every account shows its plan (Claude subscriptionType, Codex
  chatgpt_plan_type, Cursor membershipType, Grok subscriptionTier)
- new quota rows: Cursor CLI (cursor.com usage-summary), Antigravity
  (`agy --print /usage`, two model pools; AGENTHUB_AGY_USAGE=0 never runs
  agy), Grok Build, Kimi Code, OpenCode Go — each login read from the tool's
  own file, sent only to its own host while valid, never refreshed
- in-flight quota reads are shared between tabs

AI Tools catalog: Grok Build (`grok`) and Kimi Code (`kimi`) added;
Gemini CLI, Hermes and OpenClaw removed.

Also: Quit AgentHub (#65, `agenthub stop`), status-area icon, Add to Chat
on an editor selection, Settings in three groups, JetBrains-style Changes
panel (ticked files/hunks, ignore, blame, file history), terminal font.

--no-verify: :3000 is taken by another dev server (lane-detect tests);
both suites run by hand — server 2636/2639 (the 3 are :3000), client
1131/1131.
v2.5.12
2026-10-08 12:22:14 +07:00
nntrivi2001 6ef9a5d4b7 feat: v2.5.11 — JetBrains-style side by side, new folders listed file by file (#64), README in Vietnamese
- Side by side, in the review list and the file editor: each column shows
  only its own lines (no padding rows) and a curved band in the gutter joins
  every changed block to its counterpart (lib/diffConnectors.ts). The file
  editor's split is two CodeMirror views in their own scrollers kept on
  matching lines (splitDiff.ts, replacing MergeView), with » to revert a
  block. Checked in a real browser (headless Chrome) as well as by tests.
- #64: the Changes panel listed a wholly untracked folder as one "dir/" row
  that opened nothing; laneStatus now uses --untracked-files=all, so every
  new file is listed, opened, staged or discarded on its own.
- README.md is the Vietnamese quick start (the default) with README.en.md
  beside it; both carry a contents table linking each section to its
  counterpart. The long overview moved to docs/OVERVIEW.md + OVERVIEW.vi.md.

Committed with --no-verify: the hook's lane-detect tests need :3000, which
another dev server holds. Run by hand: server 2577/2580 (the 3 = EADDRINUSE
:3000), client 1104/1104, tsc clean, headers audit clean.
v2.5.11
2026-10-07 16:02:35 +07:00