Commit Graph

288 Commits

Author SHA1 Message Date
nntrivi2001 600e8eec6a fix(avatars): every signed-in account sees other users' pictures
System Health shows every user's name and avatarAt to everyone, but the
picture route GET /api/users/:id/avatar sat under the staff-only /users
prefix, so a developer got 403 EADMIN and saw only initials. authGuard now
lets any signed-in account make that one read (EVERYONE_READS); every
other /users path stays staff-only, and the test checks both.
2026-11-02 16:40:29 +07:00
nntrivi2001 12ea853170 feat(files): uploads survive a dashboard restart
An upload through the File Explorer is now sent in 8 MB parts that the
server stages on the host (<data dir>/upload-staging/<user>/<id>/,
server/lib/upload-staging.js) and writes only once every byte is there,
through the same upload/uploadBatch code and guards (?staged=<id>). A
restart, an update or a crash pauses it: the browser waits up to 3 min,
asks how much arrived (GET /fs/upload-part) and sends the rest; the dock
says the dashboard is restarting meanwhile. A commit's answer is stored
and replayed, never written twice; a refused one (EEXISTS) keeps the
bytes for a retry with another conflict policy.

A file uploaded into a member's folder is now written to a hidden temp
name and moved onto its name only when whole: write-as cannot tell a
dropped connection from the end of a file, and `replace` left a truncated
file under the real name when the server died mid-upload.

New: GET|POST|DELETE /api/{lanes/:id,homes/:who}/fs/upload-part; 409
EOFFSET/EINCOMPLETE/EBUSY, 429 ETOOMANY. Verified on a sandbox server:
SIGTERM mid-upload of a 60 MB file in a real browser, resumed and
sha256-identical.
2026-11-02 16:12:57 +07:00
nntrivi2001 7e41c81d53 feat(git-identity): import an existing SSH key and sign commits with it
Settings → Git gains Commit signing: one picker signs every commit
(terminal, Changes panel, agents) with one of the account's keys —
gpg.format ssh, user.signingkey = the key's private file, commit.gpgsign
true in its own ~/.gitconfig; a signing key the user set up themselves is
replaced only after a confirm (EFOREIGNSIGN without `replace`). Import a key
you have takes a key file or paste (OpenSSH or PEM, ed25519 or RSA >= 2048)
into ~/.config/agenthub/ssh/ as a named key, written like a generated one
(member: write-as, 0600), and by default signs with it in the same step.
A passphrase key, a .pub, an ecdsa key, a mismatched paste and the same key
twice are refused; only the public half ever comes back. Deleting the
signing key stops signing so commits keep working.

