commandmate 0.30.2 → 0.31.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 +151 -151
- package/.next/app-path-routes-manifest.json +25 -25
- 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_client-reference-manifest.js +1 -1
- package/.next/server/app/api/agents/versions/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/assistant/conversation/route.js +1 -1
- package/.next/server/app/api/assistant/conversation/route.js.nft.json +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.js +1 -1
- package/.next/server/app/api/assistant/current-output/route.js.nft.json +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.js +1 -1
- package/.next/server/app/api/assistant/messages/route.js.nft.json +1 -1
- package/.next/server/app/api/assistant/messages/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/assistant/session/route.js +1 -1
- package/.next/server/app/api/assistant/session/route.js.nft.json +1 -1
- package/.next/server/app/api/assistant/session/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/assistant/start/route.js +3 -3
- package/.next/server/app/api/assistant/start/route.js.nft.json +1 -1
- package/.next/server/app/api/assistant/start/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/assistant/terminal/route.js +4 -4
- package/.next/server/app/api/assistant/terminal/route.js.nft.json +1 -1
- package/.next/server/app/api/assistant/terminal/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/assistant/tools/route.js +1 -1
- package/.next/server/app/api/assistant/tools/route.js.nft.json +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/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 +1 -1
- 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/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/[id]/todos/[todoId]/route_client-reference-manifest.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.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.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.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 +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/todos/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]/env/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_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/files/[...path]/route.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/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 +1 -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.js +4 -3
- 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.js +4 -3
- 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.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 +2 -2
- 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 +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_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/terminal/route.js +1 -1
- 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_client-reference-manifest.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_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/chat/page.js +2 -2
- package/.next/server/app/chat/page.js.nft.json +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.js +2 -2
- package/.next/server/app/more/page.js.nft.json +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.js +1 -1
- package/.next/server/app/page.js.nft.json +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.js +2 -2
- package/.next/server/app/repositories/page.js.nft.json +1 -1
- package/.next/server/app/repositories/page_client-reference-manifest.js +1 -1
- package/.next/server/app/review/page.js +1 -1
- package/.next/server/app/review/page.js.nft.json +1 -1
- package/.next/server/app/review/page_client-reference-manifest.js +1 -1
- package/.next/server/app/sessions/page.js +1 -1
- package/.next/server/app/sessions/page.js.nft.json +1 -1
- package/.next/server/app/sessions/page_client-reference-manifest.js +1 -1
- package/.next/server/app/skills/[skillId]/page.js +2 -2
- package/.next/server/app/skills/[skillId]/page.js.nft.json +1 -1
- package/.next/server/app/skills/[skillId]/page_client-reference-manifest.js +1 -1
- package/.next/server/app/skills/installed/page.js +2 -2
- package/.next/server/app/skills/installed/page.js.nft.json +1 -1
- package/.next/server/app/skills/installed/page_client-reference-manifest.js +1 -1
- package/.next/server/app/skills/page.js +1 -1
- package/.next/server/app/skills/page.js.nft.json +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 +11 -11
- package/.next/server/app/worktrees/[id]/page.js.nft.json +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 +25 -25
- package/.next/server/chunks/1058.js +1 -1
- package/.next/server/chunks/1068.js +1 -1
- package/.next/server/chunks/1262.js +1 -1
- package/.next/server/chunks/1886.js +3 -3
- package/.next/server/chunks/1990.js +103 -0
- package/.next/server/chunks/2119.js +1 -1
- package/.next/server/chunks/2328.js +1 -1
- package/.next/server/chunks/2404.js +1 -1
- package/.next/server/chunks/2445.js +1 -1
- package/.next/server/chunks/261.js +1 -1
- package/.next/server/chunks/3123.js +1 -1
- package/.next/server/chunks/3241.js +1 -1
- package/.next/server/chunks/3480.js +5 -4
- package/.next/server/chunks/4299.js +1 -1
- package/.next/server/chunks/4393.js +1 -1
- package/.next/server/chunks/4443.js +1 -5
- package/.next/server/chunks/4802.js +1 -0
- package/.next/server/chunks/5017.js +1 -1
- package/.next/server/chunks/5043.js +1 -1
- package/.next/server/chunks/5164.js +1 -1
- package/.next/server/chunks/552.js +2 -2
- package/.next/server/chunks/6072.js +1 -1
- package/.next/server/chunks/6130.js +1 -1
- package/.next/server/chunks/663.js +4 -3
- package/.next/server/chunks/6894.js +2 -2
- package/.next/server/chunks/7187.js +1 -0
- package/.next/server/chunks/7472.js +1 -1
- package/.next/server/chunks/{880.js → 7859.js} +6 -6
- package/.next/server/chunks/7905.js +1 -1
- package/.next/server/chunks/8800.js +2 -2
- package/.next/server/chunks/8918.js +7 -0
- package/.next/server/chunks/9123.js +2 -2
- package/.next/server/chunks/9130.js +4 -4
- package/.next/server/chunks/9627.js +1 -1
- package/.next/server/chunks/9696.js +1 -1
- package/.next/server/chunks/9798.js +1 -1
- package/.next/server/middleware-manifest.json +5 -5
- package/.next/server/pages/500.html +1 -1
- package/.next/server/pages-manifest.json +1 -1
- package/.next/server/server-reference-manifest.json +1 -1
- package/.next/static/chunks/1783-11c9b4d8a52ebd91.js +1 -0
- package/.next/static/chunks/4718-46e1d2f07c8423ba.js +1 -0
- package/.next/static/chunks/6022-2fa0b5a82d4ce7b6.js +1 -0
- package/.next/static/chunks/{5229-6c89475b61bd2c8b.js → 6170-951b9f5fe6b4b3b5.js} +1 -1
- package/.next/static/chunks/app/chat/{page-9aa2011410895b0d.js → page-37d7615d17d7e2a0.js} +1 -1
- package/.next/static/chunks/app/layout-4c2f21e3f653ee4c.js +1 -0
- package/.next/static/chunks/app/more/{page-333bba1c8bbc9c11.js → page-353a3924e6d7968e.js} +1 -1
- package/.next/static/chunks/app/{page-214f7ffa13a0cf10.js → page-c2bf871d953a1d6f.js} +1 -1
- package/.next/static/chunks/app/repositories/{page-4951cac905e50224.js → page-7d0f26df0537254c.js} +1 -1
- package/.next/static/chunks/app/review/{page-a0899b2d285318c3.js → page-e3f141b2829973e9.js} +1 -1
- package/.next/static/chunks/app/sessions/{page-fb36145ec732092a.js → page-53f188ed9ca49e5f.js} +1 -1
- package/.next/static/chunks/app/skills/[skillId]/{page-9099e26b60442873.js → page-80569b720aea59cc.js} +1 -1
- package/.next/static/chunks/app/skills/installed/{page-182e32368696d824.js → page-dbd2690ad72da95b.js} +1 -1
- package/.next/static/chunks/app/skills/{page-d3fdc88239ebe0ab.js → page-dacd36f9c11aa7da.js} +1 -1
- package/.next/static/chunks/app/worktrees/[id]/page-64d4ec3051971760.js +1 -0
- package/.next/static/css/5c13ea243dfdcb66.css +1 -0
- package/.next/trace +13 -13
- package/README.md +37 -11
- package/dist/cli/commands/attach.d.ts +50 -0
- package/dist/cli/commands/attach.d.ts.map +1 -0
- package/dist/cli/commands/attach.js +204 -0
- package/dist/cli/commands/capture.d.ts.map +1 -1
- package/dist/cli/commands/capture.js +114 -0
- package/dist/cli/commands/instances.d.ts.map +1 -1
- package/dist/cli/commands/instances.js +23 -2
- package/dist/cli/commands/ls.d.ts.map +1 -1
- package/dist/cli/commands/ls.js +41 -1
- package/dist/cli/config/cli-dependencies.d.ts.map +1 -1
- package/dist/cli/config/cli-dependencies.js +34 -0
- package/dist/cli/program.d.ts.map +1 -1
- package/dist/cli/program.js +5 -0
- package/dist/cli/types/index.d.ts +36 -0
- package/dist/cli/types/index.d.ts.map +1 -1
- package/dist/config/tmux-pane-config.d.ts +153 -0
- package/dist/config/tmux-pane-config.d.ts.map +1 -0
- package/dist/config/tmux-pane-config.js +172 -0
- package/dist/lib/cli-tools/session-name.d.ts +44 -0
- package/dist/lib/cli-tools/session-name.d.ts.map +1 -0
- package/dist/lib/cli-tools/session-name.js +57 -0
- package/dist/lib/cli-tools/types.d.ts +1 -1
- package/dist/lib/cli-tools/types.d.ts.map +1 -1
- package/dist/lib/cli-tools/types.js +6 -1
- package/dist/lib/cli-tools/validation.d.ts +34 -0
- package/dist/lib/cli-tools/validation.d.ts.map +1 -0
- package/dist/lib/cli-tools/validation.js +41 -0
- package/dist/lib/detection/status-reason.d.ts +18 -0
- package/dist/lib/detection/status-reason.d.ts.map +1 -1
- package/dist/lib/detection/status-reason.js +18 -0
- package/dist/lib/detection/tools/verified-against.d.ts +57 -7
- package/dist/lib/detection/tools/verified-against.d.ts.map +1 -1
- package/dist/lib/detection/tools/verified-against.js +59 -8
- package/dist/lib/detection/version-probes.d.ts +26 -5
- package/dist/lib/detection/version-probes.d.ts.map +1 -1
- package/dist/lib/detection/version-probes.js +28 -6
- package/dist/lib/session/tmux-session-surface.d.ts +369 -0
- package/dist/lib/session/tmux-session-surface.d.ts.map +1 -0
- package/dist/lib/session/tmux-session-surface.js +525 -0
- package/dist/server/src/config/cli-tool-timing-config.js +12 -1
- package/dist/server/src/config/detection-evidence-config.js +5 -0
- package/dist/server/src/config/schedule-config.js +28 -1
- package/dist/server/src/lib/cli-tools/antigravity.js +2 -2
- package/dist/server/src/lib/cli-tools/base.js +13 -8
- package/dist/server/src/lib/cli-tools/claude.js +20 -0
- package/dist/server/src/lib/cli-tools/codex.js +205 -61
- package/dist/server/src/lib/cli-tools/command-code.js +349 -0
- package/dist/server/src/lib/cli-tools/composer-spec.js +6 -0
- 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/graceful-exit.js +16 -1
- package/dist/server/src/lib/cli-tools/index.js +4 -1
- package/dist/server/src/lib/cli-tools/install-hints.js +131 -0
- package/dist/server/src/lib/cli-tools/liveness-spec.js +18 -0
- package/dist/server/src/lib/cli-tools/manager.js +2 -0
- package/dist/server/src/lib/cli-tools/opencode.js +2 -2
- package/dist/server/src/lib/cli-tools/start-availability.js +18 -11
- package/dist/server/src/lib/cli-tools/types.js +6 -1
- package/dist/server/src/lib/cli-tools/vibe-local.js +2 -2
- package/dist/server/src/lib/cmate-parser.js +6 -0
- package/dist/server/src/lib/detection/cli-patterns.js +441 -8
- package/dist/server/src/lib/detection/opencode-modal-overlay.js +66 -100
- package/dist/server/src/lib/detection/selection-shape.js +341 -0
- package/dist/server/src/lib/detection/sgr-background.js +210 -0
- package/dist/server/src/lib/detection/status-detector.js +6 -0
- package/dist/server/src/lib/detection/status-reason.js +18 -0
- package/dist/server/src/lib/detection/tools/antigravity/detect.js +38 -15
- package/dist/server/src/lib/detection/tools/codex/cli-patterns.js +392 -0
- package/dist/server/src/lib/detection/tools/codex/detect.js +83 -2
- package/dist/server/src/lib/detection/tools/command-code/detect.js +159 -0
- package/dist/server/src/lib/detection/tools/registry.js +6 -4
- package/dist/server/src/lib/detection/tools/verified-against.js +59 -8
- package/dist/server/src/lib/detection/version-probes.js +28 -6
- package/dist/server/src/lib/hooks/sources/antigravity/history.js +77 -16
- package/dist/server/src/lib/hooks/sources/claude/history.js +94 -18
- package/dist/server/src/lib/hooks/sources/codex/history.js +84 -15
- package/dist/server/src/lib/hooks/sources/codex/hooks-config.js +130 -17
- package/dist/server/src/lib/hooks/sources/codex/relay-install.js +145 -0
- package/dist/server/src/lib/hooks/sources/command-code/history.js +865 -0
- package/dist/server/src/lib/hooks/sources/command-code/hooks-config.js +390 -0
- package/dist/server/src/lib/hooks/sources/command-code/source.js +290 -0
- package/dist/server/src/lib/hooks/sources/command-code/tool-id.js +18 -0
- package/dist/server/src/lib/hooks/sources/command-code/transcript.js +604 -0
- package/dist/server/src/lib/hooks/sources/index.js +3 -1
- package/dist/server/src/lib/hooks/sources/opencode/history.js +57 -3
- package/dist/server/src/lib/hooks/sources/opencode/transcript.js +24 -28
- package/dist/server/src/lib/hooks/sources/registry.js +4 -0
- package/dist/server/src/lib/hooks/sources/turn-body.js +77 -19
- package/dist/server/src/lib/job-executor.js +12 -0
- package/dist/server/src/lib/polling/auto-yes-dialog-gate.js +6 -0
- package/dist/server/src/lib/polling/response-checker.js +112 -7
- package/dist/server/src/lib/polling/structured-history-gate.js +15 -8
- package/dist/server/src/lib/realtime/types.js +15 -5
- package/dist/server/src/lib/response-cleaner.js +24 -1
- package/dist/server/src/lib/response-extractor.js +11 -0
- package/dist/server/src/lib/session/claude-executor.js +223 -4
- package/dist/server/src/lib/session/cli-session.js +52 -0
- package/dist/server/src/lib/session/tmux-session-surface.js +525 -0
- package/dist/server/src/lib/skills/compatibility-matrix.js +117 -1
- package/dist/server/src/lib/tmux/geometry-delegation.js +143 -0
- package/dist/server/src/lib/tmux/read-mode-pager.js +76 -10
- package/dist/server/src/lib/tmux/read-mode.js +44 -2
- package/dist/server/src/lib/tmux/session-hook-scripts.js +249 -0
- package/dist/server/src/lib/tmux/session-hooks.js +122 -0
- package/dist/server/src/lib/tmux/session-status-options.js +213 -0
- package/dist/server/src/lib/tmux/tmux.js +79 -2
- package/dist/server/src/lib/version-checker.js +110 -0
- package/dist/server/src/lib/ws-server.js +16 -10
- package/dist/server/src/types/agent-transcript.js +73 -1
- package/dist/server/src/types/terminal-keys.js +131 -3
- package/dist/types/terminal-keys.d.ts +100 -3
- package/dist/types/terminal-keys.d.ts.map +1 -1
- package/dist/types/terminal-keys.js +131 -3
- package/package.json +1 -1
- package/.next/server/chunks/3099.js +0 -5
- package/.next/server/chunks/7824.js +0 -1
- package/.next/server/chunks/848.js +0 -1
- package/.next/static/chunks/3916-0e929d089282dc5d.js +0 -1
- package/.next/static/chunks/6022-7a7d1760fc9814c7.js +0 -1
- package/.next/static/chunks/6381-147dde2a967e9458.js +0 -1
- package/.next/static/chunks/app/layout-6a26ea22d5cf03dc.js +0 -1
- package/.next/static/chunks/app/worktrees/[id]/page-2f73d69187936627.js +0 -1
- package/.next/static/css/23f9c40ecc458c77.css +0 -1
- /package/.next/static/{QXxQbXWoo7ldUyOcv7PCZ → 60mm4_QBuFtFTfo5UMyKU}/_buildManifest.js +0 -0
- /package/.next/static/{QXxQbXWoo7ldUyOcv7PCZ → 60mm4_QBuFtFTfo5UMyKU}/_ssgManifest.js +0 -0
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Server side of Issue #2317's session-scoped hooks and geometry hand-back.
|
|
4
|
+
*
|
|
5
|
+
* Two jobs, both keyed off one session name:
|
|
6
|
+
*
|
|
7
|
+
* - {@link ensureSessionHooks} reconciles the two OPT-IN `client-attached`
|
|
8
|
+
* hooks (Phase C's auto popup, Phase D's delegate-on-hand-attach) with what
|
|
9
|
+
* the environment asks for. Like #1623's key binding it CONVERGES: turning an
|
|
10
|
+
* option off removes the hook a previous run installed, rather than merely
|
|
11
|
+
* not installing it again.
|
|
12
|
+
* - {@link reconcileDelegatedGeometry} is the safety net the Issue asked a
|
|
13
|
+
* `client-detached` hook to be. tmux 3.5a does not fire that hook
|
|
14
|
+
* session-scoped (measured — see the table in `live-attach.ts`), so the
|
|
15
|
+
* restore runs from the status poll instead: every couple of seconds, for
|
|
16
|
+
* every session, "is this delegated while no human is looking at it?".
|
|
17
|
+
*
|
|
18
|
+
* Nothing here throws. All of it hangs off the status poll and off session
|
|
19
|
+
* creation, and neither may be failed by a convenience feature.
|
|
20
|
+
*/
|
|
21
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
22
|
+
exports.ensureSessionHooks = ensureSessionHooks;
|
|
23
|
+
exports.reconcileDelegatedGeometry = reconcileDelegatedGeometry;
|
|
24
|
+
exports.forgetSessionHooks = forgetSessionHooks;
|
|
25
|
+
const child_process_1 = require("child_process");
|
|
26
|
+
const util_1 = require("util");
|
|
27
|
+
const tmux_session_surface_1 = require("../session/tmux-session-surface");
|
|
28
|
+
const session_hook_scripts_1 = require("./session-hook-scripts");
|
|
29
|
+
const geometry_delegation_1 = require("./geometry-delegation");
|
|
30
|
+
const logger_1 = require("../logger");
|
|
31
|
+
const execFileAsync = (0, util_1.promisify)(child_process_1.execFile);
|
|
32
|
+
const logger = (0, logger_1.createLogger)('tmux-session-hooks');
|
|
33
|
+
/** tmux calls here are interactive-latency, not long-running. */
|
|
34
|
+
const TMUX_TIMEOUT = 5000;
|
|
35
|
+
const ensured = globalThis.__cmEnsuredSessionHooks ?? (globalThis.__cmEnsuredSessionHooks = new Set());
|
|
36
|
+
/**
|
|
37
|
+
* Reconcile a session's opt-in `client-attached` hooks with the environment.
|
|
38
|
+
*
|
|
39
|
+
* Runs once per session per process; call it freely. Both hooks are removed when
|
|
40
|
+
* their option is off, so `CM_READ_MODE_AUTO_POPUP=on` followed by a restart
|
|
41
|
+
* without it leaves no hook behind.
|
|
42
|
+
*
|
|
43
|
+
* @param sessionName - Target session
|
|
44
|
+
*/
|
|
45
|
+
async function ensureSessionHooks(sessionName) {
|
|
46
|
+
if (!(0, tmux_session_surface_1.isCommandMateSession)(sessionName))
|
|
47
|
+
return;
|
|
48
|
+
if (ensured.has(sessionName))
|
|
49
|
+
return;
|
|
50
|
+
ensured.add(sessionName);
|
|
51
|
+
try {
|
|
52
|
+
if ((0, tmux_session_surface_1.isLiveAttachHookEnabled)() && (0, tmux_session_surface_1.isLiveAttachEligibleSession)(sessionName)) {
|
|
53
|
+
// The restore script rides along with the delegate one: a user whose
|
|
54
|
+
// server is down needs the manual way back, and materializing it here is
|
|
55
|
+
// the only moment we know the delegate path is in use at all.
|
|
56
|
+
(0, session_hook_scripts_1.materializeLiveRestoreScript)();
|
|
57
|
+
const args = (0, tmux_session_surface_1.buildInstallDelegateHookArgs)(sessionName, (0, session_hook_scripts_1.materializeLiveDelegateScript)());
|
|
58
|
+
if (args)
|
|
59
|
+
await execFileAsync('tmux', args, { timeout: TMUX_TIMEOUT });
|
|
60
|
+
}
|
|
61
|
+
else {
|
|
62
|
+
await execFileAsync('tmux', (0, tmux_session_surface_1.buildRemoveDelegateHookArgs)(sessionName), {
|
|
63
|
+
timeout: TMUX_TIMEOUT,
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
if ((0, tmux_session_surface_1.isAutoPopupEnabled)()) {
|
|
67
|
+
const args = (0, tmux_session_surface_1.buildInstallAutoPopupHookArgs)(sessionName, (0, session_hook_scripts_1.materializeAutoPopupScript)());
|
|
68
|
+
if (args)
|
|
69
|
+
await execFileAsync('tmux', args, { timeout: TMUX_TIMEOUT });
|
|
70
|
+
}
|
|
71
|
+
else {
|
|
72
|
+
await execFileAsync('tmux', (0, tmux_session_surface_1.buildRemoveAutoPopupHookArgs)(sessionName), {
|
|
73
|
+
timeout: TMUX_TIMEOUT,
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
catch (error) {
|
|
78
|
+
logger.debug('session-hooks:reconcile-failed', {
|
|
79
|
+
sessionName,
|
|
80
|
+
error: error instanceof Error ? error.message : String(error),
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Take a delegated session's geometry back when no human is attached.
|
|
86
|
+
*
|
|
87
|
+
* The Phase D safety net. Restores unconditionally once the two conditions hold,
|
|
88
|
+
* because the sequence is idempotent — a session already at `manual` 200x1000
|
|
89
|
+
* with no `@cm_delegated` is left byte-identical by it.
|
|
90
|
+
*
|
|
91
|
+
* Also self-healing for a flag left behind by a CLI that was killed: the flag
|
|
92
|
+
* alone never keeps a window unpinned, because this runs whenever nobody is
|
|
93
|
+
* looking.
|
|
94
|
+
*
|
|
95
|
+
* @param sessionName - Target session
|
|
96
|
+
* @returns true when the geometry was handed back by this call
|
|
97
|
+
*/
|
|
98
|
+
async function reconcileDelegatedGeometry(sessionName) {
|
|
99
|
+
try {
|
|
100
|
+
if (!(await (0, geometry_delegation_1.isGeometryDelegated)(sessionName)))
|
|
101
|
+
return false;
|
|
102
|
+
if (await (0, geometry_delegation_1.hasHumanClientAttached)(sessionName))
|
|
103
|
+
return false;
|
|
104
|
+
for (const args of (0, tmux_session_surface_1.buildRestoreGeometryCommands)(sessionName)) {
|
|
105
|
+
await execFileAsync('tmux', args, { timeout: TMUX_TIMEOUT });
|
|
106
|
+
}
|
|
107
|
+
logger.info('session-hooks:geometry-restored', { sessionName });
|
|
108
|
+
return true;
|
|
109
|
+
}
|
|
110
|
+
catch (error) {
|
|
111
|
+
logger.debug('session-hooks:geometry-restore-failed', {
|
|
112
|
+
sessionName,
|
|
113
|
+
error: error instanceof Error ? error.message : String(error),
|
|
114
|
+
});
|
|
115
|
+
return false;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
/** Forget a session's hook bookkeeping — call when it is known to be gone. */
|
|
119
|
+
function forgetSessionHooks(sessionName) {
|
|
120
|
+
ensured.delete(sessionName);
|
|
121
|
+
(0, geometry_delegation_1.forgetGeometryDelegation)(sessionName);
|
|
122
|
+
}
|
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Phase B of Issue #2317: publish the session's state ONTO the tmux session.
|
|
4
|
+
*
|
|
5
|
+
* Before this module, `running` / `waiting` / `ready` / `idle` existed only
|
|
6
|
+
* inside CommandMate. `tmux ls` showed a name and a window count, an attached
|
|
7
|
+
* status line showed tmux's stock clock, and an operator who wanted to know what
|
|
8
|
+
* an agent was doing had to open the web UI or run `commandmate ls`. Two writes
|
|
9
|
+
* per transition fix that:
|
|
10
|
+
*
|
|
11
|
+
* - **user options** (`@cm_status` and friends) — readable without attaching:
|
|
12
|
+
* `tmux ls -F '#{session_name} #{@cm_status}'`;
|
|
13
|
+
* - **a session-scoped `status-right`** — readable while attached, rendered
|
|
14
|
+
* from those same options so the line updates without being rewritten.
|
|
15
|
+
*
|
|
16
|
+
* ## Everything here is session-scoped, and that is the point
|
|
17
|
+
*
|
|
18
|
+
* `set-option -t <session>` writes the SESSION's option table. The tmux server's
|
|
19
|
+
* global table — which every session on the machine inherits, including ones
|
|
20
|
+
* CommandMate knows nothing about — is never touched, and
|
|
21
|
+
* `tests/unit/tmux/session-status-options-2317.test.ts` asserts that by
|
|
22
|
+
* inspecting the argv of every call this module makes. #1623's `bind-key` is the
|
|
23
|
+
* one global mutation CommandMate makes and it is not made here.
|
|
24
|
+
*
|
|
25
|
+
* ## Writes happen on TRANSITION, not on every poll
|
|
26
|
+
*
|
|
27
|
+
* `GET /api/worktrees` probes every tool of every worktree every couple of
|
|
28
|
+
* seconds. Setting five options on each of those polls would be a few hundred
|
|
29
|
+
* `execFile` round-trips a minute for a value that changes a few times an hour,
|
|
30
|
+
* so {@link publishSessionStatus} keeps the last published tuple per session and
|
|
31
|
+
* returns without touching tmux when nothing moved. The memo is keyed by session
|
|
32
|
+
* name and dropped by {@link forgetSessionStatus} when the session goes away.
|
|
33
|
+
*
|
|
34
|
+
* ## The opt-out converges, without a startup hook
|
|
35
|
+
*
|
|
36
|
+
* `CM_TMUX_STATUS=off` does not merely stop writing: the first status poll after
|
|
37
|
+
* the restart REMOVES the `@cm_*` options and the CommandMate `status-right`
|
|
38
|
+
* from each session it visits, once per session. That is the same "an opt-out
|
|
39
|
+
* that uninstalls" contract #1623 gave `CM_READ_MODE`, and it is done from the
|
|
40
|
+
* poll rather than from a startup sweep for two reasons — the poll already knows
|
|
41
|
+
* which sessions exist (a sweep would have to ask tmux again), and it keeps this
|
|
42
|
+
* feature from needing a wire into `server.ts`, whose startup path is
|
|
43
|
+
* deliberately a single dynamic import per module graph.
|
|
44
|
+
*
|
|
45
|
+
* ## Nothing here may ever throw
|
|
46
|
+
*
|
|
47
|
+
* This is a convenience surface hanging off the status poll. A tmux hiccup must
|
|
48
|
+
* not be able to fail the poll that the sidebar, `commandmate ls` and the header
|
|
49
|
+
* chip all read, so every entry point swallows its errors and logs at debug.
|
|
50
|
+
*/
|
|
51
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
52
|
+
exports.publishSessionStatus = publishSessionStatus;
|
|
53
|
+
exports.ensureSessionStatusLine = ensureSessionStatusLine;
|
|
54
|
+
exports.clearSessionStatus = clearSessionStatus;
|
|
55
|
+
exports.forgetSessionStatus = forgetSessionStatus;
|
|
56
|
+
exports.publishedSessionCount = publishedSessionCount;
|
|
57
|
+
const child_process_1 = require("child_process");
|
|
58
|
+
const util_1 = require("util");
|
|
59
|
+
const tmux_session_surface_1 = require("../session/tmux-session-surface");
|
|
60
|
+
const logger_1 = require("../logger");
|
|
61
|
+
const execFileAsync = (0, util_1.promisify)(child_process_1.execFile);
|
|
62
|
+
const logger = (0, logger_1.createLogger)('tmux-session-status');
|
|
63
|
+
/** tmux calls here are interactive-latency, not long-running. */
|
|
64
|
+
const TMUX_TIMEOUT = 5000;
|
|
65
|
+
const published = globalThis.__cmPublishedTmuxStatus ??
|
|
66
|
+
(globalThis.__cmPublishedTmuxStatus = new Map());
|
|
67
|
+
/** Sessions this process has already uninstalled the surface from. */
|
|
68
|
+
const cleared = globalThis.__cmClearedTmuxStatus ?? (globalThis.__cmClearedTmuxStatus = new Set());
|
|
69
|
+
/**
|
|
70
|
+
* Write one session's state onto its tmux session, if it changed.
|
|
71
|
+
*
|
|
72
|
+
* @param publication - Session identity plus the status word to publish
|
|
73
|
+
* @returns What was done, for tests and for the caller's logging
|
|
74
|
+
*/
|
|
75
|
+
async function publishSessionStatus(publication) {
|
|
76
|
+
const { sessionName, worktreeId, cliToolId, instanceId, status } = publication;
|
|
77
|
+
if (!(0, tmux_session_surface_1.isTmuxStatusEnabled)()) {
|
|
78
|
+
// The uninstall path. Once per session per process: `clearSessionStatus`
|
|
79
|
+
// issues six `execFile` calls, and repeating them on every poll of a server
|
|
80
|
+
// that has the surface turned off would be the cost the feature was turned
|
|
81
|
+
// off to avoid.
|
|
82
|
+
if (!cleared.has(sessionName)) {
|
|
83
|
+
cleared.add(sessionName);
|
|
84
|
+
await clearSessionStatus(sessionName);
|
|
85
|
+
}
|
|
86
|
+
return 'disabled';
|
|
87
|
+
}
|
|
88
|
+
const previous = published.get(sessionName);
|
|
89
|
+
if (previous &&
|
|
90
|
+
previous.status === status &&
|
|
91
|
+
previous.worktreeId === worktreeId &&
|
|
92
|
+
previous.cliToolId === cliToolId &&
|
|
93
|
+
previous.instanceId === instanceId) {
|
|
94
|
+
return 'unchanged';
|
|
95
|
+
}
|
|
96
|
+
const updated = (publication.now ?? new Date()).toISOString();
|
|
97
|
+
const writes = [
|
|
98
|
+
[tmux_session_surface_1.CM_STATUS_OPTION, status],
|
|
99
|
+
[tmux_session_surface_1.CM_WORKTREE_OPTION, worktreeId],
|
|
100
|
+
[tmux_session_surface_1.CM_TOOL_OPTION, cliToolId],
|
|
101
|
+
[tmux_session_surface_1.CM_INSTANCE_OPTION, instanceId],
|
|
102
|
+
[tmux_session_surface_1.CM_UPDATED_OPTION, updated],
|
|
103
|
+
];
|
|
104
|
+
try {
|
|
105
|
+
for (const [option, value] of writes) {
|
|
106
|
+
await execFileAsync('tmux', (0, tmux_session_surface_1.buildSetSessionOptionArgs)(sessionName, option, value), {
|
|
107
|
+
timeout: TMUX_TIMEOUT,
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
published.set(sessionName, { status, worktreeId, cliToolId, instanceId });
|
|
111
|
+
// The line renders FROM the options above, so it is installed after them —
|
|
112
|
+
// a status-right referencing `#{@cm_status}` on a session that has none
|
|
113
|
+
// paints an empty pair of brackets until the first write lands.
|
|
114
|
+
await ensureSessionStatusLine(sessionName);
|
|
115
|
+
return 'written';
|
|
116
|
+
}
|
|
117
|
+
catch (error) {
|
|
118
|
+
// A session that was killed between the poll and this write is the ordinary
|
|
119
|
+
// case, not an incident. Drop the memo so the next live session under the
|
|
120
|
+
// same name republishes rather than being deduped against a dead one.
|
|
121
|
+
published.delete(sessionName);
|
|
122
|
+
logger.debug('session-status:write-failed', {
|
|
123
|
+
sessionName,
|
|
124
|
+
error: error instanceof Error ? error.message : String(error),
|
|
125
|
+
});
|
|
126
|
+
return 'error';
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* Install the session-scoped `status-right`, unless the session already has one.
|
|
131
|
+
*
|
|
132
|
+
* "Already has one" means a SESSION-scoped value, which is exactly what
|
|
133
|
+
* `show-options -t <session> status-right` reports: measured on tmux 3.5a, a
|
|
134
|
+
* session that never set it answers with empty stdout (the global value is not
|
|
135
|
+
* echoed), and one that did answers `status-right "…"`. So a user who
|
|
136
|
+
* customised this session in `.tmux.conf` — or who set it by hand — keeps their
|
|
137
|
+
* line, and everyone else gets CommandMate's.
|
|
138
|
+
*
|
|
139
|
+
* Idempotent: once installed, the value contains {@link CM_STATUS_RIGHT_FORMAT}
|
|
140
|
+
* and is recognised as ours, so a restart re-installs nothing.
|
|
141
|
+
*
|
|
142
|
+
* @param sessionName - Target session
|
|
143
|
+
* @returns true when the line is CommandMate's after this call
|
|
144
|
+
*/
|
|
145
|
+
async function ensureSessionStatusLine(sessionName) {
|
|
146
|
+
try {
|
|
147
|
+
const { stdout } = await execFileAsync('tmux', (0, tmux_session_surface_1.buildShowSessionOptionArgs)(sessionName, 'status-right'), { timeout: TMUX_TIMEOUT });
|
|
148
|
+
const existing = stdout.trim();
|
|
149
|
+
if (existing !== '' && !existing.includes(tmux_session_surface_1.CM_STATUS_RIGHT_FORMAT)) {
|
|
150
|
+
logger.debug('session-status:status-right-user-owned', { sessionName, existing });
|
|
151
|
+
return false;
|
|
152
|
+
}
|
|
153
|
+
if (existing === '') {
|
|
154
|
+
await execFileAsync('tmux', (0, tmux_session_surface_1.buildSetStatusRightArgs)(sessionName, tmux_session_surface_1.CM_STATUS_RIGHT_FORMAT), { timeout: TMUX_TIMEOUT });
|
|
155
|
+
}
|
|
156
|
+
return true;
|
|
157
|
+
}
|
|
158
|
+
catch (error) {
|
|
159
|
+
logger.debug('session-status:status-right-failed', {
|
|
160
|
+
sessionName,
|
|
161
|
+
error: error instanceof Error ? error.message : String(error),
|
|
162
|
+
});
|
|
163
|
+
return false;
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
/**
|
|
167
|
+
* Remove every `@cm_*` option and the session-scoped `status-right` we own.
|
|
168
|
+
*
|
|
169
|
+
* The `status-right` is only removed when it is still ours — a user who rebound
|
|
170
|
+
* it after us owns it now, and clobbering that would be the same rudeness
|
|
171
|
+
* {@link ensureSessionStatusLine} exists to avoid.
|
|
172
|
+
*
|
|
173
|
+
* @param sessionName - Target session
|
|
174
|
+
*/
|
|
175
|
+
async function clearSessionStatus(sessionName) {
|
|
176
|
+
published.delete(sessionName);
|
|
177
|
+
for (const option of tmux_session_surface_1.CM_SESSION_OPTIONS) {
|
|
178
|
+
try {
|
|
179
|
+
await execFileAsync('tmux', (0, tmux_session_surface_1.buildUnsetSessionOptionArgs)(sessionName, option), {
|
|
180
|
+
timeout: TMUX_TIMEOUT,
|
|
181
|
+
});
|
|
182
|
+
}
|
|
183
|
+
catch {
|
|
184
|
+
// Unsetting an option that was never set exits non-zero on some builds.
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
try {
|
|
188
|
+
const { stdout } = await execFileAsync('tmux', (0, tmux_session_surface_1.buildShowSessionOptionArgs)(sessionName, 'status-right'), { timeout: TMUX_TIMEOUT });
|
|
189
|
+
if (stdout.includes(tmux_session_surface_1.CM_STATUS_RIGHT_FORMAT)) {
|
|
190
|
+
await execFileAsync('tmux', (0, tmux_session_surface_1.buildUnsetStatusRightArgs)(sessionName), {
|
|
191
|
+
timeout: TMUX_TIMEOUT,
|
|
192
|
+
});
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
catch {
|
|
196
|
+
// Same reasoning as above: nothing here may fail a caller.
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
/**
|
|
200
|
+
* Forget a session's published state without touching tmux.
|
|
201
|
+
*
|
|
202
|
+
* Called when a session is known to be gone. The next session created under the
|
|
203
|
+
* same name then publishes from scratch instead of being deduped against the
|
|
204
|
+
* dead one's last status.
|
|
205
|
+
*/
|
|
206
|
+
function forgetSessionStatus(sessionName) {
|
|
207
|
+
published.delete(sessionName);
|
|
208
|
+
cleared.delete(sessionName);
|
|
209
|
+
}
|
|
210
|
+
/** Test seam: how many sessions currently have a memoised publication. */
|
|
211
|
+
function publishedSessionCount() {
|
|
212
|
+
return published.size;
|
|
213
|
+
}
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* Provides functions to manage tmux sessions for Claude CLI integration
|
|
5
5
|
*/
|
|
6
6
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
-
exports.NAVIGATION_KEY_VALUES = exports.SPECIAL_KEY_VALUES = void 0;
|
|
7
|
+
exports.REPAINT_INVALIDATE_DELAY_MS = exports.NAVIGATION_KEY_VALUES = exports.SPECIAL_KEY_VALUES = void 0;
|
|
8
8
|
exports.exactTarget = exactTarget;
|
|
9
9
|
exports.reconcileSessionGeometry = reconcileSessionGeometry;
|
|
10
10
|
exports.isTmuxAvailable = isTmuxAvailable;
|
|
@@ -28,6 +28,7 @@ exports.sendSpecialKeysAndInvalidate = sendSpecialKeysAndInvalidate;
|
|
|
28
28
|
const child_process_1 = require("child_process");
|
|
29
29
|
const util_1 = require("util");
|
|
30
30
|
const tmux_capture_cache_1 = require("./tmux-capture-cache");
|
|
31
|
+
const geometry_delegation_1 = require("./geometry-delegation");
|
|
31
32
|
const validation_1 = require("../../lib/cli-tools/validation");
|
|
32
33
|
const tmux_pane_config_1 = require("../../config/tmux-pane-config");
|
|
33
34
|
const logger_1 = require("../../lib/logger");
|
|
@@ -77,6 +78,24 @@ function exactTarget(sessionName) {
|
|
|
77
78
|
* Failures are intentionally non-fatal: geometry improves capture fidelity but
|
|
78
79
|
* must never make an otherwise healthy CLI session unusable.
|
|
79
80
|
*
|
|
81
|
+
* ## It stands down while a human is attached (Issue #2317, Phase D)
|
|
82
|
+
*
|
|
83
|
+
* `commandmate attach --live` hands the window to the terminal looking at it so
|
|
84
|
+
* the transcript is readable at all; every `send` to a running session reaches
|
|
85
|
+
* here through `reconcileExistingSession()`, and snapping the canvas back to
|
|
86
|
+
* 200x1000 mid-read would undo that under the reader's hands. So when the
|
|
87
|
+
* geometry does NOT already match, this asks whether a NON-control-mode client
|
|
88
|
+
* is attached and leaves the window alone if one is.
|
|
89
|
+
*
|
|
90
|
+
* Two properties of that ordering are load-bearing:
|
|
91
|
+
*
|
|
92
|
+
* - the question is asked only on the path that would actually resize, so the
|
|
93
|
+
* overwhelmingly common "already correct" call still costs exactly the two
|
|
94
|
+
* tmux round-trips it always has;
|
|
95
|
+
* - it keys off a live client rather than off `@cm_delegated`, so a flag left
|
|
96
|
+
* behind by a CLI that was killed can never pin a window open forever — the
|
|
97
|
+
* next reconcile with nobody attached repairs it.
|
|
98
|
+
*
|
|
80
99
|
* @returns true when at least one tmux option was changed.
|
|
81
100
|
*/
|
|
82
101
|
async function reconcileSessionGeometry(sessionName, options = {}) {
|
|
@@ -103,6 +122,13 @@ async function reconcileSessionGeometry(sessionName, options = {}) {
|
|
|
103
122
|
const sizeMatches = currentWidth === windowWidth && currentHeight === windowHeight;
|
|
104
123
|
if (modeMatches && sizeMatches)
|
|
105
124
|
return false;
|
|
125
|
+
// Issue #2317: a human is reading this pane at their terminal's size. See the
|
|
126
|
+
// docblock — asked here, after the cheap match, so the ordinary call is
|
|
127
|
+
// unchanged.
|
|
128
|
+
if ((options.respectAttachedClient ?? true) && (await (0, geometry_delegation_1.hasHumanClientAttached)(sessionName))) {
|
|
129
|
+
logger.info('session-geometry:delegated-to-client', { sessionName });
|
|
130
|
+
return false;
|
|
131
|
+
}
|
|
106
132
|
try {
|
|
107
133
|
if (!modeMatches) {
|
|
108
134
|
await execFileAsync('tmux', ['set-window-option', '-t', target, 'window-size', 'manual'], { timeout: DEFAULT_TIMEOUT });
|
|
@@ -316,7 +342,13 @@ async function createSession(sessionNameOrOptions, cwd) {
|
|
|
316
342
|
// replaced (verified: `show-window-options -v window-size` came back empty),
|
|
317
343
|
// so reconciling first would have the setting thrown away and leave the pane
|
|
318
344
|
// tracking the latest client again.
|
|
319
|
-
await reconcileSessionGeometry(sessionName, {
|
|
345
|
+
await reconcileSessionGeometry(sessionName, {
|
|
346
|
+
windowWidth,
|
|
347
|
+
windowHeight,
|
|
348
|
+
// Nothing can be attached to a session this call is still creating, so the
|
|
349
|
+
// Phase D client probe would be a round-trip with one possible answer.
|
|
350
|
+
respectAttachedClient: false,
|
|
351
|
+
});
|
|
320
352
|
}
|
|
321
353
|
catch (error) {
|
|
322
354
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
@@ -433,6 +465,20 @@ const ALLOWED_SPECIAL_KEYS = new Set([
|
|
|
433
465
|
// are tmux named keys; 'q' is the pager's literal "quit" character (sent verbatim by
|
|
434
466
|
// `tmux send-keys`, no injection risk — single fixed char via execFile, not a shell).
|
|
435
467
|
'PageUp', 'PageDown', 'Home', 'End', 'q',
|
|
468
|
+
// Issue #2254: the answer characters `1`-`9` / `y` / `n`, so a dialog nobody
|
|
469
|
+
// could parse can be answered from the chat surface's dialog card instead of
|
|
470
|
+
// only from the terminal. Literal characters on the wire like `q` above.
|
|
471
|
+
// `n` was already deliverable as an opencode chord letter and is not repeated.
|
|
472
|
+
'1', '2', '3', '4', '5', '6', '7', '8', '9', 'y',
|
|
473
|
+
// Issue #2297: claude's "use this session only" key. Its `/model` footer reads
|
|
474
|
+
// `Enter to set as default · s to use this session only · Esc to cancel`, and
|
|
475
|
+
// `Enter` there rewrites `model` in ~/.claude/settings.json (#1495) — so
|
|
476
|
+
// without this the chat surface could deliver the destructive half of that
|
|
477
|
+
// footer and not the safe half. A literal character like `q` / `y` above.
|
|
478
|
+
// Deliverable here for every session; only claude and Command Code DECLARE it
|
|
479
|
+
// (`CLAUDE_NAVIGATION_KEY_VALUES`), so the route answers 400 for anyone else —
|
|
480
|
+
// which matters, because `s` is `sort:relevance` on copilot's session picker.
|
|
481
|
+
's',
|
|
436
482
|
// Issue #2046: opencode's own chords. `C-x` is its leader prefix (measured
|
|
437
483
|
// default of 1.18.22, 2000 ms window), `C-p` opens the command palette and
|
|
438
484
|
// `C-t` cycles the model variant. The lower-case letters complete a leader
|
|
@@ -802,6 +848,36 @@ async function clearComposerLine(sessionName) {
|
|
|
802
848
|
function isAllowedSpecialKey(key, vocabulary = terminal_keys_1.NAVIGATION_KEY_VALUES) {
|
|
803
849
|
return vocabulary.includes(key) && isSendableSpecialKey(key);
|
|
804
850
|
}
|
|
851
|
+
/**
|
|
852
|
+
* How long after a key lands the TUI is still allowed to be repainting
|
|
853
|
+
* (Issue #2297).
|
|
854
|
+
*
|
|
855
|
+
* `invalidateCache()` fires the instant `tmux send-keys` returns, which is
|
|
856
|
+
* BEFORE the CLI has drawn the consequence of the key. The next capture — the
|
|
857
|
+
* chat surface's own `onKeysSent` refresh, or any of the pollers that share this
|
|
858
|
+
* cache (the sidebar status probe, the global session poller) — therefore has a
|
|
859
|
+
* good chance of storing the PRE-repaint frame, and {@link CACHE_TTL_MS} then
|
|
860
|
+
* serves that stale frame for five seconds. That is the "the highlight does not
|
|
861
|
+
* move" report in Issue #2297: the send worked, the cache was invalidated, and
|
|
862
|
+
* the surface still drew the old dialog.
|
|
863
|
+
*
|
|
864
|
+
* 250 ms is comfortably past an ink/bubbletea repaint (`SPECIAL_KEY_DELAY_MS`,
|
|
865
|
+
* the gap this transport already leaves BETWEEN keys of one chord, is 100 ms)
|
|
866
|
+
* and comfortably inside the 1-second budget the Issue sets for seeing the
|
|
867
|
+
* highlight move.
|
|
868
|
+
*/
|
|
869
|
+
exports.REPAINT_INVALIDATE_DELAY_MS = 250;
|
|
870
|
+
/**
|
|
871
|
+
* Drop the cached frame again once the TUI has had time to repaint.
|
|
872
|
+
*
|
|
873
|
+
* Fire-and-forget on purpose: the route must not wait 250 ms to answer, and the
|
|
874
|
+
* work is one `Map.delete`. The timer is `unref()`ed where the runtime supports
|
|
875
|
+
* it so a pending invalidation can never hold a process (or a test runner) open.
|
|
876
|
+
*/
|
|
877
|
+
function scheduleRepaintInvalidation(sessionName) {
|
|
878
|
+
const timer = setTimeout(() => (0, tmux_capture_cache_1.invalidateCache)(sessionName), exports.REPAINT_INVALIDATE_DELAY_MS);
|
|
879
|
+
timer.unref?.();
|
|
880
|
+
}
|
|
805
881
|
/**
|
|
806
882
|
* Send special keys to a tmux session and invalidate the capture cache.
|
|
807
883
|
* Wrapper combining sendSpecialKeys() + invalidateCache() for DRY (DR1-003).
|
|
@@ -812,4 +888,5 @@ function isAllowedSpecialKey(key, vocabulary = terminal_keys_1.NAVIGATION_KEY_VA
|
|
|
812
888
|
async function sendSpecialKeysAndInvalidate(sessionName, keys) {
|
|
813
889
|
await sendSpecialKeys(sessionName, keys);
|
|
814
890
|
(0, tmux_capture_cache_1.invalidateCache)(sessionName);
|
|
891
|
+
scheduleRepaintInvalidation(sessionName);
|
|
815
892
|
}
|
|
@@ -20,12 +20,15 @@ exports.isNewerVersion = isNewerVersion;
|
|
|
20
20
|
exports.getClientVersion = getClientVersion;
|
|
21
21
|
exports.getServerVersion = getServerVersion;
|
|
22
22
|
exports.getCurrentVersion = getCurrentVersion;
|
|
23
|
+
exports.getServedBundleVersion = getServedBundleVersion;
|
|
24
|
+
exports.resolveBundleDrift = resolveBundleDrift;
|
|
23
25
|
exports.validateReleaseUrl = validateReleaseUrl;
|
|
24
26
|
exports.sanitizeReleaseName = sanitizeReleaseName;
|
|
25
27
|
exports.checkForUpdate = checkForUpdate;
|
|
26
28
|
exports.resetCacheForTesting = resetCacheForTesting;
|
|
27
29
|
const fs_1 = require("fs");
|
|
28
30
|
const path_1 = require("path");
|
|
31
|
+
const types_1 = require("../lib/realtime/types");
|
|
29
32
|
// =============================================================================
|
|
30
33
|
// Constants
|
|
31
34
|
// =============================================================================
|
|
@@ -60,6 +63,17 @@ const FETCH_TIMEOUT_MS = 5000;
|
|
|
60
63
|
* project directory) leaking a foreign version.
|
|
61
64
|
*/
|
|
62
65
|
const PACKAGE_NAME = 'commandmate';
|
|
66
|
+
/**
|
|
67
|
+
* The manifest `next build` writes beside the client bundle, relative to the
|
|
68
|
+
* package root. [Issue #2271]
|
|
69
|
+
*
|
|
70
|
+
* It snapshots the *resolved* next.config — the `env` block included — so
|
|
71
|
+
* `config.env.NEXT_PUBLIC_APP_VERSION` is literally the string that build
|
|
72
|
+
* inlined into every browser bundle it emitted. That makes it the only file on
|
|
73
|
+
* disk that can answer "which version is the bundle we are serving?" without
|
|
74
|
+
* asking the bundle itself.
|
|
75
|
+
*/
|
|
76
|
+
const NEXT_BUILD_MANIFEST_PATH = ['.next', 'required-server-files.json'];
|
|
63
77
|
const cache = globalThis.__versionCheckCache ??
|
|
64
78
|
(globalThis.__versionCheckCache = {
|
|
65
79
|
result: null,
|
|
@@ -162,6 +176,99 @@ function getServerVersion() {
|
|
|
162
176
|
function getCurrentVersion() {
|
|
163
177
|
return getServerVersion();
|
|
164
178
|
}
|
|
179
|
+
// =============================================================================
|
|
180
|
+
// Served bundle identity (Issue #2271)
|
|
181
|
+
// =============================================================================
|
|
182
|
+
/**
|
|
183
|
+
* Memoised answer of {@link getServedBundleVersion}, resolved on first use and
|
|
184
|
+
* then frozen for the life of the process. [Issue #2271]
|
|
185
|
+
*
|
|
186
|
+
* Frozen on purpose. The question is "which build is this process serving?",
|
|
187
|
+
* and a running Next production server answers that once — at startup, from the
|
|
188
|
+
* `.next` it opened. Re-reading the manifest on every call would let a
|
|
189
|
+
* `next build` performed *underneath* the running server rename the served
|
|
190
|
+
* build without the server having reloaded a single byte of it, which is the
|
|
191
|
+
* false positive this Issue exists to remove, in a new disguise.
|
|
192
|
+
*/
|
|
193
|
+
let servedBundleVersion = null;
|
|
194
|
+
/**
|
|
195
|
+
* Read the version `next build` inlined into the client bundle, from the build
|
|
196
|
+
* manifest it left on disk. [Issue #2271]
|
|
197
|
+
*
|
|
198
|
+
* @returns The baked version, or null when the manifest is absent, unreadable,
|
|
199
|
+
* malformed, or predates the `env` block.
|
|
200
|
+
*/
|
|
201
|
+
function readServedBundleVersionFromBuild() {
|
|
202
|
+
try {
|
|
203
|
+
const raw = (0, fs_1.readFileSync)((0, path_1.join)(process.cwd(), ...NEXT_BUILD_MANIFEST_PATH), 'utf-8');
|
|
204
|
+
const parsed = JSON.parse(raw);
|
|
205
|
+
const baked = parsed.config?.env?.NEXT_PUBLIC_APP_VERSION;
|
|
206
|
+
if (typeof baked === 'string' && baked.length > 0)
|
|
207
|
+
return baked;
|
|
208
|
+
return null;
|
|
209
|
+
}
|
|
210
|
+
catch {
|
|
211
|
+
return null;
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
/** First-use resolution behind {@link getServedBundleVersion}'s memo. */
|
|
215
|
+
function resolveServedBundleVersion() {
|
|
216
|
+
// `next dev` compiles on demand and never writes the build manifest, but a
|
|
217
|
+
// stale one left by an earlier `npm run build` is still sitting in `.next` —
|
|
218
|
+
// and it names a version the dev bundle was never built from. Under dev the
|
|
219
|
+
// live next.config populates `process.env` for the server too, so the baked
|
|
220
|
+
// value already *is* the honest answer and the manifest must not be consulted.
|
|
221
|
+
if (process.env.NODE_ENV === 'production') {
|
|
222
|
+
const built = readServedBundleVersionFromBuild();
|
|
223
|
+
if (built)
|
|
224
|
+
return built;
|
|
225
|
+
}
|
|
226
|
+
return getClientVersion();
|
|
227
|
+
}
|
|
228
|
+
/**
|
|
229
|
+
* The version of the **client bundle this server is serving**. [Issue #2271]
|
|
230
|
+
*
|
|
231
|
+
* Deliberately *not* {@link getServerVersion}. That one reads package.json at
|
|
232
|
+
* runtime, which is the right answer for "is there a newer release on GitHub?"
|
|
233
|
+
* and the wrong one for "should this tab reload?": the release procedure bumps
|
|
234
|
+
* package.json on `develop` and never rebuilds the primary checkout (doing so
|
|
235
|
+
* would swap `.next` out from under the running server — see the release
|
|
236
|
+
* skill's Phase 2-3), so `package.json > bundle` is the steady state after
|
|
237
|
+
* every release. Comparing against it therefore told **every** tab, forever,
|
|
238
|
+
* that it was out of date.
|
|
239
|
+
*
|
|
240
|
+
* The honest comparand is the build the bundle came from, so this reads the
|
|
241
|
+
* `next build` manifest and falls back — never to package.json — to the baked
|
|
242
|
+
* `NEXT_PUBLIC_APP_VERSION`, and finally to `'0.0.0'`, which
|
|
243
|
+
* {@link isVersionMismatch} treats as "unknown" and never reports as drift.
|
|
244
|
+
*
|
|
245
|
+
* @returns Served bundle version string (e.g., "0.2.3"), or "0.0.0" as fallback
|
|
246
|
+
*/
|
|
247
|
+
function getServedBundleVersion() {
|
|
248
|
+
servedBundleVersion ??= resolveServedBundleVersion();
|
|
249
|
+
return servedBundleVersion;
|
|
250
|
+
}
|
|
251
|
+
/**
|
|
252
|
+
* Decide whether a tab running `clientBundleVersion` is on a stale build.
|
|
253
|
+
* [Issue #2271]
|
|
254
|
+
*
|
|
255
|
+
* The single seam the WebSocket version handshake calls, so "what counts as
|
|
256
|
+
* drift" has exactly one definition and one place to test it. Both sides are
|
|
257
|
+
* build identities: the client sends the `NEXT_PUBLIC_APP_VERSION` its bundle
|
|
258
|
+
* was compiled with, and this compares it against the version of the bundle
|
|
259
|
+
* being served — so a bare `package.json` bump moves neither side, and only a
|
|
260
|
+
* rebuild does.
|
|
261
|
+
*
|
|
262
|
+
* @param clientBundleVersion - The version the tab's bundle was built from
|
|
263
|
+
* @returns The drifted pair, or null when the two builds agree or either side
|
|
264
|
+
* is an unknown/fallback version
|
|
265
|
+
*/
|
|
266
|
+
function resolveBundleDrift(clientBundleVersion) {
|
|
267
|
+
const serverVersion = getServedBundleVersion();
|
|
268
|
+
if (!(0, types_1.isVersionMismatch)(serverVersion, clientBundleVersion))
|
|
269
|
+
return null;
|
|
270
|
+
return { serverVersion, clientVersion: clientBundleVersion };
|
|
271
|
+
}
|
|
165
272
|
/**
|
|
166
273
|
* Validate that a URL is a legitimate GitHub Releases URL.
|
|
167
274
|
* [SEC-SF-001] OWASP A03:2021 - Prevents injection via DNS pollution/MITM.
|
|
@@ -259,6 +366,9 @@ function resetCacheForTesting() {
|
|
|
259
366
|
cache.result = null;
|
|
260
367
|
cache.fetchedAt = 0;
|
|
261
368
|
cache.rateLimitResetAt = null;
|
|
369
|
+
// Issue #2271: the served-bundle memo is frozen for the life of a process, so
|
|
370
|
+
// a suite that moves process.cwd() between cases must be able to thaw it.
|
|
371
|
+
servedBundleVersion = null;
|
|
262
372
|
}
|
|
263
373
|
// =============================================================================
|
|
264
374
|
// Internal Functions
|
|
@@ -426,28 +426,34 @@ function handleMessage(ws, message) {
|
|
|
426
426
|
}
|
|
427
427
|
}
|
|
428
428
|
/**
|
|
429
|
-
* Handle a client-version hello (#1338/#1356).
|
|
429
|
+
* Handle a client-version hello (#1338/#1356/#2271).
|
|
430
430
|
*
|
|
431
|
-
* The client announces its
|
|
432
|
-
*
|
|
433
|
-
*
|
|
434
|
-
*
|
|
435
|
-
*
|
|
431
|
+
* The client announces the version its bundle was built from on every
|
|
432
|
+
* (re)connect. `resolveBundleDrift` compares that against the version of the
|
|
433
|
+
* bundle **this server is serving** and answers null unless the two builds
|
|
434
|
+
* really differ; on drift, reply with a version_mismatch event so the tab can
|
|
435
|
+
* surface a reload nudge.
|
|
436
|
+
*
|
|
437
|
+
* Issue #2271: this used to ask `getServerVersion()`, i.e. the installed
|
|
438
|
+
* package.json. Releases bump that file without rebuilding the running server's
|
|
439
|
+
* `.next` — by design — so the comparison was permanently unequal and every tab
|
|
440
|
+
* carried an undismissable banner. The resolver, not this handler, is where
|
|
441
|
+
* "what counts as drift" is defined; keep it that way.
|
|
436
442
|
*/
|
|
437
443
|
function handleClientVersion(ws, message) {
|
|
438
444
|
const clientVersion = typeof message.version === 'string' ? message.version : '';
|
|
439
445
|
if (!clientVersion)
|
|
440
446
|
return;
|
|
441
|
-
const
|
|
442
|
-
if (!
|
|
447
|
+
const drift = (0, version_checker_1.resolveBundleDrift)(clientVersion);
|
|
448
|
+
if (!drift)
|
|
443
449
|
return;
|
|
444
450
|
if (ws.readyState !== ws_1.WebSocket.OPEN)
|
|
445
451
|
return;
|
|
446
452
|
try {
|
|
447
453
|
ws.send(JSON.stringify({
|
|
448
454
|
type: types_2.VERSION_MISMATCH_EVENT_TYPE,
|
|
449
|
-
serverVersion,
|
|
450
|
-
clientVersion,
|
|
455
|
+
serverVersion: drift.serverVersion,
|
|
456
|
+
clientVersion: drift.clientVersion,
|
|
451
457
|
}));
|
|
452
458
|
}
|
|
453
459
|
catch (error) {
|