commandmate 0.17.0 → 0.19.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.next/BUILD_ID +1 -1
- package/.next/app-build-manifest.json +120 -120
- package/.next/app-path-routes-manifest.json +19 -19
- package/.next/build-manifest.json +2 -2
- package/.next/prerender-manifest.json +12 -12
- package/.next/required-server-files.json +1 -1
- package/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
- package/.next/server/app/api/app/update/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/app/update-check/route.js +1 -1
- package/.next/server/app/api/app/update-check/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/assistant/conversation/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/assistant/current-output/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/assistant/messages/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/assistant/session/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/assistant/start/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/assistant/terminal/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/assistant/tools/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/auth/login/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/auth/logout/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/auth/status/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/daily-summary/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/daily-summary/status/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/external-apps/[id]/health/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/external-apps/[id]/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/external-apps/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/fs/browse/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/fs/recent-paths/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/hooks/agent-event/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/hooks/claude-done/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/metrics/vibe/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/ollama/models/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/push/subscriptions/route.js +1 -1
- package/.next/server/app/api/push/subscriptions/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/push/vapid/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/repositories/[id]/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/repositories/[id]/todos/[todoId]/route.js +1 -1
- package/.next/server/app/api/repositories/[id]/todos/[todoId]/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/repositories/[id]/todos/route.js +1 -1
- package/.next/server/app/api/repositories/[id]/todos/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/repositories/clone/[jobId]/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/repositories/clone/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/repositories/excluded/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/repositories/restore/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/repositories/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/repositories/scan/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/repositories/sync/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/repositories/validate-path/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/sidebar/group-order/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/skills/[id]/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/skills/installations/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/skills/operations/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/skills/reindex/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/skills/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/slash-commands/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/tasks/[taskId]/cancel/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/tasks/[taskId]/route.js +1 -1
- package/.next/server/app/api/tasks/[taskId]/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/templates/[id]/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/templates/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/todos/route.js +1 -1
- package/.next/server/app/api/todos/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/verification/runs/[runId]/route.js +1 -1
- package/.next/server/app/api/verification/runs/[runId]/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/verification/runs/route.js +1 -1
- package/.next/server/app/api/verification/runs/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/auto-yes/route.js +1 -1
- package/.next/server/app/api/worktrees/[id]/auto-yes/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/capture/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/cli-tool/route.js +1 -1
- package/.next/server/app/api/worktrees/[id]/cli-tool/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/cmate/schedules/route.js +1 -1
- package/.next/server/app/api/worktrees/[id]/cmate/schedules/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/current-output/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/execution-logs/[logId]/route.js +1 -1
- package/.next/server/app/api/worktrees/[id]/execution-logs/[logId]/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/execution-logs/route.js +1 -1
- package/.next/server/app/api/worktrees/[id]/execution-logs/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/files/[...path]/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/git/branch/create/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/git/branch/delete/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/git/branches/route.js +1 -1
- package/.next/server/app/api/worktrees/[id]/git/branches/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/git/checkout/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/git/commit/route.js +1 -1
- package/.next/server/app/api/worktrees/[id]/git/commit/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/git/diff/route.js +1 -1
- package/.next/server/app/api/worktrees/[id]/git/diff/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/git/fetch/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/git/log/route.js +1 -1
- package/.next/server/app/api/worktrees/[id]/git/log/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/git/pull/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/git/push/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/git/reset/route.js +1 -1
- package/.next/server/app/api/worktrees/[id]/git/reset/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/git/revert/route.js +1 -1
- package/.next/server/app/api/worktrees/[id]/git/revert/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/git/show/[commitHash]/route.js +1 -1
- package/.next/server/app/api/worktrees/[id]/git/show/[commitHash]/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/git/stage/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/git/staged/route.js +1 -1
- package/.next/server/app/api/worktrees/[id]/git/staged/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/git/stash/[index]/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/git/stash/apply/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/git/stash/pop/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/git/stash/push/route.js +1 -1
- package/.next/server/app/api/worktrees/[id]/git/stash/push/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/git/stash/route.js +1 -1
- package/.next/server/app/api/worktrees/[id]/git/stash/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/git/status/route.js +1 -1
- package/.next/server/app/api/worktrees/[id]/git/status/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/git/unstage/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/git/working-diff/route.js +1 -1
- package/.next/server/app/api/worktrees/[id]/git/working-diff/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/interrupt/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/kill-session/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/logs/[filename]/route.js +1 -1
- package/.next/server/app/api/worktrees/[id]/logs/[filename]/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/logs/route.js +1 -1
- package/.next/server/app/api/worktrees/[id]/logs/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/marp-render/route.js +1 -1
- package/.next/server/app/api/worktrees/[id]/marp-render/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/memos/[memoId]/route.js +1 -1
- package/.next/server/app/api/worktrees/[id]/memos/[memoId]/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/memos/route.js +1 -1
- package/.next/server/app/api/worktrees/[id]/memos/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/messages/route.js +1 -1
- package/.next/server/app/api/worktrees/[id]/messages/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/prompt-response/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/respond/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/schedules/[scheduleId]/route.js +1 -1
- package/.next/server/app/api/worktrees/[id]/schedules/[scheduleId]/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/schedules/active/route.js +1 -1
- package/.next/server/app/api/worktrees/[id]/schedules/active/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/schedules/route.js +1 -1
- package/.next/server/app/api/worktrees/[id]/schedules/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/search/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/send/route.js +1 -1
- package/.next/server/app/api/worktrees/[id]/send/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/skills/[skillId]/git-workflow/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/skills/[skillId]/install/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/skills/[skillId]/plan/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/skills/[skillId]/uninstall/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/skills/[skillId]/uninstall-plan/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/skills/route.js +1 -1
- package/.next/server/app/api/worktrees/[id]/skills/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/slash-commands/route.js +1 -1
- package/.next/server/app/api/worktrees/[id]/slash-commands/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/special-keys/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/start-polling/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/tasks/route.js +2 -2
- package/.next/server/app/api/worktrees/[id]/tasks/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/terminal/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/timers/history/route.js +1 -1
- package/.next/server/app/api/worktrees/[id]/timers/history/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/timers/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/todos/[todoId]/route.js +1 -1
- package/.next/server/app/api/worktrees/[id]/todos/[todoId]/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/todos/route.js +1 -1
- package/.next/server/app/api/worktrees/[id]/todos/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/tree/[...path]/route.js +1 -1
- package/.next/server/app/api/worktrees/[id]/tree/[...path]/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/tree/route.js +1 -1
- package/.next/server/app/api/worktrees/[id]/tree/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/upload/[...path]/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/verify/route.js +1 -1
- package/.next/server/app/api/worktrees/[id]/verify/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/verify/runs/[runId]/route.js +1 -1
- package/.next/server/app/api/worktrees/[id]/verify/runs/[runId]/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/verify/runs/route.js +1 -1
- package/.next/server/app/api/worktrees/[id]/verify/runs/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/viewed/route.js +1 -1
- package/.next/server/app/api/worktrees/[id]/viewed/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/route_client-reference-manifest.js +1 -1
- package/.next/server/app/chat/page_client-reference-manifest.js +1 -1
- package/.next/server/app/login/page_client-reference-manifest.js +1 -1
- package/.next/server/app/manifest.webmanifest/route_client-reference-manifest.js +1 -1
- package/.next/server/app/more/page_client-reference-manifest.js +1 -1
- package/.next/server/app/offline/page_client-reference-manifest.js +1 -1
- package/.next/server/app/page_client-reference-manifest.js +1 -1
- package/.next/server/app/proxy/[...path]/route_client-reference-manifest.js +1 -1
- package/.next/server/app/repositories/page_client-reference-manifest.js +1 -1
- package/.next/server/app/review/page_client-reference-manifest.js +1 -1
- package/.next/server/app/sessions/page_client-reference-manifest.js +1 -1
- package/.next/server/app/skills/[skillId]/page_client-reference-manifest.js +1 -1
- package/.next/server/app/skills/installed/page_client-reference-manifest.js +1 -1
- package/.next/server/app/skills/page_client-reference-manifest.js +1 -1
- package/.next/server/app/worktrees/[id]/files/[...path]/page_client-reference-manifest.js +1 -1
- package/.next/server/app/worktrees/[id]/page.js +1 -1
- package/.next/server/app/worktrees/[id]/page_client-reference-manifest.js +1 -1
- package/.next/server/app/worktrees/[id]/terminal/page_client-reference-manifest.js +1 -1
- package/.next/server/app-paths-manifest.json +19 -19
- package/.next/server/chunks/1117.js +1 -1
- package/.next/server/chunks/3042.js +1 -1
- package/.next/server/chunks/3753.js +19 -23
- package/.next/server/chunks/5959.js +3 -3
- package/.next/server/chunks/6130.js +1 -1
- package/.next/server/chunks/6274.js +10 -9
- package/.next/server/chunks/6283.js +1 -1
- package/.next/server/chunks/8681.js +1 -1
- package/.next/server/chunks/8807.js +492 -485
- package/.next/server/chunks/8856.js +1 -1
- package/.next/server/middleware-manifest.json +5 -5
- package/.next/server/pages/500.html +1 -1
- package/.next/server/server-reference-manifest.json +1 -1
- package/.next/static/chunks/{6488-f4e447464c9e0e22.js → 6488-2c412c29258637b6.js} +1 -1
- package/.next/static/chunks/app/worktrees/[id]/{page-5ffc84094830babb.js → page-f6e09d25cd3b56f9.js} +1 -1
- package/.next/trace +12 -12
- package/dist/cli/commands/auto-yes.d.ts.map +1 -1
- package/dist/cli/commands/auto-yes.js +9 -2
- package/dist/cli/commands/capture.d.ts +1 -0
- package/dist/cli/commands/capture.d.ts.map +1 -1
- package/dist/cli/commands/capture.js +112 -2
- package/dist/cli/commands/instances.d.ts +24 -0
- package/dist/cli/commands/instances.d.ts.map +1 -1
- package/dist/cli/commands/instances.js +44 -0
- package/dist/cli/commands/respond.d.ts.map +1 -1
- package/dist/cli/commands/respond.js +10 -2
- package/dist/cli/commands/send.d.ts.map +1 -1
- package/dist/cli/commands/send.js +47 -19
- package/dist/cli/commands/wait.d.ts +4 -1
- package/dist/cli/commands/wait.d.ts.map +1 -1
- package/dist/cli/commands/wait.js +134 -10
- package/dist/cli/types/api-responses.d.ts +10 -0
- package/dist/cli/types/api-responses.d.ts.map +1 -1
- package/dist/cli/types/index.d.ts +16 -0
- package/dist/cli/types/index.d.ts.map +1 -1
- package/dist/cli/utils/pager.d.ts +28 -0
- package/dist/cli/utils/pager.d.ts.map +1 -0
- package/dist/cli/utils/pager.js +57 -0
- package/dist/cli/utils/verify-runner.d.ts +6 -0
- package/dist/cli/utils/verify-runner.d.ts.map +1 -1
- package/dist/cli/utils/verify-runner.js +1 -0
- package/dist/lib/detection/ansi.d.ts +30 -0
- package/dist/lib/detection/ansi.d.ts.map +1 -0
- package/dist/lib/detection/ansi.js +39 -0
- package/dist/lib/tmux/transcript-squeeze.d.ts +76 -0
- package/dist/lib/tmux/transcript-squeeze.d.ts.map +1 -0
- package/dist/lib/tmux/transcript-squeeze.js +96 -0
- package/dist/server/server.js +18 -0
- package/dist/server/src/config/tmux-pane-config.js +33 -1
- package/dist/server/src/lib/cli-tools/antigravity.js +2 -1
- package/dist/server/src/lib/cli-tools/codex.js +3 -2
- package/dist/server/src/lib/cli-tools/copilot.js +2 -2
- package/dist/server/src/lib/cli-tools/gemini.js +2 -2
- package/dist/server/src/lib/cli-tools/opencode.js +2 -2
- package/dist/server/src/lib/cli-tools/vibe-local.js +2 -2
- package/dist/server/src/lib/db/agent-instances-db.js +53 -0
- package/dist/server/src/lib/db/migrations/index.js +2 -0
- package/dist/server/src/lib/db/migrations/runner.js +1 -1
- package/dist/server/src/lib/db/migrations/v52-orphaned-worktree-children.js +76 -0
- package/dist/server/src/lib/db/migrations/worktree-child-tables.js +119 -0
- package/dist/server/src/lib/db/tasks-db.js +46 -1
- package/dist/server/src/lib/db/verification-db.js +21 -3
- package/dist/server/src/lib/db/worktree-db.js +65 -42
- package/dist/server/src/lib/detection/cli-patterns.js +24 -1
- package/dist/server/src/lib/detection/status-detector.js +52 -2
- package/dist/server/src/lib/session/claude-session.js +4 -2
- package/dist/server/src/lib/tmux/read-mode-pager.js +161 -0
- package/dist/server/src/lib/tmux/read-mode.js +327 -0
- package/dist/server/src/lib/tmux/tmux.js +43 -5
- package/package.json +1 -1
- /package/.next/static/{gR8Sh86RrTynJON8SWkWK → -E7I667pJDTBamyG8M_Z-}/_buildManifest.js +0 -0
- /package/.next/static/{gR8Sh86RrTynJON8SWkWK → -E7I667pJDTBamyG8M_Z-}/_ssgManifest.js +0 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"transcript-squeeze.d.ts","sourceRoot":"","sources":["../../../src/lib/tmux/transcript-squeeze.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAIH;;;;;;GAMG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAErD;AAED,6CAA6C;AAC7C,MAAM,WAAW,cAAc;IAC7B;;;;;;;OAOG;IACH,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,6EAA6E;AAC7E,MAAM,WAAW,aAAa;IAC5B,gDAAgD;IAChD,IAAI,EAAE,MAAM,CAAC;IACb,iDAAiD;IACjD,QAAQ,EAAE,MAAM,CAAC;IACjB,kCAAkC;IAClC,KAAK,EAAE,MAAM,CAAC;IACd,4EAA4E;IAC5E,MAAM,EAAE,OAAO,CAAC;CACjB;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,GAAE,cAAmB,GAAG,aAAa,CAwC1F"}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Whitespace squeeze for the on-demand reading mode (Issue #1623).
|
|
4
|
+
*
|
|
5
|
+
* TUI sessions are pinned to a 200x1000 canvas (`src/config/tmux-pane-config.ts`)
|
|
6
|
+
* and the agent draws its transcript near the top and its composer at the very
|
|
7
|
+
* bottom, so a raw `capture-pane` is mostly layout-only blank rows. Measured on a
|
|
8
|
+
* live `mcbd-claude-*` session: 1000 rows, 480 truly empty, 7 more that LOOK empty
|
|
9
|
+
* but carry an SGR sequence (`ESC[49m`) and therefore survive a naive
|
|
10
|
+
* `grep -v '^$'` or `less -s`. Squeezing those runs is what makes `less +G` land
|
|
11
|
+
* on the composer instead of hundreds of rows below the last readable line.
|
|
12
|
+
*
|
|
13
|
+
* ## Relationship to the Web UI normalizer
|
|
14
|
+
*
|
|
15
|
+
* `src/lib/terminal/terminal-display-normalizer.ts` (Issue #1172) solves the same
|
|
16
|
+
* problem for the browser terminal and its rules are the ones reproduced here.
|
|
17
|
+
* This module exists as a separate entry point rather than importing that one
|
|
18
|
+
* because the CLI is compiled by `tsconfig.cli.json`, which sets `"paths": {}` —
|
|
19
|
+
* the normalizer's `@/lib/detection/ansi` import fails there with TS2307 (verified
|
|
20
|
+
* by adding the import and running `tsc --project tsconfig.cli.json`), while the
|
|
21
|
+
* relative import below resolves under every build. The two are held byte-identical
|
|
22
|
+
* by a conformance test over real captures
|
|
23
|
+
* (`tests/unit/lib/tmux/transcript-squeeze.test.ts`), so neither can drift.
|
|
24
|
+
*
|
|
25
|
+
* The transform is DISPLAY-ONLY. Nothing here touches the raw output that status
|
|
26
|
+
* detection, Auto-Yes, the response saver or the DB record consume — those keep
|
|
27
|
+
* full fidelity and full line counts.
|
|
28
|
+
*/
|
|
29
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
30
|
+
exports.isVisuallyBlank = isVisuallyBlank;
|
|
31
|
+
exports.squeezeTranscript = squeezeTranscript;
|
|
32
|
+
const ansi_1 = require("../detection/ansi");
|
|
33
|
+
/**
|
|
34
|
+
* A line is visually blank when nothing printable remains after ANSI removal.
|
|
35
|
+
*
|
|
36
|
+
* `.trim()` is deliberate: ECMAScript's WhiteSpace includes U+00A0, which the
|
|
37
|
+
* Claude composer emits (`ESC[38;5;246m❯ ESC[39m`). That line still holds
|
|
38
|
+
* `❯`, so it is content either way, but a row of pure NBSP correctly reads blank.
|
|
39
|
+
*/
|
|
40
|
+
function isVisuallyBlank(line) {
|
|
41
|
+
return (0, ansi_1.stripAnsi)(line).trim() === '';
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Collapse layout-only blank rows in a `capture-pane` frame.
|
|
45
|
+
*
|
|
46
|
+
* Rules (identical to `normalizeTerminalOutputForDisplay`, Issue #1172):
|
|
47
|
+
* 1. Blankness is judged on `stripAnsi(line).trim()`.
|
|
48
|
+
* 2. Leading and trailing blank runs are removed entirely.
|
|
49
|
+
* 3. An internal blank run of 1-2 rows is kept verbatim (real paragraph spacing).
|
|
50
|
+
* 4. An internal blank run of 3+ rows collapses to ONE row carrying that run's
|
|
51
|
+
* ANSI sequences in order, so a color/reset that spans the gap still applies
|
|
52
|
+
* to the rows below it.
|
|
53
|
+
* 5. Non-blank lines are never altered — content, order and duplicates survive.
|
|
54
|
+
*
|
|
55
|
+
* @param raw - Raw `capture-pane -pe` output
|
|
56
|
+
* @param options - Tail limit
|
|
57
|
+
* @returns The squeezed text plus before/after line counts
|
|
58
|
+
*/
|
|
59
|
+
function squeezeTranscript(raw, options = {}) {
|
|
60
|
+
const lines = raw === '' ? [] : raw.split('\n');
|
|
61
|
+
const result = [];
|
|
62
|
+
let seenContent = false;
|
|
63
|
+
let i = 0;
|
|
64
|
+
while (i < lines.length) {
|
|
65
|
+
if (!isVisuallyBlank(lines[i])) {
|
|
66
|
+
result.push(lines[i]);
|
|
67
|
+
seenContent = true;
|
|
68
|
+
i++;
|
|
69
|
+
continue;
|
|
70
|
+
}
|
|
71
|
+
let j = i;
|
|
72
|
+
while (j < lines.length && isVisuallyBlank(lines[j]))
|
|
73
|
+
j++;
|
|
74
|
+
const isLeading = !seenContent;
|
|
75
|
+
const isTrailing = j >= lines.length;
|
|
76
|
+
if (!isLeading && !isTrailing) {
|
|
77
|
+
if (j - i <= 2) {
|
|
78
|
+
for (let k = i; k < j; k++)
|
|
79
|
+
result.push(lines[k]);
|
|
80
|
+
}
|
|
81
|
+
else {
|
|
82
|
+
result.push((0, ansi_1.extractAnsiSequences)(lines.slice(i, j).join('')));
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
i = j;
|
|
86
|
+
}
|
|
87
|
+
const tail = options.tail;
|
|
88
|
+
const tailed = tail !== undefined && tail > 0 && result.length > tail;
|
|
89
|
+
const kept = tailed ? result.slice(result.length - tail) : result;
|
|
90
|
+
return {
|
|
91
|
+
text: kept.join('\n'),
|
|
92
|
+
rawLines: lines.length,
|
|
93
|
+
lines: kept.length,
|
|
94
|
+
tailed,
|
|
95
|
+
};
|
|
96
|
+
}
|
package/dist/server/server.js
CHANGED
|
@@ -319,6 +319,24 @@ app.prepare().then(() => {
|
|
|
319
319
|
console.log(`> WebSocket server ready`);
|
|
320
320
|
// Initialize worktrees after server starts
|
|
321
321
|
await initializeWorktrees();
|
|
322
|
+
// Issue #1623: reconcile the `prefix+g` reading-mode binding on the user's
|
|
323
|
+
// tmux server. Fail-open in its own try/catch — a convenience key must never
|
|
324
|
+
// be able to stop the server from serving.
|
|
325
|
+
//
|
|
326
|
+
// Dynamic import, NOT a top-level static import, for the same reason as the
|
|
327
|
+
// reconcilers above: adding a module graph to server.ts's eval-time graph
|
|
328
|
+
// perturbs Next's AsyncLocalStorage bootstrap under `tsx server.ts`, and the
|
|
329
|
+
// first request that compiles middleware then dies. Do not hoist this.
|
|
330
|
+
try {
|
|
331
|
+
const { initReadMode } = await Promise.resolve().then(() => __importStar(require('./src/lib/tmux/read-mode')));
|
|
332
|
+
const readMode = await initReadMode();
|
|
333
|
+
if (readMode.installed) {
|
|
334
|
+
console.log(`Reading mode ready: press prefix+${readMode.key} in an attached CommandMate session`);
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
catch (error) {
|
|
338
|
+
console.error('Error initializing tmux reading mode:', error);
|
|
339
|
+
}
|
|
322
340
|
// [S3-010] Initialize schedule manager AFTER worktrees are ready
|
|
323
341
|
(0, schedule_manager_1.initScheduleManager)();
|
|
324
342
|
// Issue #534: Initialize timer manager AFTER schedule manager
|
|
@@ -17,8 +17,40 @@
|
|
|
17
17
|
* so `capture-pane` cost / render load stays reasonable.
|
|
18
18
|
*/
|
|
19
19
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
20
|
-
exports.TUI_PANE_WIDTH = exports.TUI_PANE_HEIGHT = void 0;
|
|
20
|
+
exports.TMUX_HISTORY_LIMIT = exports.TUI_PANE_WIDTH = exports.TUI_PANE_HEIGHT = void 0;
|
|
21
21
|
/** Fixed pane height (rows) for TUI sessions. Large enough to retain useful history. */
|
|
22
22
|
exports.TUI_PANE_HEIGHT = 1000;
|
|
23
23
|
/** Fixed pane width (columns) for TUI sessions. Matches the historical default. */
|
|
24
24
|
exports.TUI_PANE_WIDTH = 200;
|
|
25
|
+
/**
|
|
26
|
+
* Scrollback lines retained per pane (`history-limit`), Issue #1624.
|
|
27
|
+
*
|
|
28
|
+
* Only NON-alternate-screen tools (codex / gemini / vibe-local / antigravity)
|
|
29
|
+
* consume this: the alternate-screen tools in `ALTERNATE_SCREEN_CLI_TOOLS`
|
|
30
|
+
* (claude / opencode / copilot) keep `history_size` at 0, so scrollback depth is
|
|
31
|
+
* irrelevant to them. See `usesAlternateScreen` in lib/cli-tools/types.ts.
|
|
32
|
+
*
|
|
33
|
+
* WHY 20000 and not the 50000 this used to request — measured on tmux 3.5a with
|
|
34
|
+
* a 200-column pane full of realistic, heavily-SGR-coloured agent output
|
|
35
|
+
* (274 bytes/line as re-emitted by `capture-pane -e`):
|
|
36
|
+
*
|
|
37
|
+
* 1. `capturePane` runs tmux under `execFile` with `maxBuffer: 10 MB`. A full
|
|
38
|
+
* 50000-line capture extrapolates to ~13 MB of escape-laden text, which
|
|
39
|
+
* OVERFLOWS that buffer and makes `capturePane` THROW rather than return a
|
|
40
|
+
* truncated string — i.e. the deepest part of a 50000-line buffer was never
|
|
41
|
+
* readable by this app in the first place. 20000 lines is ~5.2 MB, leaving
|
|
42
|
+
* roughly 2x headroom.
|
|
43
|
+
* 2. Nothing in the app reads deeper than 10000 lines by default (`capturePane`'s
|
|
44
|
+
* options-path default is `-S -10000`; every other call site asks for 1000 or
|
|
45
|
+
* fewer). 20000 keeps a 2x margin over the deepest default read.
|
|
46
|
+
* 3. Memory is charged per line ACTUALLY emitted, not preallocated from the
|
|
47
|
+
* limit: a fresh session cost ~0.3 MB of tmux-server RSS, growing to ~34 MB
|
|
48
|
+
* only after 45000 lines had scrolled (~0.68 KB/line). At 20000 a saturated
|
|
49
|
+
* pane costs ~14 MB, so 20 concurrent sessions cap out near 270 MB instead of
|
|
50
|
+
* the ~680 MB implied by 50000.
|
|
51
|
+
*
|
|
52
|
+
* This is still a 10x increase over tmux's built-in 2000, which is what sessions
|
|
53
|
+
* were actually getting before #1624 (a `mcbd-codex-*` session was observed at
|
|
54
|
+
* 1977/2000 lines used, i.e. actively losing transcript).
|
|
55
|
+
*/
|
|
56
|
+
exports.TMUX_HISTORY_LIMIT = 20000;
|
|
@@ -119,10 +119,11 @@ class AntigravityTool extends base_1.BaseCLITool {
|
|
|
119
119
|
try {
|
|
120
120
|
// Create tmux session with large history buffer for agy output
|
|
121
121
|
// (agy is inline-rendered and retains scrollback, like Codex)
|
|
122
|
+
// Scrollback depth comes from the shared TMUX_HISTORY_LIMIT default
|
|
123
|
+
// (Issue #1624) — do not re-hardcode it here.
|
|
122
124
|
await (0, tmux_1.createSession)({
|
|
123
125
|
sessionName,
|
|
124
126
|
workingDirectory: worktreePath,
|
|
125
|
-
historyLimit: 50000,
|
|
126
127
|
});
|
|
127
128
|
// Wait a moment for the session to be created
|
|
128
129
|
await new Promise((resolve) => setTimeout(resolve, cli_tool_timing_config_1.TUI_SESSION_CREATE_WAIT_MS));
|
|
@@ -67,11 +67,12 @@ class CodexTool extends base_1.BaseCLITool {
|
|
|
67
67
|
return;
|
|
68
68
|
}
|
|
69
69
|
try {
|
|
70
|
-
// Create tmux session
|
|
70
|
+
// Create tmux session. Codex is inline-rendered, so its transcript lives in
|
|
71
|
+
// the pane scrollback — depth comes from the shared TMUX_HISTORY_LIMIT
|
|
72
|
+
// default (Issue #1624), do not re-hardcode it here.
|
|
71
73
|
await (0, tmux_1.createSession)({
|
|
72
74
|
sessionName,
|
|
73
75
|
workingDirectory: worktreePath,
|
|
74
|
-
historyLimit: 50000,
|
|
75
76
|
});
|
|
76
77
|
// Wait a moment for the session to be created
|
|
77
78
|
await new Promise((resolve) => setTimeout(resolve, cli_tool_timing_config_1.TUI_SESSION_CREATE_WAIT_MS));
|
|
@@ -113,11 +113,11 @@ class CopilotTool extends base_1.BaseCLITool {
|
|
|
113
113
|
return;
|
|
114
114
|
}
|
|
115
115
|
try {
|
|
116
|
-
// Create tmux session
|
|
116
|
+
// Create tmux session. Scrollback depth comes from the shared
|
|
117
|
+
// TMUX_HISTORY_LIMIT default (Issue #1624) — do not re-hardcode it here.
|
|
117
118
|
await (0, tmux_1.createSession)({
|
|
118
119
|
sessionName,
|
|
119
120
|
workingDirectory: worktreePath,
|
|
120
|
-
historyLimit: 50000,
|
|
121
121
|
});
|
|
122
122
|
// Wait a moment for the session to be created
|
|
123
123
|
await new Promise((resolve) => setTimeout(resolve, cli_tool_timing_config_1.TUI_SESSION_CREATE_WAIT_MS));
|
|
@@ -74,11 +74,11 @@ class GeminiTool extends base_1.BaseCLITool {
|
|
|
74
74
|
return;
|
|
75
75
|
}
|
|
76
76
|
try {
|
|
77
|
-
// Create tmux session
|
|
77
|
+
// Create tmux session. Scrollback depth comes from the shared
|
|
78
|
+
// TMUX_HISTORY_LIMIT default (Issue #1624) — do not re-hardcode it here.
|
|
78
79
|
await (0, tmux_1.createSession)({
|
|
79
80
|
sessionName,
|
|
80
81
|
workingDirectory: worktreePath,
|
|
81
|
-
historyLimit: 50000,
|
|
82
82
|
});
|
|
83
83
|
// Wait a moment for the session to be created
|
|
84
84
|
await new Promise((resolve) => setTimeout(resolve, cli_tool_timing_config_1.TUI_SESSION_CREATE_WAIT_MS));
|
|
@@ -89,11 +89,11 @@ class OpenCodeTool extends base_1.BaseCLITool {
|
|
|
89
89
|
try {
|
|
90
90
|
// Generate opencode.json if not present (non-fatal on failure)
|
|
91
91
|
await (0, opencode_config_1.ensureOpencodeConfig)(worktreePath);
|
|
92
|
-
// Create tmux session
|
|
92
|
+
// Create tmux session. Scrollback depth comes from the shared
|
|
93
|
+
// TMUX_HISTORY_LIMIT default (Issue #1624) — do not re-hardcode it here.
|
|
93
94
|
await (0, tmux_1.createSession)({
|
|
94
95
|
sessionName,
|
|
95
96
|
workingDirectory: worktreePath,
|
|
96
|
-
historyLimit: 50000,
|
|
97
97
|
});
|
|
98
98
|
// Wait a moment for the session to be created
|
|
99
99
|
await new Promise((resolve) => setTimeout(resolve, cli_tool_timing_config_1.TUI_SESSION_CREATE_WAIT_MS));
|
|
@@ -66,11 +66,11 @@ class VibeLocalTool extends base_1.BaseCLITool {
|
|
|
66
66
|
return;
|
|
67
67
|
}
|
|
68
68
|
try {
|
|
69
|
-
// Create tmux session
|
|
69
|
+
// Create tmux session. Scrollback depth comes from the shared
|
|
70
|
+
// TMUX_HISTORY_LIMIT default (Issue #1624) — do not re-hardcode it here.
|
|
70
71
|
await (0, tmux_1.createSession)({
|
|
71
72
|
sessionName,
|
|
72
73
|
workingDirectory: worktreePath,
|
|
73
|
-
historyLimit: 50000,
|
|
74
74
|
});
|
|
75
75
|
// Wait a moment for the session to be created
|
|
76
76
|
await new Promise((resolve) => setTimeout(resolve, cli_tool_timing_config_1.TUI_SESSION_CREATE_WAIT_MS));
|
|
@@ -15,6 +15,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
15
15
|
exports.InvalidAgentInstanceError = exports.AgentInstanceLimitError = void 0;
|
|
16
16
|
exports.getAgentInstances = getAgentInstances;
|
|
17
17
|
exports.getAgentInstance = getAgentInstance;
|
|
18
|
+
exports.resolveInstanceCliTool = resolveInstanceCliTool;
|
|
18
19
|
exports.countAgentInstances = countAgentInstances;
|
|
19
20
|
exports.setAgentInstances = setAgentInstances;
|
|
20
21
|
exports.addAgentInstance = addAgentInstance;
|
|
@@ -103,6 +104,58 @@ function getAgentInstance(db, worktreeId, instanceId) {
|
|
|
103
104
|
`).get(worktreeId, instanceId);
|
|
104
105
|
return row ? mapAgentInstance(row) : null;
|
|
105
106
|
}
|
|
107
|
+
/**
|
|
108
|
+
* Resolve which CLI tool backs a targeted agent instance (Issue #1629).
|
|
109
|
+
*
|
|
110
|
+
* The CLI tool id is part of the tmux session name, so getting it wrong starts
|
|
111
|
+
* (or looks for) the wrong agent under a session name that claims otherwise:
|
|
112
|
+
* `--instance codex` used to start Claude in `mcbd-claude-<wt>-codex`.
|
|
113
|
+
*
|
|
114
|
+
* Resolution order:
|
|
115
|
+
* 1. the roster entry for `instanceId` — the roster is what declares that
|
|
116
|
+
* `codex` is a codex instance, so it wins over the worktree default
|
|
117
|
+
* 2. `requestedCliTool`, for an instance the roster does not know about
|
|
118
|
+
* (the ad-hoc `send --instance <new-id>` / `--register` flow)
|
|
119
|
+
* 3. `instanceId` when it is itself a CLI tool id — that is how the primary
|
|
120
|
+
* instance is anchored (Issue #868), and holds without a roster row
|
|
121
|
+
* 4. no signal (`cliToolId: null`) — the caller falls back to its default
|
|
122
|
+
*
|
|
123
|
+
* An explicit `requestedCliTool` that contradicts the roster is reported as a
|
|
124
|
+
* conflict rather than silently overriding it: the roster is user-maintained
|
|
125
|
+
* and a mismatch means one of the two is wrong. Callers surface it as an error.
|
|
126
|
+
*
|
|
127
|
+
* @param db - Database instance
|
|
128
|
+
* @param worktreeId - Worktree ID
|
|
129
|
+
* @param instanceId - Targeted agent instance ID (omitted for the primary instance)
|
|
130
|
+
* @param requestedCliTool - CLI tool explicitly named by the caller, if any
|
|
131
|
+
*/
|
|
132
|
+
function resolveInstanceCliTool(db, worktreeId, instanceId, requestedCliTool) {
|
|
133
|
+
if (!instanceId) {
|
|
134
|
+
return { ok: true, cliToolId: requestedCliTool ?? null };
|
|
135
|
+
}
|
|
136
|
+
const registered = getAgentInstance(db, worktreeId, instanceId);
|
|
137
|
+
if (registered && (0, types_1.isCliToolType)(registered.cliTool)) {
|
|
138
|
+
if (requestedCliTool && requestedCliTool !== registered.cliTool) {
|
|
139
|
+
return {
|
|
140
|
+
ok: false,
|
|
141
|
+
instanceId,
|
|
142
|
+
rosterCliTool: registered.cliTool,
|
|
143
|
+
requestedCliTool,
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
return { ok: true, cliToolId: registered.cliTool };
|
|
147
|
+
}
|
|
148
|
+
// Not registered: an instance id that names a CLI tool is that tool's primary
|
|
149
|
+
// instance by definition, which outranks the worktree default but not an
|
|
150
|
+
// explicit request.
|
|
151
|
+
if (requestedCliTool) {
|
|
152
|
+
return { ok: true, cliToolId: requestedCliTool };
|
|
153
|
+
}
|
|
154
|
+
if ((0, types_1.isCliToolType)(instanceId)) {
|
|
155
|
+
return { ok: true, cliToolId: instanceId };
|
|
156
|
+
}
|
|
157
|
+
return { ok: true, cliToolId: null };
|
|
158
|
+
}
|
|
106
159
|
/**
|
|
107
160
|
* Count agent instances for a worktree.
|
|
108
161
|
*/
|
|
@@ -35,6 +35,7 @@ const v48_skill_operations_audit_index_1 = require("./v48-skill-operations-audit
|
|
|
35
35
|
const v49_verification_runs_1 = require("./v49-verification-runs");
|
|
36
36
|
const v50_tasks_1 = require("./v50-tasks");
|
|
37
37
|
const v51_task_events_1 = require("./v51-task-events");
|
|
38
|
+
const v52_orphaned_worktree_children_1 = require("./v52-orphaned-worktree-children");
|
|
38
39
|
/**
|
|
39
40
|
* Complete ordered list of all migrations.
|
|
40
41
|
* Used internally by the barrel file (db-migrations.ts); NOT exported from the barrel (DR2-004).
|
|
@@ -73,4 +74,5 @@ exports.migrations = [
|
|
|
73
74
|
...v49_verification_runs_1.v49_migrations,
|
|
74
75
|
...v50_tasks_1.v50_migrations,
|
|
75
76
|
...v51_task_events_1.v51_migrations,
|
|
77
|
+
...v52_orphaned_worktree_children_1.v52_migrations,
|
|
76
78
|
];
|
|
@@ -17,7 +17,7 @@ const path_1 = __importDefault(require("path"));
|
|
|
17
17
|
* Current schema version
|
|
18
18
|
* Increment this when adding new migrations
|
|
19
19
|
*/
|
|
20
|
-
exports.CURRENT_SCHEMA_VERSION =
|
|
20
|
+
exports.CURRENT_SCHEMA_VERSION = 52;
|
|
21
21
|
/**
|
|
22
22
|
* Get current schema version from database
|
|
23
23
|
*/
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Migration v52: delete child rows pointing at a worktree that no longer exists
|
|
4
|
+
* (Issue #1621, Phase 0).
|
|
5
|
+
*
|
|
6
|
+
* `getWorktreeChildTables` used to enumerate child tables from foreign-key
|
|
7
|
+
* metadata alone, so `tasks` (#1548), `verification_runs` (#1544) and
|
|
8
|
+
* `skill_operations` (#1428) — each holding a `worktree_id` with no constraint
|
|
9
|
+
* declared — were invisible to both the rename path and the delete path. No
|
|
10
|
+
* CASCADE fires without a foreign key, so every worktree deleted since those
|
|
11
|
+
* tables landed left their rows behind, and every worktree ID rename left them
|
|
12
|
+
* addressing an ID nothing resolves. The code fix stops new orphans; this
|
|
13
|
+
* migration removes the ones already written.
|
|
14
|
+
*
|
|
15
|
+
* The sweep reuses the shared enumeration rather than naming tables, so the
|
|
16
|
+
* FK-less tables above and the FK-declared ones are treated identically — an
|
|
17
|
+
* orphan in a CASCADE table is equally unreachable, it just needed a
|
|
18
|
+
* `foreign_keys = OFF` window to be created.
|
|
19
|
+
*
|
|
20
|
+
* Three deliberate narrowings:
|
|
21
|
+
*
|
|
22
|
+
* - Append-only ledgers are excluded (`getDeletableWorktreeChildTables`).
|
|
23
|
+
* `skill_operations` holds a `worktree_id` and does have rows referencing
|
|
24
|
+
* worktrees that are gone, but it declares `BEFORE DELETE ... RAISE(ABORT)`
|
|
25
|
+
* (#1234) and is read across every worktree by the audit feed. Deleting from
|
|
26
|
+
* it aborts the statement, which here would roll back the migration and take
|
|
27
|
+
* startup with it (measured). Its rows are history, not orphans.
|
|
28
|
+
* - `worktree_id IS NULL` rows are left alone. Every current child table
|
|
29
|
+
* declares the column NOT NULL, so this cannot fire today; if a future table
|
|
30
|
+
* makes it nullable, a NULL means "not attached", not "attached to something
|
|
31
|
+
* gone", and deleting it would be data loss rather than cleanup.
|
|
32
|
+
* - `verification_gate_results` has no `worktree_id` and is not swept directly.
|
|
33
|
+
* It reaches its worktree through `verification_runs(id) ON DELETE CASCADE`,
|
|
34
|
+
* so deleting the orphaned runs takes its rows with them (better-sqlite3
|
|
35
|
+
* enables `foreign_keys` by default, and db-instance.ts sets it explicitly).
|
|
36
|
+
* `task_events` is *not* in the same position: v51 declares no foreign key to
|
|
37
|
+
* `tasks` on purpose, because history that vanishes with its subject cannot
|
|
38
|
+
* be audited afterwards. That is a design choice, not an orphan, so it stays.
|
|
39
|
+
*/
|
|
40
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
41
|
+
exports.v52_migrations = void 0;
|
|
42
|
+
const worktree_child_tables_1 = require("./worktree-child-tables");
|
|
43
|
+
exports.v52_migrations = [
|
|
44
|
+
{
|
|
45
|
+
version: 52,
|
|
46
|
+
name: 'delete-orphaned-worktree-children',
|
|
47
|
+
up: (db) => {
|
|
48
|
+
let deleted = 0;
|
|
49
|
+
for (const { table, column } of (0, worktree_child_tables_1.getDeletableWorktreeChildTables)(db)) {
|
|
50
|
+
// Identifiers come from sqlite_master / PRAGMA, never from user input.
|
|
51
|
+
// The alias is `cm_parent` rather than something short like `w` so it
|
|
52
|
+
// cannot collide with a child table that happens to be named the same.
|
|
53
|
+
const result = db
|
|
54
|
+
.prepare(`DELETE FROM "${table}"
|
|
55
|
+
WHERE "${column}" IS NOT NULL
|
|
56
|
+
AND NOT EXISTS (
|
|
57
|
+
SELECT 1 FROM worktrees AS cm_parent
|
|
58
|
+
WHERE cm_parent.id = "${table}"."${column}"
|
|
59
|
+
)`)
|
|
60
|
+
.run();
|
|
61
|
+
if (result.changes > 0) {
|
|
62
|
+
console.log(`Deleted ${result.changes} orphaned row(s) from ${table}.${column}`);
|
|
63
|
+
deleted += result.changes;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
if (deleted === 0) {
|
|
67
|
+
console.log('No orphaned worktree child rows found');
|
|
68
|
+
}
|
|
69
|
+
},
|
|
70
|
+
down: () => {
|
|
71
|
+
// Deleted rows cannot be resurrected. Defined anyway so rollback tooling
|
|
72
|
+
// (and the tests that rebuild a v51 database) keep working.
|
|
73
|
+
console.log('No rollback for orphaned child row cleanup (rows are gone)');
|
|
74
|
+
},
|
|
75
|
+
},
|
|
76
|
+
];
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Live-schema introspection for "which tables reference a worktree".
|
|
4
|
+
*
|
|
5
|
+
* Lives under `migrations/` rather than next to `worktree-db.ts` because the
|
|
6
|
+
* dependency has to point this way: migration v52 sweeps orphans with the same
|
|
7
|
+
* enumeration the runtime uses, and a migration that imports `worktree-db.ts`
|
|
8
|
+
* inherits every mock of it — `vi.mock('@/lib/db/worktree-db')` in an unrelated
|
|
9
|
+
* suite made `runMigrations` throw `No "getDeletableWorktreeChildTables" export
|
|
10
|
+
* is defined` (measured across 5 tests). Schema introspection is the more
|
|
11
|
+
* foundational of the two, so it is what the other side imports.
|
|
12
|
+
*
|
|
13
|
+
* Issue #1621.
|
|
14
|
+
*/
|
|
15
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
16
|
+
exports.getWorktreeChildTables = getWorktreeChildTables;
|
|
17
|
+
exports.getWriteGuardedTables = getWriteGuardedTables;
|
|
18
|
+
exports.getDeletableWorktreeChildTables = getDeletableWorktreeChildTables;
|
|
19
|
+
/** Conventional name of the column holding a worktree reference. */
|
|
20
|
+
const WORKTREE_REFERENCE_COLUMN = 'worktree_id';
|
|
21
|
+
/**
|
|
22
|
+
* Discover every table that references a worktree, along with the referencing
|
|
23
|
+
* column. Derived dynamically from the live schema so it never drifts out of
|
|
24
|
+
* sync with future migrations that add child tables.
|
|
25
|
+
*
|
|
26
|
+
* Two sources are unioned:
|
|
27
|
+
*
|
|
28
|
+
* 1. a `worktree_id` column, **whether or not** the table declares a foreign key
|
|
29
|
+
* 2. a declared foreign key to `worktrees(id)`, whatever the column is named
|
|
30
|
+
*
|
|
31
|
+
* Source 1 is what Issue #1621 adds. FK metadata alone (all this did before)
|
|
32
|
+
* silently skipped `tasks` (#1548), `verification_runs` (#1544) and
|
|
33
|
+
* `skill_operations` (#1428): all three carry a `worktree_id` but declare no
|
|
34
|
+
* constraint, so a rename left them pointing at an ID that no longer exists and
|
|
35
|
+
* a delete left them behind as orphans — no CASCADE fires without a FK.
|
|
36
|
+
*
|
|
37
|
+
* Source 2 is kept so a future table whose FK column is *not* named
|
|
38
|
+
* `worktree_id` keeps being followed; dropping it would be a regression for
|
|
39
|
+
* every table #1151 already handled.
|
|
40
|
+
*
|
|
41
|
+
* Issue #1151, #1621.
|
|
42
|
+
*/
|
|
43
|
+
function getWorktreeChildTables(db) {
|
|
44
|
+
const tables = db
|
|
45
|
+
.prepare("SELECT name FROM sqlite_master WHERE type = 'table' AND name NOT LIKE 'sqlite_%'")
|
|
46
|
+
.all();
|
|
47
|
+
const children = [];
|
|
48
|
+
const seen = new Set();
|
|
49
|
+
const add = (table, column) => {
|
|
50
|
+
// JSON encoding keeps the pair unambiguous whatever the identifiers contain.
|
|
51
|
+
const key = JSON.stringify([table, column]);
|
|
52
|
+
if (seen.has(key))
|
|
53
|
+
return;
|
|
54
|
+
seen.add(key);
|
|
55
|
+
children.push({ table, column });
|
|
56
|
+
};
|
|
57
|
+
for (const { name: tableName } of tables) {
|
|
58
|
+
if (tableName === 'worktrees')
|
|
59
|
+
continue;
|
|
60
|
+
// PRAGMA identifiers cannot be bound; `tableName` comes from sqlite_master,
|
|
61
|
+
// not user input, so string interpolation here is safe.
|
|
62
|
+
const columns = db.prepare(`PRAGMA table_info("${tableName}")`).all();
|
|
63
|
+
if (columns.some((column) => column.name === WORKTREE_REFERENCE_COLUMN)) {
|
|
64
|
+
add(tableName, WORKTREE_REFERENCE_COLUMN);
|
|
65
|
+
}
|
|
66
|
+
const fks = db.prepare(`PRAGMA foreign_key_list("${tableName}")`).all();
|
|
67
|
+
for (const fk of fks) {
|
|
68
|
+
if (fk.table === 'worktrees' && (fk.to === 'id' || fk.to === null)) {
|
|
69
|
+
add(tableName, fk.from);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
return children;
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Tables the schema itself refuses to let us rewrite, per statement kind.
|
|
77
|
+
*
|
|
78
|
+
* An append-only ledger enforces itself with a `BEFORE UPDATE` / `BEFORE DELETE`
|
|
79
|
+
* trigger that calls `RAISE(ABORT, ...)`; `skill_operations` declares both
|
|
80
|
+
* (#1234, migrations v44/v48). Such a table carries a `worktree_id` yet is
|
|
81
|
+
* deliberately *not* owned by its worktree's lifetime: `querySkillOperationAudit`
|
|
82
|
+
* reads it across every worktree for the dashboard feed, exactly like v51 keeps
|
|
83
|
+
* `task_events` alive after its task is gone. Sweeping it is not cleanup, it is
|
|
84
|
+
* destroying audit history — and it does not even fail safely: the trigger
|
|
85
|
+
* aborts the statement, which rolls back the whole enclosing transaction, so a
|
|
86
|
+
* blind sweep breaks worktree deletion outright (measured: `deleteWorktreesByIds`
|
|
87
|
+
* threw `skill_operations is append-only` and the worktree row survived; the
|
|
88
|
+
* same sweep inside migration v52 fails startup).
|
|
89
|
+
*
|
|
90
|
+
* Detected from trigger SQL rather than by table name so a future ledger is
|
|
91
|
+
* protected the moment it declares its guard (Issue #1621).
|
|
92
|
+
*/
|
|
93
|
+
function getWriteGuardedTables(db) {
|
|
94
|
+
const triggers = db
|
|
95
|
+
.prepare("SELECT tbl_name, sql FROM sqlite_master WHERE type = 'trigger'")
|
|
96
|
+
.all();
|
|
97
|
+
const noUpdate = new Set();
|
|
98
|
+
const noDelete = new Set();
|
|
99
|
+
for (const trigger of triggers) {
|
|
100
|
+
if (!trigger.sql)
|
|
101
|
+
continue;
|
|
102
|
+
if (!/\bRAISE\s*\(\s*ABORT/i.test(trigger.sql))
|
|
103
|
+
continue;
|
|
104
|
+
if (/\bBEFORE\s+UPDATE\b/i.test(trigger.sql))
|
|
105
|
+
noUpdate.add(trigger.tbl_name);
|
|
106
|
+
if (/\bBEFORE\s+DELETE\b/i.test(trigger.sql))
|
|
107
|
+
noDelete.add(trigger.tbl_name);
|
|
108
|
+
}
|
|
109
|
+
return { noUpdate, noDelete };
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Child tables whose rows a worktree actually owns — everything
|
|
113
|
+
* getWorktreeChildTables reports, minus the append-only ledgers that outlive it.
|
|
114
|
+
* This is the set a delete or an orphan sweep may touch (Issue #1621).
|
|
115
|
+
*/
|
|
116
|
+
function getDeletableWorktreeChildTables(db) {
|
|
117
|
+
const { noDelete } = getWriteGuardedTables(db);
|
|
118
|
+
return getWorktreeChildTables(db).filter(({ table }) => !noDelete.has(table));
|
|
119
|
+
}
|
|
@@ -13,13 +13,14 @@
|
|
|
13
13
|
* failure `verification-db.ts` guards against.
|
|
14
14
|
*/
|
|
15
15
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
16
|
-
exports.ACTIVE_TASK_STATUSES = exports.TERMINAL_TASK_STATUSES = exports.TASK_STATUSES = void 0;
|
|
16
|
+
exports.VERIFIABLE_TASK_STATUSES = exports.ACTIVE_TASK_STATUSES = exports.TERMINAL_TASK_STATUSES = exports.TASK_STATUSES = void 0;
|
|
17
17
|
exports.isTerminalTaskStatus = isTerminalTaskStatus;
|
|
18
18
|
exports.createTask = createTask;
|
|
19
19
|
exports.getTask = getTask;
|
|
20
20
|
exports.listTasks = listTasks;
|
|
21
21
|
exports.updateTaskStatus = updateTaskStatus;
|
|
22
22
|
exports.getActiveTask = getActiveTask;
|
|
23
|
+
exports.getVerifiableTask = getVerifiableTask;
|
|
23
24
|
exports.getActiveTaskForInstance = getActiveTaskForInstance;
|
|
24
25
|
const crypto_1 = require("crypto");
|
|
25
26
|
/**
|
|
@@ -61,6 +62,27 @@ exports.ACTIVE_TASK_STATUSES = [
|
|
|
61
62
|
'waiting_input',
|
|
62
63
|
'verifying',
|
|
63
64
|
];
|
|
65
|
+
/**
|
|
66
|
+
* Statuses a verification run that has to *discover* its task may attach to.
|
|
67
|
+
*
|
|
68
|
+
* The active three plus the two the state machine explicitly reopens:
|
|
69
|
+
* `transitionTask` accepts `verify_started` from `failed` and `not_started`
|
|
70
|
+
* because a task can fail on one run and pass on the retry. Resolving only the
|
|
71
|
+
* active three meant that reopening was reachable only by a caller that already
|
|
72
|
+
* knew the task id — so the retry every agent performs after a red gate found
|
|
73
|
+
* no contract, and with it no scope to judge (#1620).
|
|
74
|
+
*
|
|
75
|
+
* `succeeded` and `cancelled` stay out: those are closed for good, and a run
|
|
76
|
+
* that silently adopted one would be judging work against a verdict already
|
|
77
|
+
* given. {@link ACTIVE_TASK_STATUSES} keeps its narrower meaning for the
|
|
78
|
+
* callers that ask "what is this agent working on right now" (Auto-Yes, prompt
|
|
79
|
+
* events), where a finished task must not answer.
|
|
80
|
+
*/
|
|
81
|
+
exports.VERIFIABLE_TASK_STATUSES = [
|
|
82
|
+
...exports.ACTIVE_TASK_STATUSES,
|
|
83
|
+
'failed',
|
|
84
|
+
'not_started',
|
|
85
|
+
];
|
|
64
86
|
function isTerminalTaskStatus(status) {
|
|
65
87
|
return exports.TERMINAL_TASK_STATUSES.includes(status);
|
|
66
88
|
}
|
|
@@ -185,6 +207,29 @@ function getActiveTask(db, worktreeId, cliToolId, instanceId) {
|
|
|
185
207
|
.get(...params, ...exports.ACTIVE_TASK_STATUSES);
|
|
186
208
|
return row ? mapTaskRow(row) : null;
|
|
187
209
|
}
|
|
210
|
+
/**
|
|
211
|
+
* The task a verification run should attach to when the caller cannot name one.
|
|
212
|
+
*
|
|
213
|
+
* Worktree-scoped for the same reason {@link getActiveTask} is: the gates run
|
|
214
|
+
* over the whole working tree, so there is nothing finer to narrow by. Ordered
|
|
215
|
+
* like {@link getActiveTask} — most recently updated first, insertion order
|
|
216
|
+
* breaking a same-millisecond tie — so the answer is the last task to have
|
|
217
|
+
* moved, not whichever row SQLite happened to reach first.
|
|
218
|
+
*
|
|
219
|
+
* @see VERIFIABLE_TASK_STATUSES for why this is wider than "active"
|
|
220
|
+
*/
|
|
221
|
+
function getVerifiableTask(db, worktreeId) {
|
|
222
|
+
const placeholders = exports.VERIFIABLE_TASK_STATUSES.map(() => '?').join(', ');
|
|
223
|
+
const row = db
|
|
224
|
+
.prepare(`
|
|
225
|
+
SELECT ${TASK_COLUMNS} FROM tasks
|
|
226
|
+
WHERE worktree_id = ? AND status IN (${placeholders})
|
|
227
|
+
ORDER BY updated_at DESC, created_at DESC, rowid DESC
|
|
228
|
+
LIMIT 1
|
|
229
|
+
`)
|
|
230
|
+
.get(worktreeId, ...exports.VERIFIABLE_TASK_STATUSES);
|
|
231
|
+
return row ? mapTaskRow(row) : null;
|
|
232
|
+
}
|
|
188
233
|
/**
|
|
189
234
|
* The active task governing one agent instance, or null.
|
|
190
235
|
*
|