The SSH tab is renamed "Git (GitHub, GitLab, Gitea, Backlog…)" with the
Gitea and Backlog steps, and the File Explorer's ESECRET toast now says
that top-level dot folders of a home are edited in a terminal, and where
the SSH keys live instead.
2026-11-02 16:12:57 +07:00
nntrivi2001 2d6c751fb2 fix(terminal): touchpad scrolls 1:1 with the finger
xterm.js sends one wheel report per event whatever its size and damps a
touchpad's pixel deltas x0.3, so a swipe crawled. With mouse tracking on,
TerminalView now sends the SGR reports itself (lib/wheelReports.ts): one
per 5 rows of travel (what one report scrolls in a Claude pane), the
remainder carried between events, at least one per wheel notch, at the
pointer's cell with xterm's modifier bits.
2026-11-02 16:06:43 +07:00
nntrivi2001 1ad2fec073 fix(preview): say why an outside site shows blank, with Open tab beside it
YouTube, Google and login pages refuse framing and the browser draws a
broken-page icon; the hint lived only in the empty state. A page not on this
machine now gets a strip under the address bar with Open tab. The refused-
address toast says what is allowed in plain words.
2026-11-02 15:10:22 +07:00
nntrivi2001 5d62003fe5 feat(workspace): Preview tab in the session panel; local links open there
The session panel's header becomes two tabs, Changes and Preview. Preview
shows a web page by address (Reload, Open in a browser tab, the workspace's
ports as chips, last address per workspace) in an iframe on the page's own
origin: http(s) only, no userinfo, never the dashboard's origin, no top
navigation. One rule for every link (lib/openInPreview.ts): a chat or
terminal link to localhost/127.0.0.1/0.0.0.0/[::1] opens in Preview while a
session panel answers and the dashboard is on loopback; anything else, or a
Ctrl/Cmd click, opens a browser tab as before.
2026-11-02 15:06:04 +07:00
nntrivi2001 12d53afaeb fix(changes): long branch name truncates; armed Discard matches the row icons
The branch name was shrink-0, so a long one ran over the head sha and the
action icons. It now truncates after the commit subject (full name in the
tooltip). The armed per-file Discard is the trash icon plus a small red
word on a faint tint, sized like its neighbours, instead of a bordered chip.
2026-11-02 14:44:23 +07:00
nntrivi2001 d58f13e1ce fix(chat): conversation column widens to 64rem instead of 48rem
A wide pane left the chat squeezed into a 768px column with empty space
either side, so a wide table was cut off. The message list, the prompt
box and the session replay now share max-w-5xl.
2026-11-02 14:41:56 +07:00
nntrivi2001 0a99eed369 fix(workspace): a new tab or split pane starts in the account's Session defaults
newTabBody sent only prompt/tool/model, so a Claude tab opened from the tab
bar + or a split menu always started in auto mode even with Bypass
permissions set as the default; a Codex one ignored its sandbox, approval
and YOLO defaults. It now sends the same defaults the workspace home's
prompt box starts from.
2026-11-02 13:34:10 +07:00
nntrivi2001 a401a318c5 fix(chat): + menu paints above the controls row under the prompt box
Opened downward, the panel had no z-index, so the AI Proxy / Permission /
Local selects (positioned, later in the DOM) drew over its first rows.
2026-11-02 13:29:08 +07:00
nntrivi2001 01e66ba122 fix(chat): Stop shows while Claude thinks, not only while its footer says "esc to interrupt"
The chat's live line said "Thinking… 21s" while the box offered Send, so the
turn could not be stopped. Stop shows only when a hook marked main "working"
or the pane's busy line matched, and Claude Code 2.1.3xx writes
"esc to interrupt" only in its footer, which a narrow pane, a tip or a notice
replaces. Claude's busy line now also matches its spinner (verb… (41s · …)),
which the line a finished turn leaves ("Cooked for 1m 2s") does not; and the
chat re-reads "working" on transcript_changed, so a hook that reached another
dashboard on the same database no longer leaves Stop hidden.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-02 13:29:05 +07:00
nntrivi2001 c37bc5205b fix(chat): + menu opens downward when the prompt box sits too near the top
On the workspace home the box is high on the screen, so the menu and its
submenus (Project files) opened upward and were clipped off the top.
Measure the room above/below (viewport narrowed by clipping ancestors) at
open and drop the menu below the button, submenus top-aligned, when it fits better.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-02 13:25:05 +07:00
nntrivi2001 e1f433804c feat(git): history graph with search, commit details, file history, blame/browse at a revision, compare refs, and multi-commit cherry-pick/revert with preview, pause banner and undo 2026-11-02 12:22:46 +07:00
nntrivi2001 2b5d7268ed feat(git): 3-way merge editor, reflog, rebase/interactive rebase, reset with undo, force-with-lease push, remote branches and remotes
Server: git-rewrite.js (rebase + continue/skip/abort, rebase-interactive with
no editor, reset soft/mixed/keep/hard, undo-rewrite, force-push with lease,
remote-branch-delete, remote add/rename/remove) and git-conflicts.js
(conflict stages read + conflict-save, reflog), dispatched from runGitAction;
operationInProgress now reports a rebase and only ALLOWED_WHILE_REBASING
writes run during one. New reads: /git/conflict, /git/reflog, /git/remotes,
/git/rewrite-plan. Client: History tools menu, MergeEditor, ReflogView,
RebaseDialog, InteractiveRebase, ResetDialog, ForcePushDialog, RemotesDialog,
rebase banner Skip. CLAUDE.md git-writes rule rewritten for the new actions.
2026-11-02 12:12:45 +07:00
nntrivi2001 2cc492bb88 feat(git): Branches & tags panel — rename, branch from any ref, upstream, tags
GET /git/refs lists local branches (upstream, ahead/behind, last commit,
merged, held by a worktree, protected), remote branches grouped by remote
and tags (target, annotated message/tagger/date). New writes in
server/lib/git-refs.js, dispatched from git-ops.js: branch-rename,
branch-upstream (set/unset), tag-create (light/annotated), tag-delete
(confirm), tag-push (one tag to origin, no --tags, no force), and
branch-create with a looked-up from + switch on a clean tree. Every ref is
looked up, never passed through; repo-wide writes are repo-root only
(ESUBDIR) and refused during any operation (EMERGING).

