feat: workspaces own their sessions; desktop parity; sidebar and settings cleanup

Workspace sessions (docs/plans/desktop-parity, phases 1-5 and 7):
- sessions.lane_id: a workspace lists only the sessions its panes held,
  stamped by bindRunSession (first writer wins, nested `claude -p`
  excluded); GET /api/sessions?lane=&archived=
- session row state dot, double-click rename, archive (sessions.archived_at,
  hides only); the activity bar's duplicate Sessions tab removed
- Fork beside Resume (--fork-session, Claude only), Ctrl/Cmd+click a live
  session opens it in a second pane, a diff line's comment is pasted into
  the workspace's last-focused pane (lib/panePaste.ts)

dashboard_runs stability:
- a pane's previous run is archived as <pane>@<started_at> instead of being
  overwritten by INSERT OR REPLACE
- a run is flagged abandoned only once its tmux session is gone, at boot and
  on the run-heal beat (panes outlive a server restart)

UI:
- sidebar: no footer, language or theme block; small collapse button in the
  brand corner; version + update check in the connection details modal, a
  status-bar dot while a release is out; language in Settings -> General
- one Switch component for every switch, the themed Checkbox for every
  checkbox (no native checkboxes)

Also: AI Tools catalog trimmed to 10 built-ins (custom-file prompt modes
tested through fixtures), and the workspace plans under docs/plans/ (the
multi-user server plan lives in the AgentHub-Server repo).
This commit is contained in:
2026-10-02 12:05:00 +07:00
parent 5b269698f6
commit a6bd33bcff
121 changed files with 2699 additions and 1552 deletions
+12 -7
View File
@@ -539,14 +539,18 @@ function startBackgroundServices() {
}
}
// A codex/antigravity pane that exits fires no hook, so release its lane on
// a beat instead (one `tmux has-session` per lane with a run). Off with
// DASHBOARD_RUN_HEAL_MS=0.
// a beat instead (one `tmux has-session` per lane with a run), and end the
// run record of any pane that is gone. Off with DASHBOARD_RUN_HEAL_MS=0.
const HEAL_MS = Number(process.env.DASHBOARD_RUN_HEAL_MS ?? 10_000);
if (Number.isFinite(HEAL_MS) && HEAL_MS > 0) {
const { healAllRunIds } = require("./routes/lanes");
const { reconcileOrphans } = require("./lib/dashboard-runs");
const { hasSession } = require("./lib/tmux");
const healTimer = setInterval(() => {
try {
healAllRunIds();
// A pane that quit (Ctrl+C, `exit`) ends its run record too.
reconcileOrphans(hasSession);
} catch (err) {
console.warn("run heal failed:", err.message);
}
@@ -587,13 +591,14 @@ function startBackgroundServices() {
} catch (err) {
console.warn("remote source sync failed to start:", err.message);
}
// Flip any dashboard_runs rows the previous process left flagged
// running/spawning — those handles died with the previous server, so
// there's no way to attach to them anymore. Marking them abandoned
// keeps the Run history honest and unblocks Resume on conversation rows.
// Flip the dashboard_runs rows still flagged running/spawning whose tmux
// pane is gone (a reboot, a pane that quit while no server was up). A pane
// that is still there survived the restart and stays running — tmux, not
// this process, owns it.
try {
const { reconcileOrphans } = require("./lib/dashboard-runs");
const reconciled = reconcileOrphans();
const { hasSession } = require("./lib/tmux");
const reconciled = reconcileOrphans(hasSession);
if (reconciled > 0) {
console.log(`[runs] reconciled ${reconciled} orphan run(s) → abandoned`);
}