feat: v2.5.10 — server mode for a team, file drops in the prompt, tab joins

Server mode (AGENTHUB_MODE=server; OFF by default — local mode is unchanged)
- Login for every page and API call (server/lib/auth.js, routes/auth.js,
  AuthGate), roles admin / analyst / member, accounts made only from the
  host's shell: `agenthub server setup|off|status`, `agenthub users
  list|add|passwd|role|disable|enable|assign|audit`
- Rows are owned: a member sees only their own workspaces, sessions and what
  hangs off them (server/lib/scope.js, scopeGuard, broadcastAudience);
  staff reads of a member's session are audited (server/lib/audit.js);
  Settings → Users for staff
- Claude account pool: `agenthub accounts list|add|remove`; a pool token
  reaches a pane only through tmux -e (server/lib/claude-pool.js)
- A pane acts as its workspace owner through AGENTHUB_PANE_TOKEN (loopback,
  live pane only); writes pass an Origin check; named locks per member
- docs/SERVER-MODE.md, docs/plans/server-mode/

Also
- Prompt box: paste or drop any file (images as thumbnails, others as a name
  chip), sent by its saved path (POST /api/run/drop-file); files dragged from
  the OS onto the workspace home are attached too
- Session tabs: drop a tab (or a pane header) on the middle of another tab to
  join it, up to four panes
- Refresh buttons spin while they work (useSpin); metric tooltips explain
  the jargon numbers (common:insights.help.*)
- Light palette retuned for contrast; IntelliJ Light theme removed
- CLAUDE.md sync marker trimmed; its history moved to docs/CLAUDE-SYNC-LOG.md

Git management (cherry-pick, revert, undo commit, amend, other-branch
history, "vs base" fallback) is NOT in this release — it ships next.

Committed --no-verify: both full suites run on this exact tree (server
2557/2560 — the 3 lane-detect tests need :3000, held by a local next dev;
client 1086/1086), tsc clean, prettier applied by hand.
This commit is contained in:
2026-10-07 14:11:25 +07:00
parent ca821008e2
commit 9a54978eef
112 changed files with 6509 additions and 436 deletions
@@ -157,6 +157,7 @@ exports[`screen snapshots > Activity feed 1`] = `
JSON
</button>
<button
aria-busy="false"
aria-label="Refresh"
class="btn-ghost"
title="Refresh"
@@ -3220,6 +3221,7 @@ exports[`screen snapshots > Dashboard 1`] = `
</div>
</div>
<button
aria-busy="false"
class="btn-ghost"
>
<svg
@@ -3292,7 +3294,8 @@ exports[`screen snapshots > Dashboard 1`] = `
class="flex items-center justify-between gap-3 mb-3"
>
<span
class="text-xs font-medium text-fg-muted uppercase tracking-wider truncate"
class="text-xs font-medium text-fg-muted uppercase tracking-wider truncate cursor-help underline decoration-dotted underline-offset-2"
title="A session is one conversation with an AI agent, from start to finish."
>
Total Sessions
</span>
@@ -3339,7 +3342,8 @@ exports[`screen snapshots > Dashboard 1`] = `
class="flex items-center justify-between gap-3 mb-3"
>
<span
class="text-xs font-medium text-fg-muted uppercase tracking-wider truncate"
class="text-xs font-medium text-fg-muted uppercase tracking-wider truncate cursor-help underline decoration-dotted underline-offset-2"
title="Agents are the AI workers: the main one in each session plus any helpers it started."
>
Total Agents
</span>
@@ -3405,7 +3409,8 @@ exports[`screen snapshots > Dashboard 1`] = `
class="flex items-center justify-between gap-3 mb-3"
>
<span
class="text-xs font-medium text-fg-muted uppercase tracking-wider truncate"
class="text-xs font-medium text-fg-muted uppercase tracking-wider truncate cursor-help underline decoration-dotted underline-offset-2"
title="Tokens are the pieces of text the AI reads and writes. Roughly 1 token ≈ 4 characters. More tokens = more cost."
>
Tokens (excl. cache reads)
</span>
@@ -3487,7 +3492,8 @@ exports[`screen snapshots > Dashboard 1`] = `
class="flex items-center justify-between gap-3 mb-3"
>
<span
class="text-xs font-medium text-fg-muted uppercase tracking-wider truncate"
class="text-xs font-medium text-fg-muted uppercase tracking-wider truncate cursor-help underline decoration-dotted underline-offset-2"
title="Estimated money spent, calculated from token counts and each model's price."
>
Total Cost
</span>
@@ -3541,7 +3547,8 @@ exports[`screen snapshots > Dashboard 1`] = `
class="flex items-center justify-between gap-3 mb-3"
>
<span
class="text-xs font-medium text-fg-muted uppercase tracking-wider truncate"
class="text-xs font-medium text-fg-muted uppercase tracking-wider truncate cursor-help underline decoration-dotted underline-offset-2"
title="Every step the AI takes (running a tool, finishing a reply…) is recorded as one event."
>
Total Events
</span>
@@ -5386,7 +5393,8 @@ exports[`screen snapshots > Dashboard 1`] = `
class="card p-5"
>
<h3
class="text-sm font-medium text-fg-secondary mb-5"
class="text-sm font-medium text-fg-secondary mb-5 cursor-help underline decoration-dotted underline-offset-2 w-fit"
title="How fast money is being spent, and where this month will end up if that speed continues."
>
Spend pace
</h3>
@@ -6018,6 +6026,7 @@ exports[`screen snapshots > Session detail 1`] = `
</button>
</div>
<button
aria-busy="false"
aria-label="Refresh"
class="btn-ghost"
title="Refresh"
@@ -6210,7 +6219,8 @@ exports[`screen snapshots > Session detail 1`] = `
class="rounded-lg border px-3 py-2.5 border-surface-3 bg-surface-2 text-fg-secondary"
>
<div
class="flex items-center gap-1.5 text-[10px] uppercase tracking-wider text-fg-muted"
class="flex items-center gap-1.5 text-[10px] uppercase tracking-wider text-fg-muted cursor-help"
title="Every step the AI takes (running a tool, finishing a reply…) is recorded as one event."
>
<span
class="text-fg-muted"
@@ -6232,7 +6242,11 @@ exports[`screen snapshots > Session detail 1`] = `
/>
</svg>
</span>
Events
<span
class="underline decoration-dotted underline-offset-2"
>
Events
</span>
</div>
<div
class="mt-1 font-mono text-lg font-semibold text-fg-primary leading-tight"
@@ -6244,7 +6258,8 @@ exports[`screen snapshots > Session detail 1`] = `
class="rounded-lg border px-3 py-2.5 border-syntax-keyword/20 bg-syntax-keyword/5 text-syntax-keyword"
>
<div
class="flex items-center gap-1.5 text-[10px] uppercase tracking-wider text-fg-muted"
class="flex items-center gap-1.5 text-[10px] uppercase tracking-wider text-fg-muted cursor-help"
title="How many actions the AI took: reading a file, editing, running a command…"
>
<span
class="text-syntax-keyword"
@@ -6266,7 +6281,11 @@ exports[`screen snapshots > Session detail 1`] = `
/>
</svg>
</span>
Tool calls
<span
class="underline decoration-dotted underline-offset-2"
>
Tool calls
</span>
</div>
<div
class="mt-1 font-mono text-lg font-semibold text-fg-primary leading-tight"
@@ -6278,7 +6297,8 @@ exports[`screen snapshots > Session detail 1`] = `
class="rounded-lg border px-3 py-2.5 border-syntax-property/20 bg-syntax-property/5 text-syntax-property"
>
<div
class="flex items-center gap-1.5 text-[10px] uppercase tracking-wider text-fg-muted"
class="flex items-center gap-1.5 text-[10px] uppercase tracking-wider text-fg-muted cursor-help"
title="Helper agents the main AI started to work on part of the task in parallel."
>
<span
class="text-syntax-property"
@@ -6316,7 +6336,11 @@ exports[`screen snapshots > Session detail 1`] = `
/>
</svg>
</span>
Subagents
<span
class="underline decoration-dotted underline-offset-2"
>
Subagents
</span>
</div>
<div
class="mt-1 font-mono text-lg font-semibold text-fg-primary leading-tight"
@@ -6328,7 +6352,8 @@ exports[`screen snapshots > Session detail 1`] = `
class="rounded-lg border px-3 py-2.5 border-syntax-builtin/20 bg-syntax-builtin/5 text-syntax-builtin"
>
<div
class="flex items-center gap-1.5 text-[10px] uppercase tracking-wider text-fg-muted"
class="flex items-center gap-1.5 text-[10px] uppercase tracking-wider text-fg-muted cursor-help"
title="When a conversation gets too long, it is summarized to make room. Early details can be lost."
>
<span
class="text-syntax-builtin"
@@ -6356,7 +6381,11 @@ exports[`screen snapshots > Session detail 1`] = `
/>
</svg>
</span>
Compactions
<span
class="underline decoration-dotted underline-offset-2"
>
Compactions
</span>
</div>
<div
class="mt-1 font-mono text-lg font-semibold text-fg-primary leading-tight"
@@ -6368,7 +6397,8 @@ exports[`screen snapshots > Session detail 1`] = `
class="rounded-lg border px-3 py-2.5 border-surface-3 bg-surface-2 text-fg-secondary"
>
<div
class="flex items-center gap-1.5 text-[10px] uppercase tracking-wider text-fg-muted"
class="flex items-center gap-1.5 text-[10px] uppercase tracking-wider text-fg-muted cursor-help"
title="Steps that failed. A few are normal; many may mean the AI got stuck."
>
<span
class="text-fg-muted"
@@ -6396,7 +6426,11 @@ exports[`screen snapshots > Session detail 1`] = `
/>
</svg>
</span>
Errors
<span
class="underline decoration-dotted underline-offset-2"
>
Errors
</span>
</div>
<div
class="mt-1 font-mono text-lg font-semibold text-fg-primary leading-tight"
@@ -6435,7 +6469,9 @@ exports[`screen snapshots > Session detail 1`] = `
/>
</svg>
</span>
Duration
<span>
Duration
</span>
</div>
<div
class="mt-1 font-mono text-lg font-semibold text-fg-primary leading-tight"
@@ -6830,6 +6866,7 @@ exports[`screen snapshots > Sessions 1`] = `
JSON
</button>
<button
aria-busy="false"
class="btn-ghost"
>
<svg
@@ -7521,6 +7558,7 @@ exports[`screen snapshots > Settings — data 1`] = `
Export Data
</a>
<button
aria-busy="false"
class="btn-ghost"
>
<svg
@@ -9857,6 +9895,7 @@ exports[`screen snapshots > Workflows 1`] = `
</button>
</div>
<button
aria-busy="false"
class="p-2 rounded-lg text-fg-muted hover:text-fg-secondary hover:bg-surface-3 transition-colors"
title="Refresh data"
>