Client: git/BranchManager + TagManager dialog opened from the git bar's
tags icon, en+vi (gitRefs namespace), keyboard navigable.

# Conflicts:
#	CLAUDE.md
#	client/src/components/lanes/LaneGitBar.tsx
#	docs/API.md
#	server/__tests__/lanes-api.test.js
#	server/routes/lanes.js
2026-11-02 12:00:55 +07:00
nntrivi2001 f6eb39eb73 feat(git): switch with uncommitted changes (carry / shelve), Create & switch, and shelves with Restore/Delete
- switch takes changes: carry (plain git switch, 409 ECARRYCONFLICT with files) or shelve (shelf first, re-applied if the switch fails)
- branch-create switch: true runs git switch -c, the changes come along
- shelve / unshelve / shelf-drop + GET /git/shelves: only agenthub-shelf: stash entries, addressed by sha, repo root only, never mid-operation; a clean restore drops the shelf, a conflicted one keeps it
- 409 EAGENTRUNNING until confirm: true while the workspace's agent is working
- git bar: Bring my changes / Shelve and switch choice, Create & switch, restore offer after a switch; Changes panel: Shelve (n) and the Shelves list
- fix: after a switch the Changes panel showed the old branch — the post-write re-read joined an in-flight GET started before the write (request() dedupe); writes now close in-flight GETs, and every git write fires agenthub:git-changed so every view of the workspace re-reads
2026-11-02 11:44:35 +07:00
nntrivi2001 2b26fe1efa fix(jobs): a failed clone's chip is readable and clickable
A failed or stopped clone also raised a toast on the same corner, a layer
above the dock, so it covered the chip's x / Retry / Stop and repeated the
same long message; the chip now says it alone, and every toast stacks above
the dock (--jobs-dock-h). A URL with no space to break at (user@host:/PATH)
wraps inside the chip and inside a toast instead of running off the screen.
Open Settings -> Git is a router link; a plain <a href> reloaded the app.
2026-11-02 11:26:48 +07:00
nntrivi2001 57cbab916a feat(clone): background clones and uploads in one global dock that survives F5 and restarts
Clone repository (Files), Add workspace -> Clone and a workspace's extra-folder
clones now start server-side jobs (background: true) and close at once; the
app-wide BackgroundJobsDock (Layout) shows each job's queue place, git progress,
Stop (click twice), and outcome: Open workspace, Retry, an inline login form on
ECREDENTIALS, a Settings -> Git link on ESSHAUTH, dismiss. lib/cloneJobs.ts
polls GET /workspaces/clone/jobs while any job is active, announces a finish it
saw once (toast + agenthub:clone-job event + the starter's onDone), and treats
an older server's synchronous answer as a finished clone. File Explorer uploads
report to lib/uploadJobs.ts (Stop aborts the request in flight; a reload-cut
upload shows as interrupted from localStorage). CloneProgress renders a job's
fields instead of polling a token.
2026-11-02 10:44:23 +07:00
nntrivi2001 a7a8bfd24c fix(changes): Discard (n) dialog shows its count and does not count a new file twice
The confirm button was translated without count, so it read Discard ({{count}}).
The message counted ticked untracked files both as changed files and as new
files to delete; tracked and untracked are now counted apart, with a wording
for each mix.
2026-11-02 10:34:34 +07:00
nntrivi2001 0c964909e0 feat(git): a CodeCommit SSH key ID is saved for every region; a clone tries each saved account
Settings → Git → AWS CodeCommit asks for the key ID only: the default key's
login is written for every CodeCommit region at once (allRegions), shown as
one row, with Use in every region for an older one-region save. A clone of
a CodeCommit URL with no user tries every saved CodeCommit login — the
region's own first, then other regions' and each named key's — so a second
AWS account clones from the plain AWS URL with no ssh://<alias>/ rewrite.
2026-11-02 10:32:20 +07:00
nntrivi2001 220309db12 fix(files): a workspace whose folder was deleted leaves the Files places
The lane payload carries folder_missing (ENOENT/ENOTDIR only). Files hides
such a workspace and never reopens it as the default place; the Workspace
sidebar strikes its name through with a hint to remove it from its menu.
A folder that cannot be read no longer keeps the previous folder's entries
under its error, and a change in Files re-reads the workspace list.
2026-11-02 10:11:47 +07:00
nntrivi2001 e4ecd116a2 fix(clone): a CodeCommit region borrows saved key IDs; Clone runs in the background
An IAM SSH key ID logs in to every region of its AWS account, but a login
was saved per host, so a repo in a region with no key ID of its own was
refused. Clone now tries the key IDs saved for the other CodeCommit regions
(each with its own key; the first ls-remote accepts, or the only one) and
keeps that user in the clone's origin.

Files → New → Clone repository steps aside for its progress chip as soon
as the clone starts; a failure asking for a login reopens the form.
2026-11-02 10:00:09 +07:00
nntrivi2001 e7e9cb20d8 fix(files): a folder upload leaves .git out with one notice
Uploading a cloned repo from the desktop sent every .git object, and the
server refused each one (nothing is uploaded into .git) — one error toast
per object, thousands of them. The drop walker no longer reads a .git
folder, and uploadAll drops any path inside .git before sending, saying
once how many were skipped.
2026-11-02 09:53:03 +07:00
nntrivi2001 da7b86d5c8 fix(changes): Discard takes only the ticked files and blocks (#87)
The Changes panel's "Discard all" threw away every changed file, ticked or
not. It is now "Discard (n)": the same selection Commit sends — whole ticked
files as `paths`, a partly ticked file's ticked blocks as `hunks` — and is
disabled with nothing ticked. The server's `discard` accepts `hunks`
(`selectionPatch` in reverse, `git apply -R`), building every patch before
applying any so a stale selection undoes nothing.

Closes #87
2026-10-30 13:05:13 +07:00
nntrivi2001 ee119fe437 fix(terminal): restamp every glyph-atlas page so no pane keeps a stale texture
v2.8.42 reacted to a page merge by toggling the font family on the next
frame, so each renderer re-acquired an atlas. Panes still showed squeezed,
overlapping glyphs: the toggle lands a frame late, swaps every pane onto a
fresh atlas (or onto one another pane already toggled to), and a page added
without a merge shifts nothing yet still raced it.

addon-webgl 0.20 fixes the root cause with globally monotonic page versions.
Do the same on 0.19: whenever the shared atlas adds, removes or merges a page,
give every page a version no texture slot has uploaded, synchronously, so
every renderer on that atlas re-uploads all pages before it draws again.
When the page comes back from the background, restamp and repaint. An addon
of another shape keeps the old font-toggle fallback.
2026-10-30 10:33:57 +07:00
nntrivi2001 d97c403aab fix(changes): a huge line no longer cuts the diff short; preview pages up to 64 MB
A page that inlines a video as base64 has rows of megabytes. Its patch was read
at the 200 KB per-file cap, so the first such row used the whole budget and
every hunk after it was lost ("Diff cut short"). The patch is now read up to
16 M characters, rows over 4,000 characters are cut to 1,000 with an `elided`
count the diff shows, and only then is the cap applied. Hunk actions re-read
git's own diff by header and are unchanged.

The HTML preview refused a page over 10 MB and showed only the file icon; a
self-contained 18 MB slide deck now opens (limit 64 MB).
2026-10-30 10:28:53 +07:00
nntrivi2001 c850f7603a fix(terminal): re-upload the shared WebGL atlas after a page merge
@xterm/addon-webgl 0.19 shares one glyph atlas between every terminal
with the same font. When it fills, four pages are merged into a larger
one and the rest shift down; a GPU texture is re-uploaded only when its
page version differs, and those counters can coincide, so a slot keeps a
stale page of another size and every glyph is drawn squeezed or foreign
(fixed upstream only in the 0.20 betas). On a merge — and when the page
comes back — toggle the font family's trailing space on the next frame,
which makes each renderer re-acquire its atlas and re-upload all pages.

This also drops yesterday's clearTextureAtlas on focus: it emptied the
SHARED atlas but redrew only the calling pane, corrupting its siblings.
2026-10-30 10:09:28 +07:00
nntrivi2001 783b7f4101 fix(cost): bill a session outside every workspace to its Linux account's user
A script's claude -p in a temp folder (the SG Flow eval runner strips the
pane's env) had no workspace, so its spend reached nobody's Today, the Users
page or a spend quota — ~$57 of a $74 day was missing from the status bar.

sessions.account records the Linux account a session ran as (the hook's
peer uid, else its folder for poller-made and old rows); a session no
workspace owns is owned by that account's user — a member's to them, the
host's to the admin ticked 'Owns this server's host account' (or the only
admin). Workspace ownership still wins.
2026-10-30 08:50:34 +07:00
nntrivi2001 7798530528 fix(profiles): a Codex switch reaches running and new sessions
Codex >= 0.164 sends every request through a shared app-server daemon that
outlives its TUIs and keeps the login it started with, so writing a new
auth.json changed nothing: open sessions and even new ones stayed on the
old (out-of-quota) account. After a switch, restart that CODEX_HOME's
daemon when one runs; open TUIs reconnect to it on their own (verified on
0.164.0 with two keys). The response says so (following), and the toast
tells the user running sessions follow.
2026-10-29 15:54:20 +07:00
nntrivi2001 25f63d9ad3 fix(terminal): rebuild the WebGL glyph atlas when the page comes back
A browser can drop the atlas's GPU memory without a context-loss event
(sleep/resume, a long-backgrounded tab, display/zoom change on macOS),
leaving every cell painted with a clipped or foreign glyph.
2026-10-29 15:44:16 +07:00
nntrivi2001 6099841cef fix(sessions): archiving a live session stops its pane
Archive on a session a live pane held only hid its row, and the list
deliberately does not bring such a tab back, so the pane kept running
with nothing on screen showing it — a member archived five sessions
believing them closed and kept ~2.7 GB of claude/omp/postgres-mcp alive
for up to two days. Archive on a live session is now a Stop (endRuns,
the tab X's confirm, DELETE /api/run/:id?archive=1) in the sidebar row,
the workspace home's Recents and their right-click menu.
2026-10-29 14:26:25 +07:00
nntrivi2001 f1a90e120e fix(users): every user row shows its avatar, not only its initials
System Health's per-user CPU/RAM card asked UserAvatar for the viewer's own
picture with no user id, and the host stats never carried avatarAt, so every
card fell back to letters. The groups now carry avatarAt and the card passes
userId. The user editor's header, the pool board's member chips and the pool
member picker drew hand-made initials; they use UserAvatar too.
2026-10-29 13:40:28 +07:00
nntrivi2001 97c39d5e15 feat(files): an HTML page opens in a modal over the screen
- Changes panel: Preview beside Whole file opens the page in a modal
  (HtmlViewerModal, shared with the File Explorer) instead of inside the
  panel; Changes and Whole file stay as they were.
- Chat: an HTML file's card, Outputs row or a path in a reply (in the
  workspace or not) opens FilePreview as a modal — Preview | Code, versions,
  Save to Artifacts kept — rather than the side panel or the Changes panel.
- Esc closes both modals: they stop every key from reaching the view below,
  which also kept useDialog's document listener from seeing it.
2026-10-29 11:20:54 +07:00
nntrivi2001 80000793f4 feat(files): an HTML file's Preview sits beside Whole file
The Changes panel's view toggle becomes Changes | Whole file | Preview for an
HTML file; the Preview/Raw button on the file's name row stays for markdown.
2026-10-29 10:47:39 +07:00
nntrivi2001 364fe4a286 fix(files): an HTML file in the Changes panel shows as the page only on Preview
Changes stays the default view and Whole file shows the source, as for any
file. A Preview button — offered on the Changes diff too, where it was
missing — opens the page; Raw, Changes or Whole file go back to the source.
2026-10-29 10:38:15 +07:00
nntrivi2001 7b5abc6ad9 feat(files): an HTML file opens as the page — Files panel, Chat preview, File Explorer
Clicking an .html/.htm file now renders it instead of showing its source or
downloading it:
- Changes/Files panel: a whole HTML file opens on Preview (Raw = the editor).
- Chat file preview: a workspace page brings its relative files along.
- File Explorer: double-click / Enter opens a viewer; Download stays on its
  header and right-click menu.

The page runs in the chat artifact's sandbox (iframe srcdoc, scripts, never
allow-same-origin). Its relative stylesheets (and their url()s), scripts,
images, media and icons are read by the dashboard through the existing
download routes and inlined as <style>/data: URLs (lib/htmlInline.ts: never
above the folder's root, ≤ 60 files / 30 MB) — the login cookie is
SameSite=Strict, so a frame could not fetch them itself, and no new route
serves files without it.
2026-10-28 16:46:07 +07:00
nntrivi2001 14e979837e fix(git): the branch switcher lists and checks out branches only the remote has
GET /api/lanes/:id/git/branches read only refs/heads, so a branch someone
else pushed never showed after a fetch or pull, and `switch` refused it
(ENOBRANCH). It now also returns `remotes`: fetched remote-tracking branches
with no local branch of that name (origin/HEAD left out), listed under
"On the remote"; switching to one runs `git switch -c <b> --track
refs/remotes/<remote>/<b>` with the remote ref looked up, never passed
through (bare name only when exactly one remote has it, else EAMBIGUOUS).
The bar also re-reads the branch list after a fetch or pull instead of
keeping the list it loaded first.
2026-10-28 14:57:32 +07:00
nntrivi2001 fa27b299e7 feat(server-mode): spend quota per user — no new session, prompts refused once used up
An admin sets a default spend quota on the Users page and overrides it per
user (Edit user → Spend quota: default / no limit / their own), in dollars
per local day or per sliding 5 hours. Spend is the users page's own
hourlyTokenRows + calculateCost over the sessions a user owns.

Over the quota, until it opens again:
- spawnRun refuses every start/resume/home prompt with EQUOTA (429);
- a minute sweep writes each pane's AGENTHUB_QUOTA_FILE (host: data dir,
  member: handoff dir via write-as) and hook-handler.js exits 2 on
  UserPromptSubmit from that file alone (Claude Code, Codex with trusted
  hooks) — fail-open, never asks the dashboard, never for Antigravity;
- a member's omp on the shared login loses its gateway key (clientsFor);
- panes no hook can stop (Antigravity, Cursor, Codex without trusted
  hooks, omp on its own login) are ended.

GET /api/me/quota feeds a "Quota N%" chip in the status bar.
New: server/lib/user-quota.js, user_quotas table, PUT /api/users/quota-default,
PATCH /api/users/:id {quota}, docs in SERVER-MODE.md § Spend quotas.
2026-10-28 14:31:11 +07:00
nntrivi2001 26aabd6660 feat(pool): one board of pools with split/merge by drag; plan names from tier and seat
- The Users page account pool is one board: a column per pool holding its
  Claude Code, Codex and Cursor accounts (plan, quota, live panes) and its
  members. Accounts sit in one pool; drag moves them. Drop on New pool to
  split one off; drag a pool's only account into another pool to merge
  (members follow, the emptied pool is deleted on Save). One password Save.
- Claude plan label (planLabel in cc-profiles.js) reads rateLimitTier and
  seatTier: Max 5x/20x, Team Standard/Premium — subscriptionType alone said
  'pro' after an upgrade and 'team' for both seats. Shown on profiles, the
  status bar quota badge and pool cards; Codex pool cards show the ChatGPT plan.
2026-10-28 11:47:19 +07:00
nntrivi2001 0f17efe3b4 feat: choose your own pool account; skills with "token" in a doc name upload; System Health off Linux
- Skill snapshot/upload: a key file's name (.env, *.pem, id_rsa, .netrc,
  .credentials.json…) is always refused, but a word like token/secret/
  password/credential only in a data file's name — design-tokens.md or
  count_tokens.py is skill content, not a key.
