commandmate 0.41.1 → 0.42.1
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 +134 -134
- package/.next/app-path-routes-manifest.json +24 -24
- package/.next/build-manifest.json +2 -2
- package/.next/prerender-manifest.json +9 -9
- 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/agents/update/route.js +1 -1
- package/.next/server/app/api/agents/update/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/agents/versions/route.js +1 -1
- package/.next/server/app/api/agents/versions/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/app/release-notes/route_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/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/capabilities/route.js +1 -1
- package/.next/server/app/api/capabilities/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.js +1 -1
- package/.next/server/app/api/hooks/agent-event/route.js.nft.json +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.js +2 -2
- package/.next/server/app/api/hooks/claude-done/route.js.nft.json +1 -1
- package/.next/server/app/api/hooks/claude-done/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/hooks/permission-request/route.js +1 -1
- package/.next/server/app/api/hooks/permission-request/route.js.nft.json +1 -1
- package/.next/server/app/api/hooks/permission-request/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/escalation/route_client-reference-manifest.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/relays/[relayId]/cancel/route.js +1 -1
- package/.next/server/app/api/relays/[relayId]/cancel/route.js.nft.json +1 -1
- package/.next/server/app/api/relays/[relayId]/cancel/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/relays/route.js +1 -1
- package/.next/server/app/api/relays/route.js.nft.json +1 -1
- package/.next/server/app/api/relays/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/remote/pair/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/clone/[jobId]/route.js +1 -1
- package/.next/server/app/api/repositories/clone/[jobId]/route.js.nft.json +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.js +1 -1
- package/.next/server/app/api/repositories/clone/route.js.nft.json +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.js +1 -1
- package/.next/server/app/api/repositories/restore/route.js.nft.json +1 -1
- package/.next/server/app/api/repositories/restore/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/repositories/route.js +1 -1
- package/.next/server/app/api/repositories/route.js.nft.json +1 -1
- package/.next/server/app/api/repositories/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/repositories/scan/route.js +1 -1
- package/.next/server/app/api/repositories/scan/route.js.nft.json +1 -1
- package/.next/server/app/api/repositories/scan/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/repositories/sync/route.js +1 -1
- package/.next/server/app/api/repositories/sync/route.js.nft.json +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/settings/default-agents/route.js +1 -1
- package/.next/server/app/api/settings/default-agents/route.js.nft.json +1 -1
- package/.next/server/app/api/settings/default-agents/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/settings/default-surface-mode/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.js.nft.json +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_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/verification/runs/[runId]/route_client-reference-manifest.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.js.nft.json +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.js +1 -1
- package/.next/server/app/api/worktrees/[id]/capture/route.js.nft.json +1 -1
- package/.next/server/app/api/worktrees/[id]/capture/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/clear-composer/route.js +1 -1
- package/.next/server/app/api/worktrees/[id]/clear-composer/route.js.nft.json +1 -1
- package/.next/server/app/api/worktrees/[id]/clear-composer/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/cli-reference/route_client-reference-manifest.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_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/current-output/route.js +1 -1
- package/.next/server/app/api/worktrees/[id]/current-output/route.js.nft.json +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]/direct-input/route.js +1 -1
- package/.next/server/app/api/worktrees/[id]/direct-input/route.js.nft.json +1 -1
- package/.next/server/app/api/worktrees/[id]/direct-input/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/env/route_client-reference-manifest.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_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_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_client-reference-manifest.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_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_client-reference-manifest.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_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_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_client-reference-manifest.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_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_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/instances/notes/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/instances/opencode/route.js.nft.json +1 -1
- package/.next/server/app/api/worktrees/[id]/instances/opencode/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/interrupt/route.js +1 -1
- package/.next/server/app/api/worktrees/[id]/interrupt/route.js.nft.json +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.js +1 -1
- package/.next/server/app/api/worktrees/[id]/kill-session/route.js.nft.json +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_client-reference-manifest.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_client-reference-manifest.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_client-reference-manifest.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]/opencode/diff/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/opencode/session/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/opencode/share/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/prompt-response/route.js +1 -1
- package/.next/server/app/api/worktrees/[id]/prompt-response/route.js.nft.json +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]/resolve-target/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/respond/route.js +1 -1
- package/.next/server/app/api/worktrees/[id]/respond/route.js.nft.json +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.js +103 -1
- package/.next/server/app/api/worktrees/[id]/route.js.nft.json +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_client-reference-manifest.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_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 +103 -1
- package/.next/server/app/api/worktrees/[id]/send/route.js.nft.json +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.js +1 -103
- package/.next/server/app/api/worktrees/[id]/skills/[skillId]/git-workflow/route.js.nft.json +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/[skillId]/update/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/skills/[skillId]/update-plan/route_client-reference-manifest.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.nft.json +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.js +1 -1
- package/.next/server/app/api/worktrees/[id]/special-keys/route.js.nft.json +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]/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.js +1 -103
- package/.next/server/app/api/worktrees/[id]/terminal/route.js.nft.json +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_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/timers/route.js +1 -1
- package/.next/server/app/api/worktrees/[id]/timers/route.js.nft.json +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_client-reference-manifest.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_client-reference-manifest.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/config/route.js +1 -1
- package/.next/server/app/api/worktrees/[id]/verify/config/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]/cancel/route.js +1 -1
- package/.next/server/app/api/worktrees/[id]/verify/runs/[runId]/cancel/route_client-reference-manifest.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_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/viewed/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/apply-default-agents/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/route.js +1 -1
- package/.next/server/app/api/worktrees/route.js.nft.json +1 -1
- package/.next/server/app/api/worktrees/route_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 +9 -9
- 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 +24 -24
- package/.next/server/chunks/1068.js +1 -0
- package/.next/server/chunks/1210.js +1 -1
- package/.next/server/chunks/1501.js +5 -5
- package/.next/server/chunks/1990.js +103 -0
- package/.next/server/chunks/2119.js +1 -1
- package/.next/server/chunks/2404.js +1 -1
- package/.next/server/chunks/2445.js +1 -1
- package/.next/server/chunks/3241.js +1 -1
- package/.next/server/chunks/3707.js +1 -1
- package/.next/server/chunks/3718.js +1 -0
- package/.next/server/chunks/434.js +1 -1
- package/.next/server/chunks/4443.js +1 -1
- package/.next/server/chunks/5164.js +1 -1
- package/.next/server/chunks/5775.js +1 -0
- package/.next/server/chunks/611.js +1 -0
- package/.next/server/chunks/6130.js +1 -1
- package/.next/server/chunks/650.js +1 -1
- package/.next/server/chunks/6781.js +1 -1
- package/.next/server/chunks/7905.js +1 -1
- package/.next/server/chunks/8581.js +1 -1
- package/.next/server/chunks/8583.js +1 -1
- package/.next/server/chunks/8673.js +2 -2
- package/.next/server/chunks/8728.js +3 -3
- package/.next/server/chunks/8791.js +1 -0
- package/.next/server/chunks/8800.js +1 -1
- package/.next/server/chunks/8918.js +1 -103
- package/.next/server/chunks/9130.js +2 -2
- package/.next/server/chunks/964.js +1 -1
- package/.next/server/chunks/9731.js +3 -3
- package/.next/server/chunks/9882.js +3 -3
- 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/{2407-7bd75de2f6be3692.js → 2407-21fbf12d753b8d4d.js} +1 -1
- package/.next/static/chunks/7705-2a2936551b0cf9a3.js +1 -0
- package/.next/static/chunks/8165-934ee7ff3b4ddfd4.js +1 -0
- package/.next/static/chunks/app/{layout-51fa91ea833c97fc.js → layout-8ff4c7c08ef1f621.js} +1 -1
- package/.next/static/chunks/app/worktrees/[id]/page-3c946bd2875391fd.js +1 -0
- package/.next/static/css/{974314e796232f53.css → b3f433d52b8ed5c8.css} +1 -1
- package/.next/trace +13 -13
- package/dist/cli/commands/attach.d.ts +6 -4
- package/dist/cli/commands/attach.d.ts.map +1 -1
- package/dist/cli/commands/attach.js +36 -5
- package/dist/cli/commands/instances.d.ts.map +1 -1
- package/dist/cli/commands/instances.js +5 -0
- package/dist/cli/commands/ls.d.ts.map +1 -1
- package/dist/cli/commands/ls.js +19 -9
- package/dist/cli/commands/whoami.d.ts +9 -25
- package/dist/cli/commands/whoami.d.ts.map +1 -1
- package/dist/cli/commands/whoami.js +18 -42
- package/dist/cli/types/api-responses.d.ts +37 -0
- package/dist/cli/types/api-responses.d.ts.map +1 -1
- package/dist/lib/cli-tools/session-name.d.ts +72 -4
- package/dist/lib/cli-tools/session-name.d.ts.map +1 -1
- package/dist/lib/cli-tools/session-name.js +115 -12
- package/dist/lib/detection/tools/verified-against.d.ts +11 -2
- package/dist/lib/detection/tools/verified-against.d.ts.map +1 -1
- package/dist/lib/detection/tools/verified-against.js +11 -2
- package/dist/lib/session/tmux-session-surface.d.ts +9 -7
- package/dist/lib/session/tmux-session-surface.d.ts.map +1 -1
- package/dist/lib/session/tmux-session-surface.js +15 -8
- package/dist/lib/tmux/legacy-session-alias.d.ts +43 -0
- package/dist/lib/tmux/legacy-session-alias.d.ts.map +1 -0
- package/dist/lib/tmux/legacy-session-alias.js +64 -0
- package/dist/lib/verification/verify-config.d.ts +21 -0
- package/dist/lib/verification/verify-config.d.ts.map +1 -1
- package/dist/lib/verification/verify-config.js +54 -1
- package/dist/server/locales/en/worktree.json +60 -17
- package/dist/server/locales/ja/worktree.json +63 -20
- package/dist/server/server.js +29 -0
- package/dist/server/src/lib/auto-yes-poller.js +15 -0
- package/dist/server/src/lib/cli-tools/antigravity.js +1 -1
- package/dist/server/src/lib/cli-tools/base.js +10 -1
- package/dist/server/src/lib/cli-tools/codex.js +1 -1
- package/dist/server/src/lib/cli-tools/command-code.js +1 -1
- package/dist/server/src/lib/cli-tools/copilot.js +1 -1
- package/dist/server/src/lib/cli-tools/gemini.js +1 -1
- package/dist/server/src/lib/cli-tools/legacy-session-adoption.js +19 -0
- package/dist/server/src/lib/cli-tools/opencode.js +1 -1
- package/dist/server/src/lib/cli-tools/session-name.js +115 -12
- package/dist/server/src/lib/cli-tools/session-namespace.js +55 -0
- package/dist/server/src/lib/cli-tools/session-ownership.js +21 -0
- package/dist/server/src/lib/cli-tools/vibe-local.js +1 -1
- package/dist/server/src/lib/cli-tools/worktree-session-ownership.js +28 -0
- package/dist/server/src/lib/db/app-settings-db.js +29 -0
- package/dist/server/src/lib/detection/cli-patterns.js +51 -10
- package/dist/server/src/lib/detection/tools/codex/cli-patterns.js +8 -2
- package/dist/server/src/lib/detection/tools/codex/detect.js +8 -1
- package/dist/server/src/lib/detection/tools/codex/prompt.js +6 -2
- package/dist/server/src/lib/detection/tools/verified-against.js +11 -2
- package/dist/server/src/lib/hooks/sources/codex/hooks-config.js +77 -2
- package/dist/server/src/lib/polling/auto-yes-dialog-gate.js +77 -1
- package/dist/server/src/lib/realtime/terminal-broadcast.js +11 -0
- package/dist/server/src/lib/session/adopt-legacy-sessions.js +89 -0
- package/dist/server/src/lib/session/claude-session.js +13 -10
- package/dist/server/src/lib/session/current-output-builder.js +18 -0
- package/dist/server/src/lib/session/send-user-message.js +26 -0
- package/dist/server/src/lib/session/session-instance-targets.js +53 -0
- package/dist/server/src/lib/session/tmux-session-surface.js +15 -8
- package/dist/server/src/lib/session/worktree-session-reconcile.js +101 -56
- package/dist/server/src/lib/session-cleanup.js +33 -3
- package/dist/server/src/lib/tmux/legacy-session-alias.js +64 -0
- package/dist/server/src/lib/tmux/session-hook-scripts.js +12 -4
- package/dist/server/src/lib/tmux/session-ownership.js +174 -0
- package/dist/server/src/lib/tmux/tmux.js +13 -3
- package/dist/server/src/lib/verification/env-clean-gate.js +46 -14
- package/dist/server/src/lib/verification/gate-runner.js +1 -0
- package/dist/server/src/lib/verification/verify-config.js +54 -1
- package/dist/server/src/lib/ws-server.js +11 -0
- package/package.json +1 -1
- package/release-notes/0.42.0.json +62 -0
- package/release-notes/0.42.1.json +16 -0
- package/.next/server/chunks/3323.js +0 -1
- package/.next/server/chunks/7075.js +0 -1
- package/.next/server/chunks/9080.js +0 -1
- package/.next/static/chunks/7705-25076b57224da370.js +0 -1
- package/.next/static/chunks/8165-d3b7c8a7aa776bc7.js +0 -1
- package/.next/static/chunks/app/worktrees/[id]/page-1e6300dd50cd9a4d.js +0 -1
- /package/.next/static/{_fBjTkjoWfOqdjl2SeMIN → 3M_S0B7dhEDdVE2XSJz1Y}/_buildManifest.js +0 -0
- /package/.next/static/{_fBjTkjoWfOqdjl2SeMIN → 3M_S0B7dhEDdVE2XSJz1Y}/_ssgManifest.js +0 -0
|
@@ -74,11 +74,15 @@ exports.reconcileWorktreeSessions = reconcileWorktreeSessions;
|
|
|
74
74
|
exports.reconcileWorktreeSessionsFromAliases = reconcileWorktreeSessionsFromAliases;
|
|
75
75
|
const manager_1 = require("../../lib/cli-tools/manager");
|
|
76
76
|
const types_1 = require("../../lib/cli-tools/types");
|
|
77
|
-
const validation_1 = require("../../lib/cli-tools/validation");
|
|
78
77
|
const agent_instances_db_1 = require("../../lib/db/agent-instances-db");
|
|
78
|
+
const validation_1 = require("../../lib/cli-tools/validation");
|
|
79
|
+
const session_name_1 = require("../../lib/cli-tools/session-name");
|
|
80
|
+
const session_namespace_1 = require("../../lib/cli-tools/session-namespace");
|
|
81
|
+
const session_instance_targets_1 = require("./session-instance-targets");
|
|
79
82
|
const worktree_alias_db_1 = require("../../lib/db/worktree-alias-db");
|
|
80
83
|
const path_validator_1 = require("../../lib/security/path-validator");
|
|
81
84
|
const tmux_1 = require("../../lib/tmux/tmux");
|
|
85
|
+
const session_ownership_1 = require("../../lib/tmux/session-ownership");
|
|
82
86
|
const control_mode_tmux_transport_1 = require("../../lib/tmux/control-mode-tmux-transport");
|
|
83
87
|
const auto_yes_manager_1 = require("../../lib/polling/auto-yes-manager");
|
|
84
88
|
const response_poller_1 = require("../../lib/polling/response-poller");
|
|
@@ -131,44 +135,16 @@ function normalizeRenames(renames) {
|
|
|
131
135
|
* The roster is read for BOTH IDs because the caller may reconcile either side
|
|
132
136
|
* of the DB move: after `migrateWorktreeIdPreservingChildren` the rows sit under
|
|
133
137
|
* `newId`, but a caller reconciling ahead of the move still finds them under
|
|
134
|
-
* `oldId`.
|
|
135
|
-
* worktree that predates the roster (#1000) has no `agent_instances` rows at all
|
|
136
|
-
* yet can absolutely have a running `mcbd-claude-<id>` session.
|
|
138
|
+
* `oldId`.
|
|
137
139
|
*/
|
|
138
|
-
function
|
|
139
|
-
|
|
140
|
-
const targets = [];
|
|
141
|
-
const add = (cliToolId, instanceId) => {
|
|
142
|
-
const key = `${cliToolId}|${instanceId}`;
|
|
143
|
-
if (seen.has(key))
|
|
144
|
-
return;
|
|
145
|
-
seen.add(key);
|
|
146
|
-
targets.push({ cliToolId, instanceId });
|
|
147
|
-
};
|
|
148
|
-
for (const cliToolId of types_1.CLI_TOOL_IDS)
|
|
149
|
-
add(cliToolId, cliToolId);
|
|
150
|
-
for (const worktreeId of [rename.newId, rename.oldId]) {
|
|
151
|
-
let instances;
|
|
152
|
-
try {
|
|
153
|
-
instances = (0, agent_instances_db_1.getAgentInstances)(db, worktreeId);
|
|
154
|
-
}
|
|
155
|
-
catch {
|
|
156
|
-
// No agent_instances table (older database) — the primary instances above
|
|
157
|
-
// already cover the pre-roster shape.
|
|
158
|
-
continue;
|
|
159
|
-
}
|
|
160
|
-
for (const instance of instances) {
|
|
161
|
-
if ((0, types_1.isCliToolType)(instance.cliTool))
|
|
162
|
-
add(instance.cliTool, instance.id);
|
|
163
|
-
}
|
|
164
|
-
}
|
|
165
|
-
return targets;
|
|
140
|
+
function collectRenameInstanceTargets(db, rename) {
|
|
141
|
+
return (0, session_instance_targets_1.collectInstanceTargets)(db, [rename.newId, rename.oldId]);
|
|
166
142
|
}
|
|
167
143
|
/** Build the (old session name → new session name) plan for one ID move. */
|
|
168
144
|
function planSessionRenames(db, rename, errors) {
|
|
169
145
|
const manager = manager_1.CLIToolManager.getInstance();
|
|
170
146
|
const plans = [];
|
|
171
|
-
for (const { cliToolId, instanceId } of
|
|
147
|
+
for (const { cliToolId, instanceId } of collectRenameInstanceTargets(db, rename)) {
|
|
172
148
|
try {
|
|
173
149
|
const tool = manager.getTool(cliToolId);
|
|
174
150
|
const oldName = tool.getSessionName(rename.oldId, instanceId);
|
|
@@ -243,27 +219,33 @@ function collectKnownWorktreeIds(db, renames) {
|
|
|
243
219
|
* @param knownIds - Every worktree ID the database can vouch for
|
|
244
220
|
* @returns The attribution, or `null` when nothing in `knownIds` explains the name
|
|
245
221
|
*/
|
|
246
|
-
function attributeSessionName(name, knownIds) {
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
const
|
|
261
|
-
|
|
262
|
-
return null;
|
|
263
|
-
candidate = candidate.slice(0, cut);
|
|
222
|
+
function attributeSessionName(name, knownIds, namespace = (0, session_namespace_1.getSessionNamespace)()) {
|
|
223
|
+
// Issue #2866: legacy names and names in this server's namespace only. A name
|
|
224
|
+
// in another namespace is another server's session, whatever ID it carries.
|
|
225
|
+
const parsed = (0, session_name_1.parseSessionName)(name);
|
|
226
|
+
if (!parsed)
|
|
227
|
+
return null;
|
|
228
|
+
if (parsed.namespace !== null && parsed.namespace !== namespace)
|
|
229
|
+
return null;
|
|
230
|
+
// No CLI tool id is a prefix of another, so the remainder is unambiguously
|
|
231
|
+
// `<id>[-<suffix>]`.
|
|
232
|
+
const { cliToolId, rest } = parsed;
|
|
233
|
+
let candidate = rest;
|
|
234
|
+
for (;;) {
|
|
235
|
+
if (knownIds.has(candidate)) {
|
|
236
|
+
const suffix = candidate.length === rest.length ? undefined : rest.slice(candidate.length + 1);
|
|
237
|
+
return { cliToolId, worktreeId: candidate, suffix: suffix || undefined };
|
|
264
238
|
}
|
|
239
|
+
const cut = candidate.lastIndexOf('-');
|
|
240
|
+
if (cut <= 0)
|
|
241
|
+
return null;
|
|
242
|
+
candidate = candidate.slice(0, cut);
|
|
265
243
|
}
|
|
266
|
-
|
|
244
|
+
}
|
|
245
|
+
/** Whether a live name is another server's namespaced session (Issue #2866). */
|
|
246
|
+
function isOtherNamespaceSession(name, namespace) {
|
|
247
|
+
const parsed = (0, session_name_1.parseSessionName)(name);
|
|
248
|
+
return parsed !== null && parsed.namespace !== null && parsed.namespace !== namespace;
|
|
267
249
|
}
|
|
268
250
|
/**
|
|
269
251
|
* Read the live session list the other way round: attribute each name to a
|
|
@@ -277,10 +259,14 @@ function attributeSessionName(name, knownIds) {
|
|
|
277
259
|
function discoverSessionRenames(renames, liveNames, knownIds, result) {
|
|
278
260
|
const byOldId = new Map(renames.map((rename) => [rename.oldId, rename]));
|
|
279
261
|
const plans = [];
|
|
262
|
+
const namespace = (0, session_namespace_1.getSessionNamespace)();
|
|
280
263
|
for (const name of liveNames) {
|
|
281
264
|
if (!name.startsWith('mcbd-'))
|
|
282
265
|
continue;
|
|
283
|
-
|
|
266
|
+
// Issue #2866: another server's namespace — neither attributed nor reported.
|
|
267
|
+
if (isOtherNamespaceSession(name, namespace))
|
|
268
|
+
continue;
|
|
269
|
+
const attributed = attributeSessionName(name, knownIds, namespace);
|
|
284
270
|
if (!attributed) {
|
|
285
271
|
result.unaccountedSessions.push(name);
|
|
286
272
|
continue;
|
|
@@ -291,7 +277,8 @@ function discoverSessionRenames(renames, liveNames, knownIds, result) {
|
|
|
291
277
|
try {
|
|
292
278
|
const tool = manager_1.CLIToolManager.getInstance().getTool(attributed.cliToolId);
|
|
293
279
|
// Rebuilt through getSessionName rather than by string surgery so the
|
|
294
|
-
// suffix convention stays owned by one place (`cli-tools/
|
|
280
|
+
// suffix convention stays owned by one place (`cli-tools/session-name.ts`).
|
|
281
|
+
// A legacy-form source lands on the current form (Issue #2866).
|
|
295
282
|
const instanceId = attributed.suffix
|
|
296
283
|
? (0, types_1.buildInstanceId)(attributed.cliToolId, attributed.suffix)
|
|
297
284
|
: attributed.cliToolId;
|
|
@@ -322,6 +309,58 @@ function mergeSessionPlans(predicted, discovered) {
|
|
|
322
309
|
}
|
|
323
310
|
return Array.from(byOldName.values());
|
|
324
311
|
}
|
|
312
|
+
/**
|
|
313
|
+
* The path of a worktree row, or null. Queried directly for the same reason
|
|
314
|
+
* {@link collectKnownWorktreeIds} is (suites mock `worktree-db`).
|
|
315
|
+
*/
|
|
316
|
+
function readWorktreePath(db, worktreeId) {
|
|
317
|
+
try {
|
|
318
|
+
const row = db.prepare('SELECT path FROM worktrees WHERE id = ?').get(worktreeId);
|
|
319
|
+
return row?.path ?? null;
|
|
320
|
+
}
|
|
321
|
+
catch {
|
|
322
|
+
return null;
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
/**
|
|
326
|
+
* Drop the plans whose live source session another CommandMate server created
|
|
327
|
+
* (Issue #2865).
|
|
328
|
+
*
|
|
329
|
+
* Session names carry no server identity, so a live `mcbd-claude-<old>` may be
|
|
330
|
+
* a different server's session that merely shares the worktree ID. Only a
|
|
331
|
+
* session whose `#{session_path}` is the worktree's own directory is renamed;
|
|
332
|
+
* the rest are reported in `skippedSessions`. The directory comes from the
|
|
333
|
+
* rename target's row, falling back to the source's row when the pass runs
|
|
334
|
+
* ahead of the DB move. A plan whose worktree has no row at all cannot be
|
|
335
|
+
* vouched for and is skipped too (fail safe).
|
|
336
|
+
*/
|
|
337
|
+
function excludeForeignSessionPlans(db, plans, live, livePaths, deps, result) {
|
|
338
|
+
const owned = [];
|
|
339
|
+
for (const plan of plans) {
|
|
340
|
+
// Not live: renameSessionsTwoStage drops it without counting it.
|
|
341
|
+
if (!live.has(plan.oldName)) {
|
|
342
|
+
owned.push(plan);
|
|
343
|
+
continue;
|
|
344
|
+
}
|
|
345
|
+
const worktreePath = readWorktreePath(db, plan.rename.newId) ?? readWorktreePath(db, plan.rename.oldId);
|
|
346
|
+
const sessionPath = livePaths.get(plan.oldName) ?? null;
|
|
347
|
+
if (worktreePath !== null && deps.isSessionOwnedBy(sessionPath, worktreePath)) {
|
|
348
|
+
owned.push(plan);
|
|
349
|
+
continue;
|
|
350
|
+
}
|
|
351
|
+
result.skippedSessions.push({
|
|
352
|
+
oldName: plan.oldName,
|
|
353
|
+
newName: plan.newName,
|
|
354
|
+
reason: 'session owned by another server',
|
|
355
|
+
});
|
|
356
|
+
logger.warn('reconcile:foreign-session-skipped', {
|
|
357
|
+
sessionName: plan.oldName,
|
|
358
|
+
sessionPath,
|
|
359
|
+
worktreePath,
|
|
360
|
+
});
|
|
361
|
+
}
|
|
362
|
+
return owned;
|
|
363
|
+
}
|
|
325
364
|
/** Mint a session name that is free both on the server and within this pass. */
|
|
326
365
|
function allocateTempName(taken, ordinal) {
|
|
327
366
|
let candidate = `${TEMP_SESSION_PREFIX}${ordinal}`;
|
|
@@ -501,13 +540,18 @@ async function reconcileWorktreeSessions(db, renamesOrOldId, newIdOrOptions, may
|
|
|
501
540
|
const deps = {
|
|
502
541
|
listSessions: options?.tmux?.listSessions ?? tmux_1.listSessions,
|
|
503
542
|
renameSession: options?.tmux?.renameSession ?? tmux_1.renameSession,
|
|
543
|
+
isSessionOwnedBy: options?.tmux?.isSessionOwnedBy ?? (0, session_ownership_1.createCachedOwnershipMatcher)(),
|
|
504
544
|
};
|
|
505
545
|
// Listed once, up front: both the prediction pass and the attribution pass
|
|
506
546
|
// need it, and it is what makes a startup with nothing stale cost exactly one
|
|
507
547
|
// `tmux list-sessions`.
|
|
508
548
|
let live = null;
|
|
549
|
+
const livePaths = new Map();
|
|
509
550
|
try {
|
|
510
|
-
|
|
551
|
+
const sessions = await deps.listSessions();
|
|
552
|
+
live = new Set(sessions.map((session) => session.name));
|
|
553
|
+
for (const session of sessions)
|
|
554
|
+
livePaths.set(session.name, session.path);
|
|
511
555
|
}
|
|
512
556
|
catch (error) {
|
|
513
557
|
result.errors.push(`list tmux sessions: ${error instanceof Error ? error.message : String(error)}`);
|
|
@@ -527,7 +571,8 @@ async function reconcileWorktreeSessions(db, renamesOrOldId, newIdOrOptions, may
|
|
|
527
571
|
else
|
|
528
572
|
result.planSources.discovered++;
|
|
529
573
|
}
|
|
530
|
-
|
|
574
|
+
const ownedPlans = excludeForeignSessionPlans(db, plans, live, livePaths, deps, result);
|
|
575
|
+
await renameSessionsTwoStage(ownedPlans, live, deps, result);
|
|
531
576
|
}
|
|
532
577
|
// Runtime state moves after the sessions: a poller that ticks in between
|
|
533
578
|
// fails one capture against a name that is briefly gone, which is a logged
|
|
@@ -12,6 +12,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
12
12
|
exports.cleanupWorktreeSessions = cleanupWorktreeSessions;
|
|
13
13
|
exports.cleanupMultipleWorktrees = cleanupMultipleWorktrees;
|
|
14
14
|
exports.killWorktreeSession = killWorktreeSession;
|
|
15
|
+
exports.createOwnedSessionKiller = createOwnedSessionKiller;
|
|
15
16
|
exports.cleanupGlobalSessions = cleanupGlobalSessions;
|
|
16
17
|
exports.syncWorktreesAndCleanup = syncWorktreesAndCleanup;
|
|
17
18
|
const response_poller_1 = require("./polling/response-poller");
|
|
@@ -27,6 +28,8 @@ const logger_1 = require("../lib/logger");
|
|
|
27
28
|
const manager_1 = require("./cli-tools/manager");
|
|
28
29
|
const tmux_1 = require("./tmux/tmux");
|
|
29
30
|
const worktrees_1 = require("./git/worktrees");
|
|
31
|
+
const worktree_db_1 = require("./db/worktree-db");
|
|
32
|
+
const session_ownership_1 = require("./cli-tools/session-ownership");
|
|
30
33
|
const logger = (0, logger_1.createLogger)('session-cleanup');
|
|
31
34
|
/**
|
|
32
35
|
* Clean up all CLI tool sessions and pollers for a single worktree
|
|
@@ -164,26 +167,50 @@ async function cleanupMultipleWorktrees(worktreeIds, killSessionFn) {
|
|
|
164
167
|
* MF-C01: getTool() throws Error if tool not found, wrapped in try-catch
|
|
165
168
|
* SF-004: isRunning() correctly awaited
|
|
166
169
|
*
|
|
170
|
+
* Issue #2865: only a session created in `worktreePath` is killed. A
|
|
171
|
+
* same-named session another CommandMate server created (the worktree ID is
|
|
172
|
+
* derived from the directory name, so two servers can share it) is left alone
|
|
173
|
+
* and `false` is returned.
|
|
174
|
+
*
|
|
167
175
|
* @param worktreeId - Worktree ID
|
|
168
176
|
* @param cliToolId - CLI tool type
|
|
177
|
+
* @param worktreePath - The worktree's directory, read from its row BEFORE the row is deleted
|
|
169
178
|
* @returns true if session was killed, false otherwise
|
|
170
179
|
*/
|
|
171
|
-
async function killWorktreeSession(worktreeId, cliToolId) {
|
|
180
|
+
async function killWorktreeSession(worktreeId, cliToolId, worktreePath) {
|
|
172
181
|
try {
|
|
173
182
|
const manager = manager_1.CLIToolManager.getInstance();
|
|
174
183
|
const tool = manager.getTool(cliToolId); // throws Error if not found
|
|
175
184
|
if (!await tool.isRunning(worktreeId))
|
|
176
185
|
return false; // SF-004: await
|
|
186
|
+
const sessionName = tool.getSessionName(worktreeId);
|
|
187
|
+
const ownership = await (0, session_ownership_1.checkSessionOwnership)(sessionName, worktreePath);
|
|
188
|
+
if (ownership.verdict !== 'owned')
|
|
189
|
+
return false;
|
|
177
190
|
// Issue #565: Clear prompt dedup cache on session kill
|
|
178
191
|
const pollerKey = `${worktreeId}:${cliToolId}`;
|
|
179
192
|
(0, response_poller_1.clearPromptHashCache)(pollerKey);
|
|
180
|
-
const sessionName = tool.getSessionName(worktreeId);
|
|
181
193
|
return (0, tmux_1.killSession)(sessionName);
|
|
182
194
|
}
|
|
183
195
|
catch {
|
|
184
196
|
return false;
|
|
185
197
|
}
|
|
186
198
|
}
|
|
199
|
+
/**
|
|
200
|
+
* A {@link KillSessionFn} that kills through {@link killWorktreeSession} with
|
|
201
|
+
* each worktree's pre-deletion path (Issue #2865). A worktree with no known
|
|
202
|
+
* path cannot be vouched for, so nothing is killed for it.
|
|
203
|
+
*
|
|
204
|
+
* @param pathById - worktree ID -> path, captured before the rows are deleted
|
|
205
|
+
*/
|
|
206
|
+
function createOwnedSessionKiller(pathById) {
|
|
207
|
+
return async (worktreeId, cliToolId) => {
|
|
208
|
+
const worktreePath = pathById.get(worktreeId);
|
|
209
|
+
if (worktreePath === undefined)
|
|
210
|
+
return false;
|
|
211
|
+
return killWorktreeSession(worktreeId, cliToolId, worktreePath);
|
|
212
|
+
};
|
|
213
|
+
}
|
|
187
214
|
/**
|
|
188
215
|
* Clean up all global assistant sessions.
|
|
189
216
|
* Issue #649: Kill any orphaned mcbd-{cli_tool_id}-__global__ tmux sessions
|
|
@@ -233,6 +260,9 @@ async function cleanupGlobalSessions() {
|
|
|
233
260
|
* @returns SyncResult and sanitized cleanupWarnings
|
|
234
261
|
*/
|
|
235
262
|
async function syncWorktreesAndCleanup(db, worktrees) {
|
|
263
|
+
// Issue #2865: the rows sync deletes are gone afterwards, and their paths are
|
|
264
|
+
// what decides which sessions are this server's to kill.
|
|
265
|
+
const pathById = new Map((0, worktree_db_1.getAllWorktreePathIds)(db).map((row) => [row.id, row.path]));
|
|
236
266
|
const syncResult = (0, worktrees_1.syncWorktreesToDB)(db, worktrees);
|
|
237
267
|
// Issue #649: Clean up orphaned global assistant sessions
|
|
238
268
|
try {
|
|
@@ -244,7 +274,7 @@ async function syncWorktreesAndCleanup(db, worktrees) {
|
|
|
244
274
|
let cleanupWarnings = [];
|
|
245
275
|
if (syncResult.deletedIds.length > 0) {
|
|
246
276
|
try {
|
|
247
|
-
const cleanupResult = await cleanupMultipleWorktrees(syncResult.deletedIds,
|
|
277
|
+
const cleanupResult = await cleanupMultipleWorktrees(syncResult.deletedIds, createOwnedSessionKiller(pathById));
|
|
248
278
|
if (cleanupResult.warnings.length > 0) {
|
|
249
279
|
// SEC-MF-001: Log detailed warnings server-side only
|
|
250
280
|
logger.warn('sync:cleanup-warnings', { warnings: cleanupResult.warnings });
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Legacy tmux session adoption table (Issue #2866).
|
|
4
|
+
*
|
|
5
|
+
* Once the server has a session-name namespace, a session is named
|
|
6
|
+
* `mcbd-{ns}-{cli}-{worktreeId}[-{suffix}]`. Sessions the same server started
|
|
7
|
+
* before that (`mcbd-{cli}-{worktreeId}[-{suffix}]`) are NOT renamed — a rename
|
|
8
|
+
* would lose them for an older CommandMate running beside this one, or after a
|
|
9
|
+
* downgrade. Instead `adoptLegacySessions` records "the new-format name X is
|
|
10
|
+
* served by the legacy session Y", and `resolveSessionName` returns Y for X
|
|
11
|
+
* while the entry lives.
|
|
12
|
+
*
|
|
13
|
+
* An entry is dropped when its legacy session is killed or found gone
|
|
14
|
+
* (`tmux.ts` `killSession` / `hasSession`), so the next start uses the
|
|
15
|
+
* new-format name.
|
|
16
|
+
*
|
|
17
|
+
* Pure (no `@/` alias, no child_process): `cli-tools/session-name.ts` reads it,
|
|
18
|
+
* and that module is part of the CLI bundle (`tsconfig.cli.json` has no paths).
|
|
19
|
+
*
|
|
20
|
+
* Kept on `globalThis` rather than in module scope: under `next start` this
|
|
21
|
+
* module is evaluated once in the custom server's graph and again in each Next
|
|
22
|
+
* route bundle (see `tui-accumulator.ts`, Issue #2223), and the table is filled
|
|
23
|
+
* by the server at startup but read by the routes.
|
|
24
|
+
*/
|
|
25
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
26
|
+
exports.registerLegacyAlias = registerLegacyAlias;
|
|
27
|
+
exports.lookupLegacyAlias = lookupLegacyAlias;
|
|
28
|
+
exports.dropLegacyAliasByLegacyName = dropLegacyAliasByLegacyName;
|
|
29
|
+
exports.clearLegacyAliasesForTests = clearLegacyAliasesForTests;
|
|
30
|
+
function aliases() {
|
|
31
|
+
return (globalThis.__cmLegacySessionAliases ??= new Map());
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Serve the new-format session name `newName` with the existing legacy session
|
|
35
|
+
* `legacyName`.
|
|
36
|
+
*/
|
|
37
|
+
function registerLegacyAlias(newName, legacyName) {
|
|
38
|
+
aliases().set(newName, legacyName);
|
|
39
|
+
}
|
|
40
|
+
/** The legacy session name adopted for `newName`, if any. */
|
|
41
|
+
function lookupLegacyAlias(newName) {
|
|
42
|
+
return aliases().get(newName);
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Forget every adoption served by the legacy session `legacyName`. A no-op for
|
|
46
|
+
* a name that is not an adopted legacy session.
|
|
47
|
+
*
|
|
48
|
+
* @returns true when an entry was removed
|
|
49
|
+
*/
|
|
50
|
+
function dropLegacyAliasByLegacyName(legacyName) {
|
|
51
|
+
const table = aliases();
|
|
52
|
+
let dropped = false;
|
|
53
|
+
for (const [newName, legacy] of table) {
|
|
54
|
+
if (legacy === legacyName) {
|
|
55
|
+
table.delete(newName);
|
|
56
|
+
dropped = true;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
return dropped;
|
|
60
|
+
}
|
|
61
|
+
/** Test-only: forget every adoption. */
|
|
62
|
+
function clearLegacyAliasesForTests() {
|
|
63
|
+
aliases().clear();
|
|
64
|
+
}
|
|
@@ -114,12 +114,20 @@ tmux set-option -u -t "=$SESSION:" @cm_delegated 2>/dev/null || true
|
|
|
114
114
|
* `CM_LIVE_ATTACH_HOOK=on`.
|
|
115
115
|
*
|
|
116
116
|
* claude-only by session-name prefix, and that is not a shortcut. The session
|
|
117
|
-
* name is `mcbd-<tool>-<worktree
|
|
118
|
-
*
|
|
119
|
-
*
|
|
117
|
+
* name is `mcbd-<ns>-<tool>-<worktree>` or the legacy `mcbd-<tool>-<worktree>`
|
|
118
|
+
* (Issue #2866), no other CLI tool id begins with `claude`, and the script has
|
|
119
|
+
* nothing but the name to go on — a hook fires with tmux formats, not with
|
|
120
|
+
* CommandMate's roster. The namespace is matched as exactly eight hex digits
|
|
121
|
+
* (Issue #2867), not `*`: `mcbd-*-claude-*` would also match a legacy codex
|
|
122
|
+
* session of a worktree whose id starts with `claude-`. Widening it means widening
|
|
120
123
|
* `LIVE_ATTACH_TOOLS` too, and that needs the per-tool re-measurement
|
|
121
124
|
* Issue #2317 puts out of scope.
|
|
122
125
|
*/
|
|
126
|
+
/**
|
|
127
|
+
* A session-name namespace as a shell `case` glob: eight hex digits, the shape
|
|
128
|
+
* `SESSION_NAMESPACE_PATTERN` accepts (Issue #2866 / #2867).
|
|
129
|
+
*/
|
|
130
|
+
const NAMESPACE_GLOB = '[0-9a-f]'.repeat(8);
|
|
123
131
|
exports.LIVE_DELEGATE_SCRIPT = `#!/bin/sh
|
|
124
132
|
# CommandMate live-attach delegate (Issue #2317) — generated file, edits are overwritten.
|
|
125
133
|
#
|
|
@@ -141,7 +149,7 @@ SESSION="\${1:-}"
|
|
|
141
149
|
CONTROL_MODE="\${2:-0}"
|
|
142
150
|
|
|
143
151
|
case "$SESSION" in
|
|
144
|
-
${tmux_session_surface_1.MCBD_SESSION_PREFIX}claude-*) ;;
|
|
152
|
+
${tmux_session_surface_1.MCBD_SESSION_PREFIX}claude-*|${tmux_session_surface_1.MCBD_SESSION_PREFIX}${NAMESPACE_GLOB}-claude-*) ;;
|
|
145
153
|
*) exit 0 ;;
|
|
146
154
|
esac
|
|
147
155
|
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* tmux session ownership (Issue #2865)
|
|
4
|
+
*
|
|
5
|
+
* Session names (`mcbd-{cli}-{worktreeId}`) carry nothing that identifies the
|
|
6
|
+
* CommandMate server that created them, and worktree IDs are derived from the
|
|
7
|
+
* directory name — so two servers with different DBs and different worktree
|
|
8
|
+
* paths can resolve the SAME session name on the shared default tmux socket.
|
|
9
|
+
* Deciding "is it running?" by name alone then shows, types into, kills and
|
|
10
|
+
* adopts the OTHER server's session.
|
|
11
|
+
*
|
|
12
|
+
* CommandMate always creates sessions with `tmux new-session -c <worktree.path>`,
|
|
13
|
+
* so `#{session_path}` records which worktree a session belongs to. A session is
|
|
14
|
+
* "owned" only when the realpath of its `#{session_path}` equals the realpath of
|
|
15
|
+
* the worktree's path; anything else under the same name is "foreign" and is
|
|
16
|
+
* excluded from display, input, stop and reuse.
|
|
17
|
+
*
|
|
18
|
+
* A worktree whose directory has since moved therefore sees its old session as
|
|
19
|
+
* foreign. That is intentional (fail safe).
|
|
20
|
+
*/
|
|
21
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
22
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
23
|
+
};
|
|
24
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
25
|
+
exports.ForeignSessionError = exports.FOREIGN_SESSION_ERROR_CODE = void 0;
|
|
26
|
+
exports.normalizeOwnershipPath = normalizeOwnershipPath;
|
|
27
|
+
exports.isSessionPathOwnedBy = isSessionPathOwnedBy;
|
|
28
|
+
exports.createCachedOwnershipMatcher = createCachedOwnershipMatcher;
|
|
29
|
+
exports.reportForeignSession = reportForeignSession;
|
|
30
|
+
exports.resetForeignSessionWarningsForTesting = resetForeignSessionWarningsForTesting;
|
|
31
|
+
exports.checkSessionOwnership = checkSessionOwnership;
|
|
32
|
+
exports.assertSessionNotForeign = assertSessionNotForeign;
|
|
33
|
+
exports.foreignSessionErrorBody = foreignSessionErrorBody;
|
|
34
|
+
exports.ownedSessionNameSet = ownedSessionNameSet;
|
|
35
|
+
const fs_1 = __importDefault(require("fs"));
|
|
36
|
+
const path_1 = __importDefault(require("path"));
|
|
37
|
+
const logger_1 = require("../../lib/logger");
|
|
38
|
+
const tmux_1 = require("./tmux");
|
|
39
|
+
const logger = (0, logger_1.createLogger)('session-ownership');
|
|
40
|
+
exports.FOREIGN_SESSION_ERROR_CODE = 'session_owned_by_other_server';
|
|
41
|
+
/**
|
|
42
|
+
* Thrown when a code path would adopt / drive a session another server owns.
|
|
43
|
+
*/
|
|
44
|
+
class ForeignSessionError extends Error {
|
|
45
|
+
sessionName;
|
|
46
|
+
sessionPath;
|
|
47
|
+
worktreePath;
|
|
48
|
+
code = exports.FOREIGN_SESSION_ERROR_CODE;
|
|
49
|
+
constructor(sessionName, sessionPath, worktreePath) {
|
|
50
|
+
super(`tmux session "${sessionName}" belongs to another server ` +
|
|
51
|
+
`(session_path=${sessionPath ?? 'unknown'}, worktree=${worktreePath})`);
|
|
52
|
+
this.sessionName = sessionName;
|
|
53
|
+
this.sessionPath = sessionPath;
|
|
54
|
+
this.worktreePath = worktreePath;
|
|
55
|
+
this.name = 'ForeignSessionError';
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
exports.ForeignSessionError = ForeignSessionError;
|
|
59
|
+
/**
|
|
60
|
+
* Normalize a path for ownership comparison: realpath when it resolves,
|
|
61
|
+
* otherwise `path.resolve()`; trailing `/` removed; case preserved.
|
|
62
|
+
*/
|
|
63
|
+
function normalizeOwnershipPath(p) {
|
|
64
|
+
let resolved;
|
|
65
|
+
try {
|
|
66
|
+
resolved = fs_1.default.realpathSync.native(p);
|
|
67
|
+
}
|
|
68
|
+
catch {
|
|
69
|
+
resolved = path_1.default.resolve(p);
|
|
70
|
+
}
|
|
71
|
+
if (resolved.length > 1) {
|
|
72
|
+
resolved = resolved.replace(/\/+$/, '');
|
|
73
|
+
}
|
|
74
|
+
return resolved === '' ? '/' : resolved;
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Pure comparison: is a session created in `sessionPath` owned by the worktree
|
|
78
|
+
* at `worktreePath`? `null` (tmux could not say) is never owned.
|
|
79
|
+
*
|
|
80
|
+
* @param normalize - Path normalizer; {@link createCachedOwnershipMatcher}
|
|
81
|
+
* passes a memoized one. Defaults to {@link normalizeOwnershipPath}.
|
|
82
|
+
*/
|
|
83
|
+
function isSessionPathOwnedBy(sessionPath, worktreePath, normalize = normalizeOwnershipPath) {
|
|
84
|
+
if (sessionPath === null || sessionPath === '')
|
|
85
|
+
return false;
|
|
86
|
+
return normalize(sessionPath) === normalize(worktreePath);
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Build an `isSessionPathOwnedBy` that memoizes realpath per input path. Meant
|
|
90
|
+
* to live for one request (e.g. the batch status listing), so a moved/created
|
|
91
|
+
* directory is picked up on the next request.
|
|
92
|
+
*/
|
|
93
|
+
function createCachedOwnershipMatcher() {
|
|
94
|
+
const cache = new Map();
|
|
95
|
+
const norm = (p) => {
|
|
96
|
+
let v = cache.get(p);
|
|
97
|
+
if (v === undefined) {
|
|
98
|
+
v = normalizeOwnershipPath(p);
|
|
99
|
+
cache.set(p, v);
|
|
100
|
+
}
|
|
101
|
+
return v;
|
|
102
|
+
};
|
|
103
|
+
return (sessionPath, worktreePath) => isSessionPathOwnedBy(sessionPath, worktreePath, norm);
|
|
104
|
+
}
|
|
105
|
+
const warnedForeignSessions = new Set();
|
|
106
|
+
/**
|
|
107
|
+
* Log `session:foreign-detected` once per session name for the process lifetime.
|
|
108
|
+
*/
|
|
109
|
+
function reportForeignSession(sessionName, sessionPath, worktreePath) {
|
|
110
|
+
if (warnedForeignSessions.has(sessionName))
|
|
111
|
+
return;
|
|
112
|
+
warnedForeignSessions.add(sessionName);
|
|
113
|
+
logger.warn('session:foreign-detected', { sessionName, sessionPath, worktreePath });
|
|
114
|
+
}
|
|
115
|
+
/** Test-only: forget which foreign sessions were already reported. */
|
|
116
|
+
function resetForeignSessionWarningsForTesting() {
|
|
117
|
+
warnedForeignSessions.clear();
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* Decide whether `sessionName` is absent, owned by the worktree at
|
|
121
|
+
* `worktreePath`, or foreign. A session that exists but whose
|
|
122
|
+
* `#{session_path}` cannot be read is treated as foreign (fail safe).
|
|
123
|
+
*/
|
|
124
|
+
async function checkSessionOwnership(sessionName, worktreePath) {
|
|
125
|
+
if (!(await (0, tmux_1.hasSession)(sessionName))) {
|
|
126
|
+
return { verdict: 'absent', sessionPath: null };
|
|
127
|
+
}
|
|
128
|
+
const sessionPath = await (0, tmux_1.getSessionWorkingDirectory)(sessionName);
|
|
129
|
+
if (sessionPath !== null && isSessionPathOwnedBy(sessionPath, worktreePath)) {
|
|
130
|
+
return { verdict: 'owned', sessionPath };
|
|
131
|
+
}
|
|
132
|
+
reportForeignSession(sessionName, sessionPath, worktreePath);
|
|
133
|
+
return { verdict: 'foreign', sessionPath };
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* Throw `ForeignSessionError` when `sessionName` is foreign; otherwise return
|
|
137
|
+
* the ownership verdict (`owned` or `absent`).
|
|
138
|
+
*/
|
|
139
|
+
async function assertSessionNotForeign(sessionName, worktreePath) {
|
|
140
|
+
const ownership = await checkSessionOwnership(sessionName, worktreePath);
|
|
141
|
+
if (ownership.verdict === 'foreign') {
|
|
142
|
+
throw new ForeignSessionError(sessionName, ownership.sessionPath, worktreePath);
|
|
143
|
+
}
|
|
144
|
+
return ownership;
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* JSON body for the HTTP 409 an API route returns for a foreign session.
|
|
148
|
+
*/
|
|
149
|
+
function foreignSessionErrorBody(sessionName, sessionPath) {
|
|
150
|
+
return {
|
|
151
|
+
error: `tmux session "${sessionName}" belongs to another CommandMate server`,
|
|
152
|
+
code: exports.FOREIGN_SESSION_ERROR_CODE,
|
|
153
|
+
sessionName,
|
|
154
|
+
sessionPath,
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
/**
|
|
158
|
+
* From one batch `listSessions()` result, the names of the sessions the worktree
|
|
159
|
+
* at `worktreePath` owns. Feed this — not the raw name set — to status detection
|
|
160
|
+
* so a same-named foreign session never reads as "running". Other worktrees'
|
|
161
|
+
* sessions drop out too, which is harmless: status detection only looks up the
|
|
162
|
+
* worktree's own session names.
|
|
163
|
+
*
|
|
164
|
+
* @param matcher - Pass one `createCachedOwnershipMatcher()` across all worktrees
|
|
165
|
+
* of a request so each realpath is resolved once.
|
|
166
|
+
*/
|
|
167
|
+
function ownedSessionNameSet(tmuxSessions, worktreePath, matcher = createCachedOwnershipMatcher()) {
|
|
168
|
+
const owned = new Set();
|
|
169
|
+
for (const session of tmuxSessions) {
|
|
170
|
+
if (matcher(session.path ?? null, worktreePath))
|
|
171
|
+
owned.add(session.name);
|
|
172
|
+
}
|
|
173
|
+
return owned;
|
|
174
|
+
}
|
|
@@ -30,6 +30,7 @@ const child_process_1 = require("child_process");
|
|
|
30
30
|
const util_1 = require("util");
|
|
31
31
|
const tmux_capture_cache_1 = require("./tmux-capture-cache");
|
|
32
32
|
const geometry_delegation_1 = require("./geometry-delegation");
|
|
33
|
+
const legacy_session_alias_1 = require("./legacy-session-alias");
|
|
33
34
|
const validation_1 = require("../../lib/cli-tools/validation");
|
|
34
35
|
const tmux_pane_config_1 = require("../../config/tmux-pane-config");
|
|
35
36
|
const logger_1 = require("../../lib/logger");
|
|
@@ -181,6 +182,9 @@ async function hasSession(sessionName) {
|
|
|
181
182
|
}
|
|
182
183
|
catch {
|
|
183
184
|
// tmux has-session returns non-zero exit code if session doesn't exist
|
|
185
|
+
// Issue #2866: an adopted legacy session that is gone no longer serves its
|
|
186
|
+
// new-format name, so the next start uses the new-format name.
|
|
187
|
+
(0, legacy_session_alias_1.dropLegacyAliasByLegacyName)(sessionName);
|
|
184
188
|
return false;
|
|
185
189
|
}
|
|
186
190
|
}
|
|
@@ -225,19 +229,23 @@ async function getSessionWorkingDirectory(sessionName) {
|
|
|
225
229
|
*/
|
|
226
230
|
async function listSessions() {
|
|
227
231
|
try {
|
|
228
|
-
|
|
232
|
+
// Issue #2865: `#{session_path}` is the LAST column and tab-separated, so a
|
|
233
|
+
// path containing `|` or spaces survives intact (everything after the third
|
|
234
|
+
// tab is the path).
|
|
235
|
+
const { stdout } = await execFileAsync('tmux', ['list-sessions', '-F', '#{session_name}\t#{session_windows}\t#{session_attached}\t#{session_path}'], { timeout: DEFAULT_TIMEOUT });
|
|
229
236
|
if (!stdout || stdout.trim() === '') {
|
|
230
237
|
return [];
|
|
231
238
|
}
|
|
232
239
|
return stdout
|
|
233
|
-
.trim()
|
|
234
240
|
.split('\n')
|
|
241
|
+
.filter(line => line.trim() !== '')
|
|
235
242
|
.map(line => {
|
|
236
|
-
const [name, windows, attached] = line.split('
|
|
243
|
+
const [name, windows, attached, ...rest] = line.split('\t');
|
|
237
244
|
return {
|
|
238
245
|
name,
|
|
239
246
|
windows: parseInt(windows, 10) || 0,
|
|
240
247
|
attached: attached === '1',
|
|
248
|
+
path: rest.join('\t'),
|
|
241
249
|
};
|
|
242
250
|
});
|
|
243
251
|
}
|
|
@@ -628,6 +636,8 @@ async function killSession(sessionName) {
|
|
|
628
636
|
await execFileAsync('tmux', ['kill-session', '-t', exactTarget(sessionName)], {
|
|
629
637
|
timeout: DEFAULT_TIMEOUT,
|
|
630
638
|
});
|
|
639
|
+
// Issue #2866: see hasSession.
|
|
640
|
+
(0, legacy_session_alias_1.dropLegacyAliasByLegacyName)(sessionName);
|
|
631
641
|
return true;
|
|
632
642
|
}
|
|
633
643
|
catch (error) {
|