refactor(sessions): unify workspace shell under sessions table #20

Merged
lz merged 1 commit from refactor/workspace-shell-unification into main 2026-05-24 13:48:32 +02:00
Owner

Per spec at docs/superpowers/specs/2026-05-24-workspace-shell-unification.md.

Summary

  • New sessions.kind column ('session' | 'workspace_shell', migration 0006_workspace_shell_session). The workspace shell now occupies a real sessions row instead of being state-probed from /proc.
  • Open/close routed through POST/DELETE /api/workers/:id/sessions/workspace; the previous workspace-session endpoint is gone.
  • createSession reserves the name workspace; the generic POST rejects it with a clear "reserved" error.
  • checkSessionHealth dispatches on kind: regular sessions match the full --remote-control "<workspace> · <name>" argv; the workspace shell matches by cwd=/workspace (its argument is just <label>, a substring of every regular session's).
  • WorkerHealthDTO.workspaceShellRunning removed — UI now derives the toggle state from the workspace_shell row's presence via listSessions.
  • ExplorerScope is now { kind: 'workspace' | 'session', ... } | null instead of a three-state union, and lives on the workspace card click (workspace scope) or session row click (session scope).

UI shape

The spec proposed showing the workspace shell as a pinned row in the sessions list. Reverted at user's request — the workspace card keeps its original start agent / stop agent toggle plus a term button that opens a terminal on the boot pane whether claude is running there or not (the workspace terminal endpoint is preserved for that reason).

Reviewed edge cases

  • Double-click on "start agent": INSERT race resolved by re-SELECT + alreadyOpen=true instead of surfacing the raw SQLite UNIQUE error.
  • Close-when-container-is-gone: tmux cleanup is now best-effort so the row still drops cleanly, leaving no orphan.
  • Reconcile: boot workspace window still filtered from the orphan-warning list; a workspace_shell row whose pane the operator kills externally is reaped after 2 strikes.
  • Explorer is cleared when its scoped session (or worker) is removed, so the panel doesn't keep rendering a deleted worktree.

Test plan

  • Local: pnpm typecheck clean; pnpm test 330 pass / 65 skip (SQLite-bound tests skip on the Windows host per AGENTS.md; runs in CI image).
  • Manual: rebuilt nexus + worker images, applied migration 0006 cleanly on startup, exercised start agent / stop agent / term / file explorer scope (workspace + session) / session delete in a running container.
  • CI: green build on Forgejo.

🤖 Generated with Claude Code

Per spec at `docs/superpowers/specs/2026-05-24-workspace-shell-unification.md`. ## Summary - New `sessions.kind` column (`'session' | 'workspace_shell'`, migration `0006_workspace_shell_session`). The workspace shell now occupies a real `sessions` row instead of being state-probed from `/proc`. - Open/close routed through `POST/DELETE /api/workers/:id/sessions/workspace`; the previous `workspace-session` endpoint is gone. - `createSession` reserves the name `workspace`; the generic POST rejects it with a clear "reserved" error. - `checkSessionHealth` dispatches on `kind`: regular sessions match the full `--remote-control "<workspace> · <name>"` argv; the workspace shell matches by `cwd=/workspace` (its argument is just `<label>`, a substring of every regular session's). - `WorkerHealthDTO.workspaceShellRunning` removed — UI now derives the toggle state from the workspace_shell row's presence via `listSessions`. - `ExplorerScope` is now `{ kind: 'workspace' | 'session', ... } | null` instead of a three-state union, and lives on the workspace card click (workspace scope) or session row click (session scope). ## UI shape The spec proposed showing the workspace shell as a pinned row in the sessions list. Reverted at user's request — the workspace card keeps its original `start agent` / `stop agent` toggle plus a `term` button that opens a terminal on the boot pane whether claude is running there or not (the workspace terminal endpoint is preserved for that reason). ## Reviewed edge cases - Double-click on "start agent": INSERT race resolved by re-SELECT + `alreadyOpen=true` instead of surfacing the raw SQLite UNIQUE error. - Close-when-container-is-gone: tmux cleanup is now best-effort so the row still drops cleanly, leaving no orphan. - Reconcile: boot `workspace` window still filtered from the orphan-warning list; a `workspace_shell` row whose pane the operator kills externally is reaped after 2 strikes. - Explorer is cleared when its scoped session (or worker) is removed, so the panel doesn't keep rendering a deleted worktree. ## Test plan - [x] Local: `pnpm typecheck` clean; `pnpm test` 330 pass / 65 skip (SQLite-bound tests skip on the Windows host per AGENTS.md; runs in CI image). - [x] Manual: rebuilt nexus + worker images, applied migration 0006 cleanly on startup, exercised start agent / stop agent / term / file explorer scope (workspace + session) / session delete in a running container. - [ ] CI: green build on Forgejo. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
refactor(sessions): unify workspace shell under sessions table
Some checks failed
ci / nexus (pull_request) Failing after 1m13s
ci / images (./nexus, agent-nexus) (pull_request) Has been skipped
ci / images (./worker, nexus-worker) (pull_request) Has been skipped
ci / nexus (push) Failing after 1m6s
ci / images (./nexus, agent-nexus) (push) Has been skipped
ci / images (./worker, nexus-worker) (push) Has been skipped
2caa986718
The workspace shell is now a real `sessions` row with kind='workspace_shell'
(new column, migration 0006). Open/close lives in the sessions service and
goes through `POST/DELETE /api/workers/:id/sessions/workspace` instead of a
parallel `workspace-session` endpoint. `checkSessionHealth` dispatches on
kind: regular sessions match the full `--remote-control <workspace · name>`
argv; the workspace shell matches by cwd=/workspace (its argument is just
the bare label, a substring of every regular session's). The workspace
window orphan-filter in `reconcileSessions` stays so the boot pane doesn't
warn before the operator opens the shell.

`createSession` reserves the literal name `workspace`. `deleteSession` of
a workspace_shell row delegates to `closeWorkspaceSession` so the worktree
removal step (which would target a non-existent path) is skipped.

UI keeps the original shape on the workspace card: card-level term button
(workspace WS endpoint preserved, attaches to the boot pane whether claude
is running or not) plus a start/stop agent toggle derived from the
workspace_shell row's presence. The Sessions component filters
workspace_shell out of the user-visible list. Drops `workspaceShellRunning`
from WorkerHealthDTO — clients now signal off the sessions list directly.

Per spec at docs/superpowers/specs/2026-05-24-workspace-shell-unification.md.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
lz merged commit 2caa986718 into main 2026-05-24 13:48:32 +02:00
lz deleted branch refactor/workspace-shell-unification 2026-05-24 13:48:33 +02:00
lz referenced this pull request from a commit 2026-09-12 19:41:22 +02:00
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
lz/agent-nexus!20
No description provided.