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,865 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Writing Command Code's own words into conversation history (Issue #2252,
|
|
4
|
+
* Epic #2249 Phase C).
|
|
5
|
+
*
|
|
6
|
+
* The fifth writer of `chat_messages` for an agent turn, on the terms
|
|
7
|
+
* `../opencode/history` established (#2041) and `../claude/history` generalised
|
|
8
|
+
* (#2121): the first writer is `lib/polling/response-checker`, which captures
|
|
9
|
+
* the pane and cleans it, and the two are mutually exclusive.
|
|
10
|
+
* `lib/polling/structured-history-gate` calls {@link captureCommandCodeTranscriptTurn}
|
|
11
|
+
* at the moment the scraped reply would be saved and saves it only if this
|
|
12
|
+
* returns false.
|
|
13
|
+
*
|
|
14
|
+
* ## Which file is this instance's, when the directory name is not computable
|
|
15
|
+
*
|
|
16
|
+
* This is the one place Command Code differs structurally from claude, and it is
|
|
17
|
+
* why Phase B (#2251) shipped `readCommandCodeTranscriptPath` for this Issue to
|
|
18
|
+
* pick up. claude's project directory is a pure function of `cwd`
|
|
19
|
+
* (`[^A-Za-z0-9] → -`, verified on 512 directories), so `../claude/history` can
|
|
20
|
+
* *compute* the path. Command Code's is `slugify(cwd)` from its own bundle, and
|
|
21
|
+
* the captured pair shows that slugify splits camel case as well —
|
|
22
|
+
* `…/MyCodeBranchDesk/probe` → `…-my-code-branch-desk-probe`. Reimplementing an
|
|
23
|
+
* unpublished slug is a rule that stops matching the day the tool changes it,
|
|
24
|
+
* and Epic #2249 決定 4 says not to.
|
|
25
|
+
*
|
|
26
|
+
* So the file is *found* rather than derived, in two steps:
|
|
27
|
+
*
|
|
28
|
+
* 1. **A hint, if the caller has one** — {@link acceptCommandCodeTranscriptHint}
|
|
29
|
+
* validates it against `~/.commandcode/projects` and `.jsonl` before anything
|
|
30
|
+
* opens it, which is what stops a value the agent printed naming
|
|
31
|
+
* `/etc/passwd`.
|
|
32
|
+
* 2. **A one-level scan of `~/.commandcode/projects/*` for
|
|
33
|
+
* `<session_id>.jsonl`.** The session id is a uuid the hooks already deliver
|
|
34
|
+
* on every event, and the directory holds one file per session, so this is
|
|
35
|
+
* exact rather than heuristic. Memoised per `(home, session)` and re-`stat`ed
|
|
36
|
+
* on every use, which is the shape `../codex/history` uses for its own scan.
|
|
37
|
+
*
|
|
38
|
+
* **The hint is not wired to the hook payload by this Issue, and that is
|
|
39
|
+
* deliberate rather than forgotten.** `transcript_path` arrives on the hook
|
|
40
|
+
* payload, and the only code that sees a raw payload *and* knows which instance
|
|
41
|
+
* sent it is `src/app/api/hooks/agent-event/route.ts`, which is outside this
|
|
42
|
+
* Issue's scope. Phase B says as much on {@link readCommandCodeTranscriptPath}.
|
|
43
|
+
* Rather than leave the reader unable to find its file until a route changes,
|
|
44
|
+
* step 2 makes the pointer sufficient on its own — and step 1 is implemented,
|
|
45
|
+
* tested and used for `capture.transcriptPathHint`, so the day the payload is
|
|
46
|
+
* plumbed the change is one line at the producer.
|
|
47
|
+
*
|
|
48
|
+
* ## What Issue #2304 measured about that seam
|
|
49
|
+
*
|
|
50
|
+
* The two halves are now joined by a test rather than by a paragraph.
|
|
51
|
+
* `tests/unit/hooks/sources/command-code-transcript-path-2304.test.ts` runs the
|
|
52
|
+
* real captured payloads through {@link readCommandCodeTranscriptPath} into
|
|
53
|
+
* `transcriptPathHint` and asserts the turn that comes out — against a **decoy**
|
|
54
|
+
* directory named exactly what claude's `[^A-Za-z0-9] → -` rule would produce
|
|
55
|
+
* for the same `cwd`, holding the same session id and a newer mtime. A reader
|
|
56
|
+
* that computed the slug and a reader that only scanned both write the decoy;
|
|
57
|
+
* only one that follows the hook's own path writes the right turn. The control
|
|
58
|
+
* is asserted too: take the hint away and the scan really does resolve to the
|
|
59
|
+
* decoy.
|
|
60
|
+
*
|
|
61
|
+
* Three live facts from that capture (Command Code 1.49.0, 2026-09-04):
|
|
62
|
+
*
|
|
63
|
+
* - **`transcript_path` is on all four events with one value for the session**,
|
|
64
|
+
* so a receiver may latch it off whichever it sees first. `cwd` is on all
|
|
65
|
+
* four as well — the temptation to compute the slug is *in the payload*, and
|
|
66
|
+
* Epic #2249 決定 4 is the answer to it.
|
|
67
|
+
* - **The slug still splits camel case**: `…/cwd/MyCodeBranchDesk/probe` →
|
|
68
|
+
* `…-cwd-my-code-branch-desk-probe`, where claude's rule would answer
|
|
69
|
+
* `-…-cwd-MyCodeBranchDesk-probe`. Two differences, both pinned by name.
|
|
70
|
+
* - **1.49.0 writes two new files beside the transcript**:
|
|
71
|
+
* `<session_id>.meta.json` and `<session_id>.checkpoints.jsonl`.
|
|
72
|
+
* {@link findCommandCodeTranscriptPath} matches `<session_id>.jsonl`
|
|
73
|
+
* exactly, so neither is a candidate — an `endsWith('.jsonl')` scan would
|
|
74
|
+
* have picked the checkpoints file up. It also passes
|
|
75
|
+
* {@link acceptCommandCodeTranscriptHint} (absolute, under the root,
|
|
76
|
+
* `.jsonl`), and the reason that is still safe is that its rows carry no
|
|
77
|
+
* `type`, so the parser declines all of them and this returns false. No
|
|
78
|
+
* fourth condition on the accept.
|
|
79
|
+
*
|
|
80
|
+
* ## Nothing here throws
|
|
81
|
+
*
|
|
82
|
+
* Same contract as its four siblings, and one more reason: a throw here would
|
|
83
|
+
* propagate into the poller's save path and could cost the scraped reply *as
|
|
84
|
+
* well as* the structured one. The database imports are dynamic so that
|
|
85
|
+
* `better-sqlite3` does not enter the module graph of everything that imports
|
|
86
|
+
* `@/lib/hooks/sources`.
|
|
87
|
+
*
|
|
88
|
+
* @module lib/hooks/sources/command-code/history
|
|
89
|
+
*/
|
|
90
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
91
|
+
if (k2 === undefined) k2 = k;
|
|
92
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
93
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
94
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
95
|
+
}
|
|
96
|
+
Object.defineProperty(o, k2, desc);
|
|
97
|
+
}) : (function(o, m, k, k2) {
|
|
98
|
+
if (k2 === undefined) k2 = k;
|
|
99
|
+
o[k2] = m[k];
|
|
100
|
+
}));
|
|
101
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
102
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
103
|
+
}) : function(o, v) {
|
|
104
|
+
o["default"] = v;
|
|
105
|
+
});
|
|
106
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
107
|
+
var ownKeys = function(o) {
|
|
108
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
109
|
+
var ar = [];
|
|
110
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
111
|
+
return ar;
|
|
112
|
+
};
|
|
113
|
+
return ownKeys(o);
|
|
114
|
+
};
|
|
115
|
+
return function (mod) {
|
|
116
|
+
if (mod && mod.__esModule) return mod;
|
|
117
|
+
var result = {};
|
|
118
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
119
|
+
__setModuleDefault(result, mod);
|
|
120
|
+
return result;
|
|
121
|
+
};
|
|
122
|
+
})();
|
|
123
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
124
|
+
exports.COMMAND_CODE_TURN_RECHECK_LIMIT = exports.COMMAND_CODE_TRANSCRIPT_TAIL_BYTES = void 0;
|
|
125
|
+
exports.resetCommandCodeTranscriptSessions = resetCommandCodeTranscriptSessions;
|
|
126
|
+
exports.resolveCommandCodeSessionId = resolveCommandCodeSessionId;
|
|
127
|
+
exports.commandCodeProjectsRoot = commandCodeProjectsRoot;
|
|
128
|
+
exports.acceptCommandCodeTranscriptHint = acceptCommandCodeTranscriptHint;
|
|
129
|
+
exports.findCommandCodeTranscriptPath = findCommandCodeTranscriptPath;
|
|
130
|
+
exports.captureCommandCodeTranscriptTurn = captureCommandCodeTranscriptTurn;
|
|
131
|
+
exports.resolveCommandCodeTranscriptPath = resolveCommandCodeTranscriptPath;
|
|
132
|
+
const promises_1 = require("fs/promises");
|
|
133
|
+
const os_1 = require("os");
|
|
134
|
+
const path_1 = require("path");
|
|
135
|
+
const auto_yes_state_1 = require("../../../../lib/auto-yes-state");
|
|
136
|
+
const transcript_tail_1 = require("../../../../lib/history/transcript-tail");
|
|
137
|
+
const user_turn_recorder_1 = require("../../../../lib/history/user-turn-recorder");
|
|
138
|
+
const logger_1 = require("../../../../lib/logger");
|
|
139
|
+
const agent_transcript_1 = require("../../../../types/agent-transcript");
|
|
140
|
+
const transcript_1 = require("./transcript");
|
|
141
|
+
const logger = (0, logger_1.createLogger)('lib/hooks/sources/command-code/history');
|
|
142
|
+
/**
|
|
143
|
+
* How much of the transcript's tail is read.
|
|
144
|
+
*
|
|
145
|
+
* The shared bound. Command Code's own files are small next to codex's — the
|
|
146
|
+
* captured sessions are single-digit kilobytes — but the file grows for the life
|
|
147
|
+
* of a session and the window is what makes "only turns whose prompt record is
|
|
148
|
+
* inside it are ever written" true, which is what stops a turn the window opened
|
|
149
|
+
* halfway through being written from its middle.
|
|
150
|
+
*/
|
|
151
|
+
exports.COMMAND_CODE_TRANSCRIPT_TAIL_BYTES = transcript_tail_1.TRANSCRIPT_TAIL_BYTES;
|
|
152
|
+
/**
|
|
153
|
+
* The shape a Command Code session id has, and the only shape this reader will
|
|
154
|
+
* look up.
|
|
155
|
+
*
|
|
156
|
+
* A uuid, which is what the tool mints (`newSession` calls its uuid generator).
|
|
157
|
+
* The check is not cosmetic: the value reaches a file-name comparison, and an id
|
|
158
|
+
* carrying `/` or `..` would otherwise be a path expression. Everything that
|
|
159
|
+
* fails it is treated as "no pointer", which falls through to the scraper.
|
|
160
|
+
*/
|
|
161
|
+
const COMMAND_CODE_SESSION_ID_PATTERN = /^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$/;
|
|
162
|
+
/**
|
|
163
|
+
* The last session id seen for each instance.
|
|
164
|
+
*
|
|
165
|
+
* On `globalThis` for the reason every shared map in this subsystem is (#1736):
|
|
166
|
+
* under `next dev` the poller's bundle and the hook receiver's bundle would each
|
|
167
|
+
* get a private copy of a module-scoped map, one would write and the other would
|
|
168
|
+
* read, and every lookup would answer null with no error at all.
|
|
169
|
+
*
|
|
170
|
+
* A latch and not a cache: `getLastAgentEvent` holds only the newest event, and
|
|
171
|
+
* Command Code sends `session_id` on all four of its events — but an event that
|
|
172
|
+
* did not would otherwise blank the pointer mid-session.
|
|
173
|
+
*/
|
|
174
|
+
const sessionPointers = (globalThis.__commandCodeTranscriptSessions ??= new Map());
|
|
175
|
+
/**
|
|
176
|
+
* Where each session id's transcript was found.
|
|
177
|
+
*
|
|
178
|
+
* Keyed by `<home>\0<sessionId>`, and a memo rather than a source of truth:
|
|
179
|
+
* every hit is re-`stat`ed before it is used. It exists because the lookup is a
|
|
180
|
+
* directory scan — the project directory name is `slugify(cwd)` and not
|
|
181
|
+
* derivable — and a scan on every finished turn is not something to do twice.
|
|
182
|
+
*/
|
|
183
|
+
const transcriptPaths = (globalThis.__commandCodeTranscriptPaths ??= new Map());
|
|
184
|
+
function keyOf(target) {
|
|
185
|
+
return (0, auto_yes_state_1.buildCompositeKey)(target.worktreeId, target.cliToolId, target.instanceId);
|
|
186
|
+
}
|
|
187
|
+
/** Forget every instance's pointer and every memoised path. Test seam. */
|
|
188
|
+
function resetCommandCodeTranscriptSessions() {
|
|
189
|
+
sessionPointers.clear();
|
|
190
|
+
transcriptPaths.clear();
|
|
191
|
+
}
|
|
192
|
+
/**
|
|
193
|
+
* The session id this instance's transcript is under, or null.
|
|
194
|
+
*
|
|
195
|
+
* Reads the structured event state first and falls back to the latched value.
|
|
196
|
+
* The import is dynamic so that `agent-event-state`'s module graph does not
|
|
197
|
+
* become a static dependency of the poller.
|
|
198
|
+
*
|
|
199
|
+
* **There is deliberately no fallback below this.** The obvious one — take the
|
|
200
|
+
* newest transcript whose `cwd` is this worktree — is wrong in the case the
|
|
201
|
+
* feature exists for: `command-code` and `command-code-2` in one worktree share
|
|
202
|
+
* a `cwd` and therefore a project directory, so the newest file is whichever of
|
|
203
|
+
* them answered last, and the primary's turn would be filed under the second's
|
|
204
|
+
* conversation. No pointer means the scraper keeps being the only record, which
|
|
205
|
+
* is merely the status quo.
|
|
206
|
+
*/
|
|
207
|
+
async function resolveCommandCodeSessionId(target) {
|
|
208
|
+
const key = keyOf(target);
|
|
209
|
+
try {
|
|
210
|
+
const { getLastAgentEvent } = await Promise.resolve().then(() => __importStar(require('../../../../lib/session/agent-event-state')));
|
|
211
|
+
const sessionId = getLastAgentEvent(target.worktreeId, target.cliToolId, target.instanceId)?.sessionId;
|
|
212
|
+
if (typeof sessionId === 'string' && sessionId.length > 0) {
|
|
213
|
+
sessionPointers.set(key, sessionId);
|
|
214
|
+
return sessionId;
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
catch (error) {
|
|
218
|
+
// A state module that cannot be reached is one that knows no session id.
|
|
219
|
+
logger.debug('command-code-transcript-session-lookup-failed', {
|
|
220
|
+
worktreeId: target.worktreeId,
|
|
221
|
+
instanceId: target.instanceId ?? target.cliToolId,
|
|
222
|
+
error: error instanceof Error ? error.message : String(error),
|
|
223
|
+
});
|
|
224
|
+
}
|
|
225
|
+
return sessionPointers.get(key) ?? null;
|
|
226
|
+
}
|
|
227
|
+
/** `<home>/.commandcode/projects`. */
|
|
228
|
+
function commandCodeProjectsRoot(homeDir) {
|
|
229
|
+
return (0, path_1.join)(homeDir, ...transcript_1.COMMAND_CODE_PROJECTS_DIR_SEGMENTS);
|
|
230
|
+
}
|
|
231
|
+
/**
|
|
232
|
+
* A transcript path named by something other than this module, accepted only if
|
|
233
|
+
* it really is one.
|
|
234
|
+
*
|
|
235
|
+
* The value this exists for is the hook payload's `transcript_path`, which is a
|
|
236
|
+
* string an agent process chose. Three conditions, all necessary and all the
|
|
237
|
+
* ones `acceptClaudeTranscriptHint` and `acceptCodexRolloutPath` apply: it must
|
|
238
|
+
* be under `~/.commandcode/projects`, so a crafted value cannot make this open
|
|
239
|
+
* `/etc/passwd`; it must end in `.jsonl`; and it must carry no NUL.
|
|
240
|
+
*
|
|
241
|
+
* Containment is checked on the **resolved** path so that `..` cannot climb out,
|
|
242
|
+
* and `resolve` is safe to use here — unlike in `validateHookCwd`, where the
|
|
243
|
+
* value is echoed back — because the resolved string is the only thing that is
|
|
244
|
+
* ever used.
|
|
245
|
+
*
|
|
246
|
+
* @returns The resolved path, or null when it is not acceptable
|
|
247
|
+
*/
|
|
248
|
+
function acceptCommandCodeTranscriptHint(homeDir, hint) {
|
|
249
|
+
if (!hint.endsWith(transcript_1.COMMAND_CODE_TRANSCRIPT_EXTENSION))
|
|
250
|
+
return null;
|
|
251
|
+
if (hint.includes('\0'))
|
|
252
|
+
return null;
|
|
253
|
+
const root = (0, path_1.resolve)(commandCodeProjectsRoot(homeDir));
|
|
254
|
+
const resolved = (0, path_1.resolve)(hint);
|
|
255
|
+
if (resolved !== root && !resolved.startsWith(root + path_1.sep))
|
|
256
|
+
return null;
|
|
257
|
+
return resolved;
|
|
258
|
+
}
|
|
259
|
+
/**
|
|
260
|
+
* Find this session's transcript under `~/.commandcode/projects`.
|
|
261
|
+
*
|
|
262
|
+
* One level: the layout is `projects/<slug>/<session_id>.jsonl` and the slug is
|
|
263
|
+
* the only unknown, so every child directory is asked whether it holds this
|
|
264
|
+
* session's file. Directories are visited newest-modified first, because the
|
|
265
|
+
* session being asked about is almost always the one whose project directory was
|
|
266
|
+
* touched last — which makes the common case one `stat` rather than N.
|
|
267
|
+
*
|
|
268
|
+
* Never throws: a root that does not exist is a session this reader cannot see,
|
|
269
|
+
* which is the same answer as a session with no file.
|
|
270
|
+
*
|
|
271
|
+
* @returns The absolute path, or null when nothing under the root matches
|
|
272
|
+
*/
|
|
273
|
+
async function findCommandCodeTranscriptPath(homeDir, sessionId) {
|
|
274
|
+
if (!COMMAND_CODE_SESSION_ID_PATTERN.test(sessionId))
|
|
275
|
+
return null;
|
|
276
|
+
const root = commandCodeProjectsRoot(homeDir);
|
|
277
|
+
let entries;
|
|
278
|
+
try {
|
|
279
|
+
entries = await (0, promises_1.readdir)(root, { withFileTypes: true });
|
|
280
|
+
}
|
|
281
|
+
catch {
|
|
282
|
+
return null;
|
|
283
|
+
}
|
|
284
|
+
const directories = [];
|
|
285
|
+
for (const entry of entries) {
|
|
286
|
+
if (!entry.isDirectory())
|
|
287
|
+
continue;
|
|
288
|
+
let modifiedAt = 0;
|
|
289
|
+
try {
|
|
290
|
+
modifiedAt = (await (0, promises_1.stat)((0, path_1.join)(root, entry.name))).mtimeMs;
|
|
291
|
+
}
|
|
292
|
+
catch {
|
|
293
|
+
// A directory that vanished between `readdir` and `stat` is one with no
|
|
294
|
+
// file in it as far as this scan is concerned; it still gets asked below,
|
|
295
|
+
// where the failure costs one `stat` and nothing else.
|
|
296
|
+
}
|
|
297
|
+
directories.push({ name: entry.name, modifiedAt });
|
|
298
|
+
}
|
|
299
|
+
directories.sort((a, b) => b.modifiedAt - a.modifiedAt);
|
|
300
|
+
const fileName = `${sessionId}${transcript_1.COMMAND_CODE_TRANSCRIPT_EXTENSION}`;
|
|
301
|
+
for (const directory of directories) {
|
|
302
|
+
const candidate = (0, path_1.join)(root, directory.name, fileName);
|
|
303
|
+
if (await isReadableFile(candidate))
|
|
304
|
+
return candidate;
|
|
305
|
+
}
|
|
306
|
+
return null;
|
|
307
|
+
}
|
|
308
|
+
/**
|
|
309
|
+
* Read this instance's unwritten turns out of its transcript and write them.
|
|
310
|
+
*
|
|
311
|
+
* **Every turn in the window that is not already a row**, oldest first, which is
|
|
312
|
+
* the shape Issue #2246 gave the pull readers; the reasoning is written out once
|
|
313
|
+
* on `../claude/history`'s `captureClaudeTranscriptTurn` and is not repeated
|
|
314
|
+
* here. The short version: this reader only runs when something judges a turn
|
|
315
|
+
* finished, so a missed judgement used to lose a turn permanently — by the next
|
|
316
|
+
* judgement, "the newest turn" had moved on.
|
|
317
|
+
*
|
|
318
|
+
* The return value is the poller's instruction. **True** means `chat_messages`
|
|
319
|
+
* holds this instance's newest turn as Markdown — because this call wrote it, or
|
|
320
|
+
* because an earlier read of the same finished turn already did — and the scrape
|
|
321
|
+
* must be dropped. **False** means it does not, for any reason at all: no
|
|
322
|
+
* session pointer, no file, an unreadable file, a window with no prompt in it,
|
|
323
|
+
* or a turn the agent has not finished writing. Everything that can go wrong
|
|
324
|
+
* answers false, which is the fail-open the acceptance criteria require.
|
|
325
|
+
*
|
|
326
|
+
* Never throws.
|
|
327
|
+
*
|
|
328
|
+
* @param target - The instance whose turn just ended
|
|
329
|
+
* @returns Whether History now holds this instance's newest turn as Markdown
|
|
330
|
+
*/
|
|
331
|
+
async function captureCommandCodeTranscriptTurn(target, capture) {
|
|
332
|
+
const instanceId = target.instanceId ?? target.cliToolId;
|
|
333
|
+
try {
|
|
334
|
+
const homeDir = capture.commandCodeHome ?? (0, os_1.homedir)();
|
|
335
|
+
const sessionId = await resolveCommandCodeSessionId(target);
|
|
336
|
+
const path = await locateCommandCodeTranscript(homeDir, capture, sessionId);
|
|
337
|
+
if (!path) {
|
|
338
|
+
logger.debug('command-code-transcript-unavailable', {
|
|
339
|
+
worktreeId: target.worktreeId,
|
|
340
|
+
instanceId,
|
|
341
|
+
sessionId,
|
|
342
|
+
reason: sessionId ? 'no-file' : 'no-session-pointer',
|
|
343
|
+
});
|
|
344
|
+
return false;
|
|
345
|
+
}
|
|
346
|
+
const text = await (0, transcript_tail_1.readTranscriptTail)(path);
|
|
347
|
+
if (text === null)
|
|
348
|
+
return false;
|
|
349
|
+
const parsed = (0, transcript_1.parseCommandCodeTranscript)(text);
|
|
350
|
+
const built = (0, transcript_1.buildCommandCodeTurns)(parsed.records, parsed.sessionId ?? sessionId ?? '');
|
|
351
|
+
if (built.turns.length === 0) {
|
|
352
|
+
logger.info('command-code-transcript-no-turn', {
|
|
353
|
+
worktreeId: target.worktreeId,
|
|
354
|
+
instanceId,
|
|
355
|
+
path,
|
|
356
|
+
records: parsed.records.length,
|
|
357
|
+
malformedLines: parsed.malformedLines,
|
|
358
|
+
orphanedAssistantRecords: built.orphanedAssistantRecords,
|
|
359
|
+
});
|
|
360
|
+
return false;
|
|
361
|
+
}
|
|
362
|
+
if (parsed.malformedLines > 0 ||
|
|
363
|
+
built.orphanedAssistantRecords > 0 ||
|
|
364
|
+
parsed.parentSession !== null) {
|
|
365
|
+
// All three are expected in small numbers and all three are the kind of
|
|
366
|
+
// thing that must be visible when they stop being small: a fragment at the
|
|
367
|
+
// tail of a file being appended to, the head of the window landing
|
|
368
|
+
// mid-turn, and — the one Issue #2252 scopes out by name — a session that
|
|
369
|
+
// was forked from another, whose records this reader makes no attempt to
|
|
370
|
+
// reattach to their original conversation.
|
|
371
|
+
logger.info('command-code-transcript-partial-read', {
|
|
372
|
+
worktreeId: target.worktreeId,
|
|
373
|
+
instanceId,
|
|
374
|
+
path,
|
|
375
|
+
malformedLines: parsed.malformedLines,
|
|
376
|
+
orphanedAssistantRecords: built.orphanedAssistantRecords,
|
|
377
|
+
unresolvedParentRecords: built.unresolvedParentRecords,
|
|
378
|
+
nonMessageRecords: built.nonMessageRecords,
|
|
379
|
+
parentSession: parsed.parentSession,
|
|
380
|
+
});
|
|
381
|
+
}
|
|
382
|
+
const pending = await selectUnwrittenCommandCodeTurns(target, built.turns);
|
|
383
|
+
// Before anything is written: the rows that are already there (#2264). This
|
|
384
|
+
// is deliberately ahead of the early return below, because "the newest turn
|
|
385
|
+
// is already a row" is the state a row saved from a half-flushed turn is
|
|
386
|
+
// stuck in — the reader answers true and does nothing while the transcript
|
|
387
|
+
// beside it holds the missing paragraph.
|
|
388
|
+
await refreshCommandCodeTurnRows(target, built.turns
|
|
389
|
+
.slice(0, built.turns.length - pending.turns.length)
|
|
390
|
+
.slice(-exports.COMMAND_CODE_TURN_RECHECK_LIMIT), path);
|
|
391
|
+
if (pending.turns.length === 0) {
|
|
392
|
+
logger.debug('command-code-transcript-turns-already-saved', {
|
|
393
|
+
worktreeId: target.worktreeId,
|
|
394
|
+
instanceId,
|
|
395
|
+
turnsInWindow: built.turns.length,
|
|
396
|
+
});
|
|
397
|
+
return true;
|
|
398
|
+
}
|
|
399
|
+
if (pending.turns.length > 1) {
|
|
400
|
+
logger.info('command-code-transcript-backfilling-turns', {
|
|
401
|
+
worktreeId: target.worktreeId,
|
|
402
|
+
instanceId,
|
|
403
|
+
path,
|
|
404
|
+
pendingTurns: pending.turns.length,
|
|
405
|
+
turnsInWindow: built.turns.length,
|
|
406
|
+
anchored: pending.anchored,
|
|
407
|
+
});
|
|
408
|
+
}
|
|
409
|
+
// Oldest first, and the ordering is load-bearing twice over: History sorts
|
|
410
|
+
// by timestamp, and each turn's `/send` row is adopted against the previous
|
|
411
|
+
// turn's start, so a later turn must not get to claim a row an earlier one
|
|
412
|
+
// is about to ask for.
|
|
413
|
+
//
|
|
414
|
+
// Every prompt row is recorded before any reply is written (Issue #2273).
|
|
415
|
+
// That is what lets a reply be dated at its turn's END without overtaking
|
|
416
|
+
// the NEXT turn's prompt: a queued `/send` row is written while this turn is
|
|
417
|
+
// still running, so its instant is only knowable once that row has been
|
|
418
|
+
// recorded. With one turn pending — the ordinary case — this is the single
|
|
419
|
+
// call it always was.
|
|
420
|
+
const userRows = [];
|
|
421
|
+
for (let index = 0; index < pending.turns.length; index += 1) {
|
|
422
|
+
const previousStartedAt = index === 0 ? pending.previousStartedAt : pending.turns[index - 1].startedAt;
|
|
423
|
+
userRows.push(await recordCommandCodeUserTurn(target, pending.turns[index], previousStartedAt));
|
|
424
|
+
}
|
|
425
|
+
const lastRecordAt = lastCommandCodeAssistantRecordAt(parsed.records);
|
|
426
|
+
let captured = false;
|
|
427
|
+
for (let index = 0; index < pending.turns.length; index += 1) {
|
|
428
|
+
const turn = pending.turns[index];
|
|
429
|
+
captured = await writeCommandCodeTurn(target, turn, (0, transcript_1.renderCommandCodeTurn)(turn), resolveAssistantTimestampMs(turn, userRows[index], lastRecordAt.get(turn.promptId) ?? 0, nextTurnOpensAt(pending.turns, userRows, index)), path);
|
|
430
|
+
}
|
|
431
|
+
return captured;
|
|
432
|
+
}
|
|
433
|
+
catch (error) {
|
|
434
|
+
logger.error('command-code-transcript-capture-failed', {
|
|
435
|
+
worktreeId: target.worktreeId,
|
|
436
|
+
instanceId,
|
|
437
|
+
error: error instanceof Error ? error.message : String(error),
|
|
438
|
+
});
|
|
439
|
+
return false;
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
/**
|
|
443
|
+
* The turns {@link captureCommandCodeTranscriptTurn} still has to write (#2246).
|
|
444
|
+
*
|
|
445
|
+
* The search runs **backwards from the newest turn** and stops at the first one
|
|
446
|
+
* that is already a row. That turn is the *anchor*, and everything after it is
|
|
447
|
+
* pending. The comparison is the transcript's own record order and never a
|
|
448
|
+
* timestamp — a turn's assistant row is dated one millisecond after its user row
|
|
449
|
+
* ({@link resolveAssistantTimestampMs}), so the rows carry no ordering that
|
|
450
|
+
* could be trusted for this.
|
|
451
|
+
*
|
|
452
|
+
* A window with no anchor answers with the newest turn alone, which is #2121's
|
|
453
|
+
* behaviour and the right one: the earlier turns really are the scraper's, and
|
|
454
|
+
* there is no evidence in the window that says otherwise.
|
|
455
|
+
*/
|
|
456
|
+
async function selectUnwrittenCommandCodeTurns(target, turns) {
|
|
457
|
+
const [{ getDbInstance }, { findMessageByRequestId }] = await Promise.all([
|
|
458
|
+
Promise.resolve().then(() => __importStar(require('../../../../lib/db/db-instance'))),
|
|
459
|
+
Promise.resolve().then(() => __importStar(require('../../../../lib/db'))),
|
|
460
|
+
]);
|
|
461
|
+
const db = getDbInstance();
|
|
462
|
+
for (let index = turns.length - 1; index >= 0; index -= 1) {
|
|
463
|
+
const requestId = (0, agent_transcript_1.commandCodeTurnRequestId)(turns[index].promptId);
|
|
464
|
+
if (!findMessageByRequestId(db, target.worktreeId, requestId))
|
|
465
|
+
continue;
|
|
466
|
+
return {
|
|
467
|
+
turns: turns.slice(index + 1),
|
|
468
|
+
previousStartedAt: turns[index].startedAt,
|
|
469
|
+
anchored: true,
|
|
470
|
+
};
|
|
471
|
+
}
|
|
472
|
+
return {
|
|
473
|
+
turns: turns.slice(-1),
|
|
474
|
+
previousStartedAt: turns.length > 1 ? turns[turns.length - 2].startedAt : 0,
|
|
475
|
+
anchored: false,
|
|
476
|
+
};
|
|
477
|
+
}
|
|
478
|
+
/**
|
|
479
|
+
* The transcript file this instance would be read from, or null (Issue #2246).
|
|
480
|
+
*
|
|
481
|
+
* The same two steps {@link captureCommandCodeTranscriptTurn} opens with, asked
|
|
482
|
+
* without reading or writing anything. The Stop receiver uses it to decide
|
|
483
|
+
* whether waiting half a second and asking again could possibly help: a session
|
|
484
|
+
* with no transcript to name will not have grown one by then.
|
|
485
|
+
*
|
|
486
|
+
* Never throws.
|
|
487
|
+
*/
|
|
488
|
+
async function resolveCommandCodeTranscriptPath(target, capture) {
|
|
489
|
+
try {
|
|
490
|
+
const homeDir = capture.commandCodeHome ?? (0, os_1.homedir)();
|
|
491
|
+
return await locateCommandCodeTranscript(homeDir, capture, await resolveCommandCodeSessionId(target));
|
|
492
|
+
}
|
|
493
|
+
catch {
|
|
494
|
+
return null;
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
/**
|
|
498
|
+
* The transcript file for this instance, or null.
|
|
499
|
+
*
|
|
500
|
+
* The hint first because it names the file outright, the scan second. Both are
|
|
501
|
+
* checked against the filesystem rather than trusted, and the memo is re-`stat`ed
|
|
502
|
+
* for the same reason: "the path we would use" and "the path that exists" differ
|
|
503
|
+
* for every session that has been deleted, renamed or never persisted at all
|
|
504
|
+
* (`--no-session` writes nothing here, which is measured).
|
|
505
|
+
*/
|
|
506
|
+
async function locateCommandCodeTranscript(homeDir, capture, sessionId) {
|
|
507
|
+
const hint = capture.transcriptPathHint;
|
|
508
|
+
if (typeof hint === 'string' && hint.length > 0) {
|
|
509
|
+
const accepted = acceptCommandCodeTranscriptHint(homeDir, hint);
|
|
510
|
+
if (accepted && (await isReadableFile(accepted)))
|
|
511
|
+
return accepted;
|
|
512
|
+
}
|
|
513
|
+
if (!sessionId)
|
|
514
|
+
return null;
|
|
515
|
+
const memoKey = `${homeDir}\0${sessionId}`;
|
|
516
|
+
const memoised = transcriptPaths.get(memoKey);
|
|
517
|
+
if (memoised && (await isReadableFile(memoised)))
|
|
518
|
+
return memoised;
|
|
519
|
+
const found = await findCommandCodeTranscriptPath(homeDir, sessionId);
|
|
520
|
+
if (found)
|
|
521
|
+
transcriptPaths.set(memoKey, found);
|
|
522
|
+
else
|
|
523
|
+
transcriptPaths.delete(memoKey);
|
|
524
|
+
return found;
|
|
525
|
+
}
|
|
526
|
+
async function isReadableFile(path) {
|
|
527
|
+
try {
|
|
528
|
+
return (await (0, promises_1.stat)(path)).isFile();
|
|
529
|
+
}
|
|
530
|
+
catch {
|
|
531
|
+
return false;
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
/**
|
|
535
|
+
* Record the prompt this turn answers, before the reply is written (#2196).
|
|
536
|
+
*
|
|
537
|
+
* Ordering is the reason this is a separate call and not a branch inside
|
|
538
|
+
* {@link writeCommandCodeTurn}: the user row has to exist *before* the assistant
|
|
539
|
+
* row so that {@link resolveAssistantTimestampMs} can put the reply after it,
|
|
540
|
+
* and so that a browser watching the broadcast sees a prompt appear and then an
|
|
541
|
+
* answer rather than the other way round.
|
|
542
|
+
*
|
|
543
|
+
* A turn whose prompt was not the operator's — a `steering` or `followup`
|
|
544
|
+
* message the agent loop appended; see `isCommandCodeOperatorPromptRecord` — is
|
|
545
|
+
* skipped and said so in the log. The assistant row is still written: the agent
|
|
546
|
+
* really did reply.
|
|
547
|
+
*
|
|
548
|
+
* Never throws; `recordUserTurn` reports its failures in the return value.
|
|
549
|
+
*
|
|
550
|
+
* @param previousStartedAt - When the turn before this one opened, or 0. Widens
|
|
551
|
+
* `/send` adoption backwards to that instant (#2246); never narrows.
|
|
552
|
+
*/
|
|
553
|
+
async function recordCommandCodeUserTurn(target, turn, previousStartedAt = 0) {
|
|
554
|
+
const instanceId = target.instanceId ?? target.cliToolId;
|
|
555
|
+
if (!turn.promptIsOperatorInput) {
|
|
556
|
+
logger.debug('command-code-transcript-user-turn-skipped', {
|
|
557
|
+
worktreeId: target.worktreeId,
|
|
558
|
+
instanceId,
|
|
559
|
+
promptId: turn.promptId,
|
|
560
|
+
reason: 'not-operator-input',
|
|
561
|
+
});
|
|
562
|
+
return { outcome: 'skipped', messageId: null, timestampMs: null };
|
|
563
|
+
}
|
|
564
|
+
const adoption = previousStartedAt > 0 ? { adoptionFromMs: previousStartedAt } : {};
|
|
565
|
+
const recorded = await (0, user_turn_recorder_1.recordUserTurn)(target, (0, agent_transcript_1.commandCodePromptRequestId)(turn.promptId), turn.promptText, turn.startedAt, adoption);
|
|
566
|
+
if (recorded.outcome === 'failed') {
|
|
567
|
+
logger.warn('command-code-transcript-user-turn-failed', {
|
|
568
|
+
worktreeId: target.worktreeId,
|
|
569
|
+
instanceId,
|
|
570
|
+
sessionId: turn.sessionId,
|
|
571
|
+
promptId: turn.promptId,
|
|
572
|
+
});
|
|
573
|
+
}
|
|
574
|
+
return recorded;
|
|
575
|
+
}
|
|
576
|
+
/**
|
|
577
|
+
* When the assistant row for this turn is dated.
|
|
578
|
+
*
|
|
579
|
+
* **The turn's LAST assistant record, not its prompt (Issue #2273).** #2252
|
|
580
|
+
* dated the reply by the prompt record's own clock — `meta.createdAt`, the
|
|
581
|
+
* instant the message was made rather than the instant Command Code's buffered
|
|
582
|
+
* store appended it — and #2196 moved it one millisecond on so that
|
|
583
|
+
* `groupMessagesIntoPairs`, which orders by timestamp and nothing else, could
|
|
584
|
+
* never return the answer above the question. Both properties survive:
|
|
585
|
+
* `earliest` is a floor this never goes under.
|
|
586
|
+
*
|
|
587
|
+
* What neither accounted for is the row that lands BETWEEN the prompt and the
|
|
588
|
+
* reply. A tool approval is written when the dialog appears, seconds into the
|
|
589
|
+
* turn, and the chat surface orders its rows by timestamp — so a reply dated at
|
|
590
|
+
* the turn's start draws ABOVE an approval that really happened before it. The
|
|
591
|
+
* turn's last assistant record is the moment the reply was finished, so
|
|
592
|
+
* everything the turn produced on the way sorts before it.
|
|
593
|
+
*
|
|
594
|
+
* `nextTurnOpensAt` is the ceiling. The next turn's prompt row may be a `/send`
|
|
595
|
+
* row written while THIS turn was still running — a queued prompt — and a reply
|
|
596
|
+
* that overtook it would be paired with the wrong question.
|
|
597
|
+
*
|
|
598
|
+
* @param lastRecordAt - Epoch ms of the turn's last assistant record, or 0
|
|
599
|
+
* @param nextTurnOpensAt - Epoch ms of the next turn's user row, or null when
|
|
600
|
+
* this is the newest turn in the window
|
|
601
|
+
*/
|
|
602
|
+
function resolveAssistantTimestampMs(turn, userRow, lastRecordAt = 0, nextTurnOpensAt = null) {
|
|
603
|
+
const earliest = userRow.timestampMs === null
|
|
604
|
+
? turn.startedAt
|
|
605
|
+
: Math.max(turn.startedAt, userRow.timestampMs + 1);
|
|
606
|
+
const latest = nextTurnOpensAt === null ? Number.POSITIVE_INFINITY : nextTurnOpensAt - 1;
|
|
607
|
+
return Math.max(earliest, Math.min(lastRecordAt, latest));
|
|
608
|
+
}
|
|
609
|
+
/**
|
|
610
|
+
* When each turn's last assistant record was written (Issue #2273).
|
|
611
|
+
*
|
|
612
|
+
* Keyed by `promptId`, which is the turn key {@link buildCommandCodeTurns} uses,
|
|
613
|
+
* and walked over the same records with the same rules — the session header and
|
|
614
|
+
* every non-`message` entry skipped, a prompt record opening a turn — so the two
|
|
615
|
+
* passes cannot disagree about which turn a record belongs to. It is a second
|
|
616
|
+
* pass rather than a field on the accumulator because `./transcript` is not this
|
|
617
|
+
* Issue's to change.
|
|
618
|
+
*
|
|
619
|
+
* Only `assistant` records count. A turn's `user` records are its tool RESULTS,
|
|
620
|
+
* and the last of those is a step the agent took before it had finished
|
|
621
|
+
* answering; the reply is dated by the agent's last word.
|
|
622
|
+
*
|
|
623
|
+
* @returns `promptId` → epoch ms; absent for a turn with no timestamped
|
|
624
|
+
* assistant record
|
|
625
|
+
*/
|
|
626
|
+
function lastCommandCodeAssistantRecordAt(records) {
|
|
627
|
+
const at = new Map();
|
|
628
|
+
let key = null;
|
|
629
|
+
for (const record of records) {
|
|
630
|
+
if (record.sessionId)
|
|
631
|
+
continue;
|
|
632
|
+
if (record.type !== transcript_1.COMMAND_CODE_MESSAGE_RECORD_TYPE)
|
|
633
|
+
continue;
|
|
634
|
+
if ((0, transcript_1.isCommandCodePromptRecord)(record)) {
|
|
635
|
+
key = record.id;
|
|
636
|
+
continue;
|
|
637
|
+
}
|
|
638
|
+
if (record.role !== 'assistant' || key === null || record.timestampMs === null)
|
|
639
|
+
continue;
|
|
640
|
+
if (record.timestampMs > (at.get(key) ?? 0))
|
|
641
|
+
at.set(key, record.timestampMs);
|
|
642
|
+
}
|
|
643
|
+
return at;
|
|
644
|
+
}
|
|
645
|
+
/**
|
|
646
|
+
* The instant the next pending turn's prompt row carries, or null (Issue #2273).
|
|
647
|
+
*
|
|
648
|
+
* The user row's own timestamp when there is one, because that is what History
|
|
649
|
+
* sorts on and it can be EARLIER than the turn's start — an adopted `/send` row
|
|
650
|
+
* was written when CommandMate handed the text to the pane, which for a queued
|
|
651
|
+
* prompt is while the previous turn was still running. The turn's start is the
|
|
652
|
+
* fallback for a turn that produced no row at all.
|
|
653
|
+
*/
|
|
654
|
+
function nextTurnOpensAt(turns, userRows, index) {
|
|
655
|
+
const next = turns[index + 1];
|
|
656
|
+
if (!next)
|
|
657
|
+
return null;
|
|
658
|
+
return userRows[index + 1]?.timestampMs ?? next.startedAt;
|
|
659
|
+
}
|
|
660
|
+
/**
|
|
661
|
+
* How many already-written turns are re-rendered and compared (Issue #2264).
|
|
662
|
+
*
|
|
663
|
+
* The repair half of #2264 has to look at rows the reader has **already**
|
|
664
|
+
* written, which is the one thing the anchor rule deliberately does not do — so
|
|
665
|
+
* it is bounded here rather than by the window. Three, the same as claude's:
|
|
666
|
+
* the poller runs every two seconds and re-rendering the whole window on every
|
|
667
|
+
* tick would turn a repair into the most expensive thing the poll does, while a
|
|
668
|
+
* row that is going to grow grows within a second of being written.
|
|
669
|
+
*/
|
|
670
|
+
exports.COMMAND_CODE_TURN_RECHECK_LIMIT = 3;
|
|
671
|
+
/**
|
|
672
|
+
* Replace a saved row whose body has since grown (Issue #2264).
|
|
673
|
+
*
|
|
674
|
+
* The second half of the fix, and the half that repairs what the first half only
|
|
675
|
+
* stops happening again. A row keyed `command-code-turn:<id>` is written once and
|
|
676
|
+
* every later read answers "already saved", so a row saved from a turn whose last
|
|
677
|
+
* record had not been flushed yet would be frozen short — and the scrape that
|
|
678
|
+
* could have replaced it is suppressed by that same idempotency answer.
|
|
679
|
+
*
|
|
680
|
+
* **Strictly longer, never merely different.** Equality is the ordinary case and
|
|
681
|
+
* must cost nothing, and a body that got *shorter* between two reads is not a
|
|
682
|
+
* turn that grew — it is a window that slid, or a truncation marker — and
|
|
683
|
+
* overwriting a full reply with a shorter one is the one outcome worse than the
|
|
684
|
+
* bug. Longer implies different, so one comparison covers both halves.
|
|
685
|
+
*
|
|
686
|
+
* `message_updated`, never `message`: the row already existed and was already
|
|
687
|
+
* delivered when it was created (#2195), so a client that appended instead of
|
|
688
|
+
* replacing would show the reply twice.
|
|
689
|
+
*
|
|
690
|
+
* @param existing - The row `findMessageByRequestId` answered with
|
|
691
|
+
* @returns Whether the row was replaced
|
|
692
|
+
*/
|
|
693
|
+
async function growCommandCodeTurnRow(target, existing, rendered, path) {
|
|
694
|
+
const instanceId = target.instanceId ?? target.cliToolId;
|
|
695
|
+
const previousLength = existing.content.length;
|
|
696
|
+
if (rendered.body.length <= previousLength)
|
|
697
|
+
return false;
|
|
698
|
+
const [{ getDbInstance }, { updateMessageContent }, { broadcastMessage }] = await Promise.all([
|
|
699
|
+
Promise.resolve().then(() => __importStar(require('../../../../lib/db/db-instance'))),
|
|
700
|
+
Promise.resolve().then(() => __importStar(require('../../../../lib/db'))),
|
|
701
|
+
Promise.resolve().then(() => __importStar(require('../../../../lib/ws-server'))),
|
|
702
|
+
]);
|
|
703
|
+
updateMessageContent(getDbInstance(), existing.id, rendered.body);
|
|
704
|
+
broadcastMessage('message_updated', {
|
|
705
|
+
worktreeId: target.worktreeId,
|
|
706
|
+
message: { ...existing, content: rendered.body },
|
|
707
|
+
});
|
|
708
|
+
logger.info('command-code-transcript-turn-updated', {
|
|
709
|
+
worktreeId: target.worktreeId,
|
|
710
|
+
instanceId,
|
|
711
|
+
sessionId: rendered.sessionId,
|
|
712
|
+
requestId: existing.requestId,
|
|
713
|
+
path,
|
|
714
|
+
previousLength,
|
|
715
|
+
bodyLength: rendered.body.length,
|
|
716
|
+
textBlocks: rendered.textBlocks,
|
|
717
|
+
toolBlocks: rendered.toolBlocks,
|
|
718
|
+
});
|
|
719
|
+
return true;
|
|
720
|
+
}
|
|
721
|
+
/**
|
|
722
|
+
* Re-read the newest already-written turns and grow the short ones (#2264).
|
|
723
|
+
*
|
|
724
|
+
* Runs before the pending turns are written and independently of whether there
|
|
725
|
+
* are any — the case it exists for is precisely the one
|
|
726
|
+
* {@link captureCommandCodeTranscriptTurn} would otherwise return `true` from
|
|
727
|
+
* without doing anything.
|
|
728
|
+
*
|
|
729
|
+
* Turns that are still open are skipped rather than compared. Their body is by
|
|
730
|
+
* definition not the final one, and a repair that raced the agent would rewrite
|
|
731
|
+
* the row on every poll of a long turn.
|
|
732
|
+
*
|
|
733
|
+
* The database is asked before the turn is rendered, so a candidate with no row
|
|
734
|
+
* — every candidate, in a session this reader has never written to — costs one
|
|
735
|
+
* indexed lookup and no Markdown.
|
|
736
|
+
*
|
|
737
|
+
* @param candidates - Already-written turns, oldest first, at most
|
|
738
|
+
* {@link COMMAND_CODE_TURN_RECHECK_LIMIT}
|
|
739
|
+
* @returns How many rows were replaced
|
|
740
|
+
*/
|
|
741
|
+
async function refreshCommandCodeTurnRows(target, candidates, path) {
|
|
742
|
+
if (candidates.length === 0)
|
|
743
|
+
return 0;
|
|
744
|
+
const [{ getDbInstance }, { findMessageByRequestId }] = await Promise.all([
|
|
745
|
+
Promise.resolve().then(() => __importStar(require('../../../../lib/db/db-instance'))),
|
|
746
|
+
Promise.resolve().then(() => __importStar(require('../../../../lib/db'))),
|
|
747
|
+
]);
|
|
748
|
+
const db = getDbInstance();
|
|
749
|
+
let updated = 0;
|
|
750
|
+
for (const turn of candidates) {
|
|
751
|
+
if (!(0, transcript_1.isCommandCodeTurnWritable)(turn))
|
|
752
|
+
continue;
|
|
753
|
+
const existing = findMessageByRequestId(db, target.worktreeId, (0, agent_transcript_1.commandCodeTurnRequestId)(turn.promptId));
|
|
754
|
+
if (!existing)
|
|
755
|
+
continue;
|
|
756
|
+
if (await growCommandCodeTurnRow(target, existing, (0, transcript_1.renderCommandCodeTurn)(turn), path)) {
|
|
757
|
+
updated += 1;
|
|
758
|
+
}
|
|
759
|
+
}
|
|
760
|
+
return updated;
|
|
761
|
+
}
|
|
762
|
+
/**
|
|
763
|
+
* Write one rendered turn, unless it is already there.
|
|
764
|
+
*
|
|
765
|
+
* `findMessageByRequestId` is both the idempotency check and the reason a repeat
|
|
766
|
+
* read does not duplicate the row: the id is derived from the prompt record's
|
|
767
|
+
* `id`, which does not change between reads of the same file.
|
|
768
|
+
*
|
|
769
|
+
* Answering **true** for a turn that was already saved is deliberate. It means
|
|
770
|
+
* "History holds this turn as Markdown", which is exactly what the poller needs
|
|
771
|
+
* to know — a second poll of the same finished turn must not save the pane's
|
|
772
|
+
* copy on top of the row this path wrote for it.
|
|
773
|
+
*
|
|
774
|
+
* @returns Whether History holds this turn as the agent's own Markdown
|
|
775
|
+
*/
|
|
776
|
+
async function writeCommandCodeTurn(target, turn, rendered, timestampMs, path) {
|
|
777
|
+
const instanceId = target.instanceId ?? target.cliToolId;
|
|
778
|
+
if (!(0, transcript_1.isCommandCodeTurnWritable)(turn)) {
|
|
779
|
+
// The agent's last word in this turn reached for a tool and no later prompt
|
|
780
|
+
// has taken over, so what is in the file is a turn in progress. Writing it
|
|
781
|
+
// would put a reply with its last paragraph missing into History
|
|
782
|
+
// **permanently** — the row is keyed on the prompt's `id`, so every later
|
|
783
|
+
// read finds it and answers "already saved" — and the emptiness guard below
|
|
784
|
+
// cannot see the difference, because a turn cut off after its tool calls
|
|
785
|
+
// renders a *non-empty* body. That is Issue #2264, reproduced here before it
|
|
786
|
+
// could be reported: the measured `-p --session` resume leaves exactly this
|
|
787
|
+
// shape on disk, a prompt and a tool call with the reply never flushed.
|
|
788
|
+
//
|
|
789
|
+
// Handing it back to the scraper costs the Markdown rendering for this turn
|
|
790
|
+
// and nothing else: the Stop receiver asks again after a short delay, and
|
|
791
|
+
// the poller asks again when the pane returns to the composer.
|
|
792
|
+
logger.info('command-code-transcript-turn-open', {
|
|
793
|
+
worktreeId: target.worktreeId,
|
|
794
|
+
instanceId,
|
|
795
|
+
sessionId: rendered.sessionId,
|
|
796
|
+
promptId: rendered.promptId,
|
|
797
|
+
assistantRecords: turn.assistantRecords,
|
|
798
|
+
textBlocks: rendered.textBlocks,
|
|
799
|
+
toolBlocks: rendered.toolBlocks,
|
|
800
|
+
});
|
|
801
|
+
return false;
|
|
802
|
+
}
|
|
803
|
+
if (rendered.body.length === 0) {
|
|
804
|
+
// The prompt record is there and the assistant records are not. Answering
|
|
805
|
+
// false hands the turn back to the scraper, which is the only correct
|
|
806
|
+
// answer: an empty row would show as a blank reply forever, and suppressing
|
|
807
|
+
// the scrape would lose the reply outright.
|
|
808
|
+
logger.info('command-code-transcript-turn-empty', {
|
|
809
|
+
worktreeId: target.worktreeId,
|
|
810
|
+
instanceId,
|
|
811
|
+
sessionId: rendered.sessionId,
|
|
812
|
+
promptId: rendered.promptId,
|
|
813
|
+
});
|
|
814
|
+
return false;
|
|
815
|
+
}
|
|
816
|
+
if (rendered.unknownBlockTypes.length > 0) {
|
|
817
|
+
logger.info('command-code-transcript-unknown-blocks', {
|
|
818
|
+
worktreeId: target.worktreeId,
|
|
819
|
+
instanceId,
|
|
820
|
+
sessionId: rendered.sessionId,
|
|
821
|
+
blockTypes: rendered.unknownBlockTypes,
|
|
822
|
+
});
|
|
823
|
+
}
|
|
824
|
+
const requestId = (0, agent_transcript_1.commandCodeTurnRequestId)(rendered.promptId);
|
|
825
|
+
const [{ getDbInstance }, { createMessage, findMessageByRequestId }, { broadcastMessage }] = await Promise.all([
|
|
826
|
+
Promise.resolve().then(() => __importStar(require('../../../../lib/db/db-instance'))),
|
|
827
|
+
Promise.resolve().then(() => __importStar(require('../../../../lib/db'))),
|
|
828
|
+
Promise.resolve().then(() => __importStar(require('../../../../lib/ws-server'))),
|
|
829
|
+
]);
|
|
830
|
+
const db = getDbInstance();
|
|
831
|
+
const existing = findMessageByRequestId(db, target.worktreeId, requestId);
|
|
832
|
+
if (existing) {
|
|
833
|
+
logger.debug('command-code-transcript-turn-already-saved', {
|
|
834
|
+
worktreeId: target.worktreeId,
|
|
835
|
+
instanceId,
|
|
836
|
+
requestId,
|
|
837
|
+
});
|
|
838
|
+
// The row may still be one of the short ones #2264 was reported for, and
|
|
839
|
+
// this is the one place that knows both the row and the turn.
|
|
840
|
+
await growCommandCodeTurnRow(target, existing, rendered, path);
|
|
841
|
+
return true;
|
|
842
|
+
}
|
|
843
|
+
const message = createMessage(db, {
|
|
844
|
+
worktreeId: target.worktreeId,
|
|
845
|
+
role: 'assistant',
|
|
846
|
+
content: rendered.body,
|
|
847
|
+
messageType: 'normal',
|
|
848
|
+
timestamp: new Date(timestampMs > 0 ? timestampMs : Date.now()),
|
|
849
|
+
cliToolId: target.cliToolId,
|
|
850
|
+
instanceId,
|
|
851
|
+
requestId,
|
|
852
|
+
});
|
|
853
|
+
broadcastMessage('message', { worktreeId: target.worktreeId, message });
|
|
854
|
+
logger.info('command-code-transcript-turn-saved', {
|
|
855
|
+
worktreeId: target.worktreeId,
|
|
856
|
+
instanceId,
|
|
857
|
+
sessionId: rendered.sessionId,
|
|
858
|
+
requestId,
|
|
859
|
+
path,
|
|
860
|
+
bodyLength: rendered.body.length,
|
|
861
|
+
textBlocks: rendered.textBlocks,
|
|
862
|
+
toolBlocks: rendered.toolBlocks,
|
|
863
|
+
});
|
|
864
|
+
return true;
|
|
865
|
+
}
|