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:
@@ -1142,6 +1142,35 @@ try {
|
||||
db.prepare("ALTER TABLE sessions ADD COLUMN tool TEXT NOT NULL DEFAULT 'claude'").run();
|
||||
}
|
||||
|
||||
// Migrate: add `lane_id` to sessions — the workspace that OWNS a session: the
|
||||
// lane whose dashboard pane held it (stamped by bindRunSession in
|
||||
// routes/hooks.js from the hook's run_id) or one the user added it to. Null =
|
||||
// a session no workspace started (a terminal, an import). No FK: removing a
|
||||
// lane leaves its sessions, which are the user's history. The backfill takes
|
||||
// only what dashboard_runs still records — older history is not guessed.
|
||||
try {
|
||||
db.prepare("SELECT lane_id FROM sessions LIMIT 1").get();
|
||||
} catch {
|
||||
db.prepare("ALTER TABLE sessions ADD COLUMN lane_id INTEGER").run();
|
||||
db.prepare(
|
||||
`UPDATE sessions SET lane_id = (
|
||||
SELECT r.lane_id FROM dashboard_runs r
|
||||
WHERE r.session_id = sessions.id AND r.lane_id IS NOT NULL
|
||||
ORDER BY r.started_at DESC LIMIT 1)
|
||||
WHERE lane_id IS NULL`
|
||||
).run();
|
||||
}
|
||||
db.exec(`CREATE INDEX IF NOT EXISTS idx_sessions_lane ON sessions(lane_id)`);
|
||||
|
||||
// Migrate: add `archived_at` to sessions — a session the user archived out of
|
||||
// its workspace's lists (Claude Desktop's archive). Hides only; nothing about
|
||||
// the session, its transcript or its tokens is deleted.
|
||||
try {
|
||||
db.prepare("SELECT archived_at FROM sessions LIMIT 1").get();
|
||||
} catch {
|
||||
db.prepare("ALTER TABLE sessions ADD COLUMN archived_at TEXT").run();
|
||||
}
|
||||
|
||||
// How far each Codex rollout journal has been read (server/lib/codex-usage.js).
|
||||
// In the DB, not a side file, so a session the user deleted stays deleted
|
||||
// until Codex writes to its journal again, while a wiped DB re-imports all.
|
||||
@@ -1444,6 +1473,8 @@ const stmts = {
|
||||
updateSession: db.prepare(
|
||||
"UPDATE sessions SET name = COALESCE(?, name), status = COALESCE(?, status), ended_at = COALESCE(?, ended_at), metadata = COALESCE(?, metadata), updated_at = strftime('%Y-%m-%dT%H:%M:%fZ', 'now') WHERE id = ?"
|
||||
),
|
||||
// First writer wins: a session a workspace already owns keeps its owner.
|
||||
claimSessionLane: db.prepare("UPDATE sessions SET lane_id = ? WHERE id = ? AND lane_id IS NULL"),
|
||||
reactivateSession: db.prepare(
|
||||
"UPDATE sessions SET status = 'active', ended_at = NULL, updated_at = strftime('%Y-%m-%dT%H:%M:%fZ', 'now') WHERE id = ?"
|
||||
),
|
||||
|
||||
Reference in New Issue
Block a user