- System Health: off Linux the History, CPU threads and Busiest processes
  cards (all read from /proc) are left out instead of shown empty; the CPU
  bar without a sample and Windows' always-zero Load row go too.
- Users pick their own pool account per tool (/api/me/pool-accounts,
  Settings → AI tools), within their groups, refused while pinned.
- Admin user detail, 9router update, session replay and Antigravity
  catalog changes from parallel sessions.
2026-10-28 10:41:53 +07:00
nntrivi2001 1c6dfea5b5 feat(health): System Health in three groups, a score that sees lag; admin user detail
Dashboard → System Health is grouped into AgentHub, Machine and Agent
activity. The machine (GET /api/host, every signed-in user) is drawn by
components/health/HostHealth.tsx: host card beside the CPU/memory history,
then CPU (threads as compact cells + temperatures/GPUs), disks & network,
busiest processes, and per-user tiles. Concurrency bars each show their own
agent type on hover.

The Health Score (client/src/lib/healthScore.ts) is now 100 minus what each
check costs, between an amber and a red threshold: event-loop p99
(server/lib/loop-delay.js, shared with /api/metrics), Linux PSI pressure for
CPU, memory and disk I/O (/api/host `pressure`; load per core without PSI),
RAM, the database's disk, V8 heap, hooks silent while agents work, and agent
errors in the last 24 h. All-time success rate, API errors and the transcript
cache hit rate no longer count. /api/settings/info adds server.event_loop and
activity {last_event_at, working_agents, agents_24h}.

Administration: a user row opens its detail in place (GET
/api/users/:id/details — spend, account pool share, live panes, workspaces,
newest sessions); the server panel is gone, member tmux folded into the table;
the Sessions list shows each row's owner in server mode.
2026-10-27 16:39:38 +07:00
nntrivi2001 569b68ec98 feat(ai-proxy): Update 9router button — shut down, npm update, relaunch hidden
Settings → AI Proxy gains an admin-only Update 9router button beside
Config 9router. One in-memory job (server/lib/nine-router-update.js) stops
9router (its user unit, else this account's own 9router processes), runs
npm install -g --prefix <its prefix> 9router@latest (sudo -n only when
sudoers already allows it, probed before anything stops), then relaunches
it hidden only if it was running. A failed update still relaunches the old
version. Nothing in the request names a command, path or package.
2026-10-26 17:00:53 +07:00
nntrivi2001 a0cd808456 fix(terminal): a pane on a hidden tab never shrinks its tmux window
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.
2026-10-26 14:53:29 +07:00
nntrivi2001 8546748759 chore: maintenance — dependency majors, rename step 4, member Claude config, OpenAPI coverage
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.
2026-10-26 14:22:41 +07:00
nntrivi2001 cc2ece94c3 fix(ui): drop the git clone hints from Git identity and the background toast on closing the update dialog 2026-10-26 14:22:18 +07:00
nntrivi2001 f2f1cd9b6c fix: per-user Today, native paste and drop on a local machine
- 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.
2026-10-26 11:18:05 +07:00
nntrivi2001 127da516ac chore(quota): drop the Kimi Code quota reader (Kimi already left the catalog)
Redone on v2.8.22 from 3179d25 (feat/grok-usage, never merged).
2026-10-26 09:31:37 +07:00
nntrivi2001 c0832182f2 feat(ai-tools): drop OpenCode and Grok Build
- 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).
2026-10-26 09:31:30 +07:00
nntrivi2001 f51a6a895e fix(chat): keep a long turn's Edited N files card whole across the page cut (#86)
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.
2026-10-26 08:32:45 +07:00
nntrivi2001 5ccbaf5c23 fix(chat): count a subagent's edits in the turn's Edited N files card (#86)
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).
2026-10-26 08:32:45 +07:00