commandmate 0.44.2 → 0.44.3
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 +149 -149
- package/.next/app-path-routes-manifest.json +21 -21
- package/.next/build-manifest.json +2 -2
- package/.next/prerender-manifest.json +12 -12
- package/.next/react-loadable-manifest.json +0 -6
- package/.next/required-server-files.json +1 -1
- package/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
- package/.next/server/app/api/agents/update/route.js +1 -1
- package/.next/server/app/api/agents/update/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/agents/versions/route.js +1 -1
- package/.next/server/app/api/agents/versions/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/app/release-notes/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/app/update/route.js +2 -2
- package/.next/server/app/api/app/update/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/app/update-check/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/auth/login/route.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.js +1 -1
- package/.next/server/app/api/daily-summary/route.js.nft.json +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.js +1 -1
- package/.next/server/app/api/daily-summary/status/route.js.nft.json +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.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.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 +2 -2
- package/.next/server/app/api/hooks/agent-event/route.js.nft.json +1 -1
- package/.next/server/app/api/hooks/agent-event/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/hooks/claude-done/route.js +2 -2
- package/.next/server/app/api/hooks/claude-done/route.js.nft.json +1 -1
- package/.next/server/app/api/hooks/claude-done/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/hooks/permission-request/route.js +1 -1
- package/.next/server/app/api/hooks/permission-request/route.js.nft.json +1 -1
- package/.next/server/app/api/hooks/permission-request/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/metrics/vibe/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/ollama/models/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/push/escalation/route.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.js +2 -2
- package/.next/server/app/api/push/subscriptions/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/push/vapid/route.js +1 -1
- package/.next/server/app/api/push/vapid/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/relays/[relayId]/cancel/route.js +1 -1
- package/.next/server/app/api/relays/[relayId]/cancel/route.js.nft.json +1 -1
- package/.next/server/app/api/relays/[relayId]/cancel/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/relays/route.js +1 -1
- package/.next/server/app/api/relays/route.js.nft.json +1 -1
- package/.next/server/app/api/relays/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/remote/pair/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/repositories/[id]/route.js +1 -1
- package/.next/server/app/api/repositories/[id]/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/repositories/clone/[jobId]/route.js +1 -1
- package/.next/server/app/api/repositories/clone/[jobId]/route.js.nft.json +1 -1
- package/.next/server/app/api/repositories/clone/[jobId]/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/repositories/clone/route.js +1 -1
- package/.next/server/app/api/repositories/clone/route.js.nft.json +1 -1
- package/.next/server/app/api/repositories/clone/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/repositories/excluded/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/repositories/restore/route.js +1 -1
- package/.next/server/app/api/repositories/restore/route.js.nft.json +1 -1
- package/.next/server/app/api/repositories/restore/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/repositories/route.js +1 -1
- package/.next/server/app/api/repositories/route.js.nft.json +1 -1
- package/.next/server/app/api/repositories/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/repositories/scan/route.js +1 -1
- package/.next/server/app/api/repositories/scan/route.js.nft.json +1 -1
- package/.next/server/app/api/repositories/scan/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/repositories/sync/route.js +1 -1
- package/.next/server/app/api/repositories/sync/route.js.nft.json +1 -1
- package/.next/server/app/api/repositories/sync/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/repositories/validate-path/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/settings/default-agents/route.js +1 -1
- package/.next/server/app/api/settings/default-agents/route.js.nft.json +1 -1
- package/.next/server/app/api/settings/default-agents/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/settings/default-surface-mode/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/sidebar/group-order/route.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_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.js +2 -2
- package/.next/server/app/api/templates/[id]/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/templates/route.js +2 -2
- package/.next/server/app/api/templates/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/verification/runs/[runId]/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/verification/runs/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/auto-yes/route.js +1 -1
- package/.next/server/app/api/worktrees/[id]/auto-yes/route.js.nft.json +1 -1
- package/.next/server/app/api/worktrees/[id]/auto-yes/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/capture/route.js +1 -1
- package/.next/server/app/api/worktrees/[id]/capture/route.js.nft.json +1 -1
- package/.next/server/app/api/worktrees/[id]/capture/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/clear-composer/route.js +1 -1
- package/.next/server/app/api/worktrees/[id]/clear-composer/route.js.nft.json +1 -1
- package/.next/server/app/api/worktrees/[id]/clear-composer/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/cli-reference/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/cli-tool/route.js +1 -1
- package/.next/server/app/api/worktrees/[id]/cli-tool/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/cmate/schedules/route.js +3 -3
- package/.next/server/app/api/worktrees/[id]/cmate/schedules/route.js.nft.json +1 -1
- package/.next/server/app/api/worktrees/[id]/cmate/schedules/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/current-output/route.js +1 -1
- package/.next/server/app/api/worktrees/[id]/current-output/route.js.nft.json +1 -1
- package/.next/server/app/api/worktrees/[id]/current-output/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/direct-input/route.js +1 -1
- package/.next/server/app/api/worktrees/[id]/direct-input/route.js.nft.json +1 -1
- package/.next/server/app/api/worktrees/[id]/direct-input/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/env/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/execution-logs/[logId]/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/execution-logs/route.js +2 -2
- package/.next/server/app/api/worktrees/[id]/execution-logs/route.js.nft.json +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.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.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.js +1 -1
- package/.next/server/app/api/worktrees/[id]/git/checkout/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/git/commit/route.js +1 -1
- package/.next/server/app/api/worktrees/[id]/git/commit/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/git/diff/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/git/fetch/route.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.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.js +1 -1
- package/.next/server/app/api/worktrees/[id]/git/push/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/git/reset/route.js +1 -1
- package/.next/server/app/api/worktrees/[id]/git/reset/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/git/revert/route.js +1 -1
- package/.next/server/app/api/worktrees/[id]/git/revert/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/git/show/[commitHash]/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/git/stage/route.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.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.js +1 -1
- package/.next/server/app/api/worktrees/[id]/git/stash/pop/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/git/stash/push/route.js +1 -1
- package/.next/server/app/api/worktrees/[id]/git/stash/push/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/git/stash/route_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.js +1 -1
- package/.next/server/app/api/worktrees/[id]/git/unstage/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/git/working-diff/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/instances/notes/route.js +1 -1
- package/.next/server/app/api/worktrees/[id]/instances/notes/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/instances/opencode/route.js +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.js +1 -1
- package/.next/server/app/api/worktrees/[id]/marp-render/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/memos/[memoId]/route.js +1 -1
- package/.next/server/app/api/worktrees/[id]/memos/[memoId]/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/memos/route.js +1 -1
- package/.next/server/app/api/worktrees/[id]/memos/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/messages/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/opencode/diff/route.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.js +1 -1
- package/.next/server/app/api/worktrees/[id]/opencode/session/route.js.nft.json +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.js +1 -1
- package/.next/server/app/api/worktrees/[id]/opencode/share/route.js.nft.json +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 +2 -2
- package/.next/server/app/api/worktrees/[id]/schedules/[scheduleId]/route.js.nft.json +1 -1
- package/.next/server/app/api/worktrees/[id]/schedules/[scheduleId]/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/schedules/active/route.js +2 -2
- package/.next/server/app/api/worktrees/[id]/schedules/active/route.js.nft.json +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 +3 -3
- package/.next/server/app/api/worktrees/[id]/schedules/route.js.nft.json +1 -1
- package/.next/server/app/api/worktrees/[id]/schedules/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/search/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/send/route.js +1 -1
- package/.next/server/app/api/worktrees/[id]/send/route.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.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.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.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 +2 -2
- 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.js +1 -1
- package/.next/server/app/api/worktrees/[id]/todos/[todoId]/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/todos/route.js +1 -1
- package/.next/server/app/api/worktrees/[id]/todos/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/tree/[...path]/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/tree/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/upload/[...path]/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/verify/config/route.js +1 -1
- package/.next/server/app/api/worktrees/[id]/verify/config/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/verify/route.js +1 -1
- package/.next/server/app/api/worktrees/[id]/verify/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/verify/runs/[runId]/cancel/route.js +1 -1
- package/.next/server/app/api/worktrees/[id]/verify/runs/[runId]/cancel/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/verify/runs/[runId]/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/verify/runs/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/viewed/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/apply-default-agents/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/route.js +1 -1
- package/.next/server/app/api/worktrees/route.js.nft.json +1 -1
- package/.next/server/app/api/worktrees/route_client-reference-manifest.js +1 -1
- package/.next/server/app/login/page_client-reference-manifest.js +1 -1
- package/.next/server/app/manifest.webmanifest/route_client-reference-manifest.js +1 -1
- package/.next/server/app/more/page_client-reference-manifest.js +1 -1
- package/.next/server/app/offline/page_client-reference-manifest.js +1 -1
- package/.next/server/app/page_client-reference-manifest.js +1 -1
- package/.next/server/app/proxy/[...path]/route_client-reference-manifest.js +1 -1
- package/.next/server/app/repositories/page_client-reference-manifest.js +1 -1
- package/.next/server/app/review/page_client-reference-manifest.js +1 -1
- package/.next/server/app/sessions/page.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_client-reference-manifest.js +1 -1
- package/.next/server/app/skills/installed/page_client-reference-manifest.js +1 -1
- package/.next/server/app/skills/page_client-reference-manifest.js +1 -1
- package/.next/server/app/worktrees/[id]/files/[...path]/page_client-reference-manifest.js +1 -1
- package/.next/server/app/worktrees/[id]/page.js +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 +21 -21
- package/.next/server/chunks/1191.js +2 -2
- package/.next/server/chunks/1210.js +1 -1
- package/.next/server/chunks/1559.js +2 -0
- package/.next/server/chunks/1789.js +1 -0
- package/.next/server/chunks/2119.js +1 -1
- package/.next/server/chunks/2404.js +1 -1
- package/.next/server/chunks/2445.js +1 -1
- package/.next/server/chunks/2460.js +1 -1
- package/.next/server/chunks/2710.js +1 -1
- package/.next/server/chunks/2908.js +1 -1
- package/.next/server/chunks/2980.js +1 -1
- package/.next/server/chunks/3241.js +1 -1
- package/.next/server/chunks/3480.js +1 -1
- package/.next/server/chunks/3718.js +1 -1
- package/.next/server/chunks/4207.js +2 -2
- package/.next/server/chunks/450.js +1 -1
- package/.next/server/chunks/4672.js +5 -0
- package/.next/server/chunks/{9353.js → 4718.js} +2 -2
- package/.next/server/chunks/4837.js +14 -0
- package/.next/server/chunks/5048.js +1 -1
- package/.next/server/chunks/5209.js +1 -0
- package/.next/server/chunks/5595.js +1 -1
- package/.next/server/chunks/5625.js +25 -0
- package/.next/server/chunks/611.js +1 -1
- package/.next/server/chunks/6130.js +1 -1
- package/.next/server/chunks/6401.js +1 -1
- package/.next/server/chunks/6781.js +1 -1
- package/.next/server/chunks/6793.js +3 -3
- package/.next/server/chunks/7491.js +2 -2
- package/.next/server/chunks/7905.js +1 -1
- package/.next/server/chunks/850.js +1 -1
- package/.next/server/chunks/8728.js +3 -3
- package/.next/server/chunks/8791.js +1 -1
- package/.next/server/chunks/8800.js +1 -1
- package/.next/server/chunks/8918.js +7 -7
- package/.next/server/chunks/9130.js +1 -1
- package/.next/server/chunks/9390.js +2 -2
- package/.next/server/chunks/9584.js +1 -1
- package/.next/server/chunks/9627.js +6 -6
- package/.next/server/chunks/9731.js +3 -3
- package/.next/server/middleware-manifest.json +5 -5
- package/.next/server/middleware-react-loadable-manifest.js +1 -1
- package/.next/server/pages/500.html +1 -1
- package/.next/server/server-reference-manifest.json +1 -1
- package/.next/static/chunks/{1642-fb2e176aaadbd137.js → 1642-767aed8e7c61ef7f.js} +1 -1
- package/.next/static/chunks/{2407-051f6f84226a9f2c.js → 2407-06d35bb1aa02de80.js} +1 -1
- package/.next/static/chunks/{2619-04bc32f026a0d946.js → 2619-a305ad4810f1393a.js} +1 -1
- package/.next/static/chunks/4727-5d695d71e3fdf97b.js +1 -0
- package/.next/static/chunks/{8165-3853772f6ae1299b.js → 8165-3525fb42f1b4976e.js} +1 -1
- package/.next/static/chunks/app/{layout-9da1ab602bcb3051.js → layout-0d1750b833f5cc3e.js} +1 -1
- package/.next/static/chunks/app/sessions/page-2af68c63211c751f.js +1 -0
- package/.next/static/chunks/app/worktrees/[id]/page-e36898c3a80e7302.js +1 -0
- package/.next/static/chunks/app/worktrees/[id]/terminal/{page-373ea37b16d930c7.js → page-c601f3b3ece6557e.js} +1 -1
- package/.next/static/css/057f64a65726c439.css +1 -0
- package/.next/trace +13 -13
- package/dist/cli/commands/ask.d.ts.map +1 -1
- package/dist/cli/commands/ask.js +120 -119
- package/dist/cli/commands/attach.d.ts +94 -0
- package/dist/cli/commands/attach.d.ts.map +1 -1
- package/dist/cli/commands/attach.js +145 -7
- package/dist/cli/commands/auto-yes.js +1 -1
- package/dist/cli/commands/capture.d.ts.map +1 -1
- package/dist/cli/commands/capture.js +3 -5
- package/dist/cli/commands/command-target.d.ts +23 -0
- package/dist/cli/commands/command-target.d.ts.map +1 -0
- package/dist/cli/commands/command-target.js +25 -0
- package/dist/cli/commands/instances.d.ts.map +1 -1
- package/dist/cli/commands/instances.js +41 -2
- package/dist/cli/commands/message-dispatch.d.ts +36 -0
- package/dist/cli/commands/message-dispatch.d.ts.map +1 -0
- package/dist/cli/commands/message-dispatch.js +63 -0
- package/dist/cli/commands/reply.d.ts.map +1 -1
- package/dist/cli/commands/reply.js +2 -5
- package/dist/cli/commands/respond.d.ts.map +1 -1
- package/dist/cli/commands/respond.js +3 -6
- package/dist/cli/commands/send.d.ts.map +1 -1
- package/dist/cli/commands/send.js +149 -146
- package/dist/cli/commands/wait.d.ts.map +1 -1
- package/dist/cli/commands/wait.js +589 -431
- package/dist/cli/types/api-responses.d.ts +159 -27
- package/dist/cli/types/api-responses.d.ts.map +1 -1
- package/dist/cli/types/api-responses.js +4 -2
- package/dist/cli/utils/api-client.d.ts +25 -5
- package/dist/cli/utils/api-client.d.ts.map +1 -1
- package/dist/cli/utils/api-client.js +87 -55
- package/dist/cli/utils/server-url.d.ts +1 -0
- package/dist/cli/utils/server-url.d.ts.map +1 -1
- package/dist/cli/utils/server-url.js +7 -0
- package/dist/config/uat-isolation.d.ts +140 -0
- package/dist/config/uat-isolation.d.ts.map +1 -0
- package/dist/config/uat-isolation.js +246 -0
- package/dist/lib/global-state.d.ts +12 -0
- package/dist/lib/global-state.d.ts.map +1 -0
- package/dist/lib/global-state.js +16 -0
- package/dist/lib/polling/auto-yes-suppression-reason.d.ts +33 -0
- package/dist/lib/polling/auto-yes-suppression-reason.d.ts.map +1 -0
- package/dist/lib/polling/auto-yes-suppression-reason.js +2 -0
- package/dist/lib/session/prompt-view.d.ts +26 -3
- package/dist/lib/session/prompt-view.d.ts.map +1 -1
- package/dist/lib/session/prompt-view.js +26 -3
- package/dist/lib/session/status-evidence-type.d.ts +16 -0
- package/dist/lib/session/status-evidence-type.d.ts.map +1 -0
- package/dist/lib/session/status-evidence-type.js +2 -0
- package/dist/lib/tmux/legacy-session-alias.d.ts.map +1 -1
- package/dist/lib/tmux/legacy-session-alias.js +2 -1
- package/dist/server/locales/en/worktree.json +3 -1
- package/dist/server/locales/ja/worktree.json +3 -1
- package/dist/server/src/cli/types/api-responses.js +4 -2
- package/dist/server/src/config/cli-tool-timing-config.js +1 -6
- package/dist/server/src/config/uat-isolation.js +246 -0
- package/dist/server/src/lib/assistant-response-saver.js +62 -3
- package/dist/server/src/lib/auto-yes-lifecycle.js +6 -0
- package/dist/server/src/lib/auto-yes-poller.js +450 -160
- package/dist/server/src/lib/auto-yes-state.js +2 -2
- package/dist/server/src/lib/cli-tools/antigravity.js +38 -79
- package/dist/server/src/lib/cli-tools/base.js +151 -0
- package/dist/server/src/lib/cli-tools/codex.js +44 -80
- package/dist/server/src/lib/cli-tools/command-code.js +24 -56
- package/dist/server/src/lib/cli-tools/copilot.js +25 -67
- package/dist/server/src/lib/cli-tools/direct-input.js +8 -0
- package/dist/server/src/lib/cli-tools/gemini.js +23 -73
- package/dist/server/src/lib/cli-tools/opencode-pane-width.js +53 -0
- package/dist/server/src/lib/cli-tools/opencode-v2.js +9 -7
- package/dist/server/src/lib/cli-tools/opencode.js +21 -85
- package/dist/server/src/lib/cli-tools/submit-verified-sender.js +127 -4
- package/dist/server/src/lib/cli-tools/vibe-local.js +21 -66
- package/dist/server/src/lib/detection/cli-patterns.js +374 -3065
- package/dist/server/src/lib/detection/idle-evidence-observation.js +2 -2
- package/dist/server/src/lib/detection/prompt-detect-multiple-choice.js +7 -32
- package/dist/server/src/lib/detection/prompt-detector.js +2 -35
- package/dist/server/src/lib/detection/selection-shape-patterns.js +69 -0
- package/dist/server/src/lib/detection/selection-shape.js +32 -520
- package/dist/server/src/lib/detection/shared/pasted-text.js +41 -0
- package/dist/server/src/lib/detection/shared/shell-pane-command.js +16 -0
- package/dist/server/src/lib/detection/shared/strip-box-drawing.js +25 -0
- package/dist/server/src/lib/detection/tools/antigravity/detect.js +5 -29
- package/dist/server/src/lib/detection/tools/antigravity/dialog.js +2 -15
- package/dist/server/src/lib/detection/tools/antigravity/patterns.js +286 -0
- package/dist/server/src/lib/detection/tools/claude/detect.js +3 -14
- package/dist/server/src/lib/detection/tools/claude/patterns.js +264 -4
- package/dist/server/src/lib/detection/tools/claude/prompt.js +2 -10
- package/dist/server/src/lib/detection/tools/codex/detect.js +46 -85
- package/dist/server/src/lib/detection/tools/codex/live-region.js +33 -2
- package/dist/server/src/lib/detection/tools/codex/patterns.js +855 -0
- package/dist/server/src/lib/detection/tools/codex/prompt.js +2 -10
- package/dist/server/src/lib/detection/tools/command-code/detect.js +8 -50
- package/dist/server/src/lib/detection/tools/command-code/dialog.js +2 -18
- package/dist/server/src/lib/detection/tools/command-code/patterns.js +234 -0
- package/dist/server/src/lib/detection/tools/command-code/selection-shape.js +461 -0
- package/dist/server/src/lib/detection/tools/context-above.js +20 -0
- package/dist/server/src/lib/detection/tools/copilot/detect.js +10 -35
- package/dist/server/src/lib/detection/tools/copilot/patterns.js +640 -0
- package/dist/server/src/lib/detection/tools/copilot/prompt.js +2 -10
- package/dist/server/src/lib/detection/tools/gemini/patterns.js +29 -0
- package/dist/server/src/lib/detection/tools/live-region-specs.js +1 -1
- package/dist/server/src/lib/detection/tools/opencode/detect.js +8 -49
- package/dist/server/src/lib/detection/tools/opencode/patterns.js +508 -0
- package/dist/server/src/lib/detection/tools/opencode-v2/detect.js +7 -42
- package/dist/server/src/lib/detection/tools/opencode-v2/patterns.js +63 -0
- package/dist/server/src/lib/detection/tools/run-detection.js +6 -47
- package/dist/server/src/lib/detection/tools/verdicts.js +59 -0
- package/dist/server/src/lib/detection/tools/vibe-local/patterns.js +20 -0
- package/dist/server/src/lib/detection/truncate-raw-content.js +38 -0
- package/dist/server/src/lib/detection/turn-abandoned.js +66 -0
- package/dist/server/src/lib/detection/unclassified-frame-tracker.js +2 -2
- package/dist/server/src/lib/global-state.js +16 -0
- package/dist/server/src/lib/hooks/agent-session-telemetry.js +4 -6
- package/dist/server/src/lib/hooks/permission-decision-state.js +2 -2
- package/dist/server/src/lib/hooks/sources/antigravity/history.js +11 -76
- package/dist/server/src/lib/hooks/sources/antigravity/hooks-config.js +63 -1
- package/dist/server/src/lib/hooks/sources/antigravity/source.js +16 -0
- package/dist/server/src/lib/hooks/sources/antigravity/transcript.js +3 -12
- package/dist/server/src/lib/hooks/sources/claude/history.js +17 -156
- package/dist/server/src/lib/hooks/sources/claude/queued-notice.js +53 -0
- package/dist/server/src/lib/hooks/sources/claude/source.js +33 -3
- package/dist/server/src/lib/hooks/sources/claude/transcript.js +3 -13
- package/dist/server/src/lib/hooks/sources/codex/history.js +11 -74
- package/dist/server/src/lib/hooks/sources/codex/hooks-config.js +147 -11
- package/dist/server/src/lib/hooks/sources/codex/relay-install.js +64 -21
- package/dist/server/src/lib/hooks/sources/codex/transcript.js +5 -15
- package/dist/server/src/lib/hooks/sources/command-code/history.js +20 -121
- package/dist/server/src/lib/hooks/sources/command-code/transcript.js +3 -13
- package/dist/server/src/lib/hooks/sources/copilot/hook-settings.js +161 -31
- package/dist/server/src/lib/hooks/sources/define-source.js +40 -55
- package/dist/server/src/lib/hooks/sources/event-mapper.js +2 -1
- package/dist/server/src/lib/hooks/sources/opencode/diff.js +3 -4
- package/dist/server/src/lib/hooks/sources/opencode/history.js +2 -1
- package/dist/server/src/lib/hooks/sources/opencode/launch-settings.js +2 -1
- package/dist/server/src/lib/hooks/sources/opencode/payloads.js +2 -1
- package/dist/server/src/lib/hooks/sources/opencode/ports.js +2 -1
- package/dist/server/src/lib/hooks/sources/opencode/subscription.js +4 -3
- package/dist/server/src/lib/hooks/sources/opencode/transcript.js +3 -16
- package/dist/server/src/lib/hooks/sources/opencode-v2/history.js +2 -1
- package/dist/server/src/lib/hooks/sources/opencode-v2/mappers.js +5 -4
- package/dist/server/src/lib/hooks/sources/opencode-v2/ports.js +2 -1
- package/dist/server/src/lib/hooks/sources/opencode-v2/subscription.js +3 -7
- package/dist/server/src/lib/hooks/sources/opencode-v2/usage.js +2 -1
- package/dist/server/src/lib/hooks/sources/pending-decisions.js +2 -1
- package/dist/server/src/lib/hooks/sources/registry.js +3 -2
- package/dist/server/src/lib/hooks/sources/transcript-history.js +262 -0
- package/dist/server/src/lib/hooks/sources/turn-body.js +31 -0
- package/dist/server/src/lib/hooks/tool-input-normalization-state.js +2 -2
- package/dist/server/src/lib/polling/antigravity-permission-receipts.js +2 -2
- package/dist/server/src/lib/polling/auto-yes-enter-fallback-state.js +142 -0
- package/dist/server/src/lib/polling/auto-yes-enter-fallback.js +187 -0
- package/dist/server/src/lib/polling/auto-yes-resolver.js +9 -3
- package/dist/server/src/lib/polling/auto-yes-suppression-reason.js +2 -0
- package/dist/server/src/lib/polling/auto-yes-suppression-state.js +2 -2
- package/dist/server/src/lib/polling/pending-scraped-response.js +190 -0
- package/dist/server/src/lib/polling/prompt-dedup-state.js +2 -2
- package/dist/server/src/lib/polling/prompt-dedup.js +2 -2
- package/dist/server/src/lib/polling/response-checker-dialog-gate.js +95 -0
- package/dist/server/src/lib/polling/response-checker-extraction-result.js +50 -0
- package/dist/server/src/lib/polling/response-checker-extraction-steps.js +670 -0
- package/dist/server/src/lib/polling/response-checker.js +641 -1142
- package/dist/server/src/lib/polling/response-dedup.js +3 -4
- package/dist/server/src/lib/polling/response-poller-constants.js +17 -0
- package/dist/server/src/lib/polling/response-poller-core.js +54 -20
- package/dist/server/src/lib/polling/startup-screen.js +142 -0
- package/dist/server/src/lib/polling/structured-history-gate.js +3 -2
- package/dist/server/src/lib/prompt-answer-semantic.js +178 -0
- package/dist/server/src/lib/prompt-answer-sender.js +5 -4
- package/dist/server/src/lib/push/failure-episode-state.js +2 -2
- package/dist/server/src/lib/push/notification-dedup.js +3 -4
- package/dist/server/src/lib/push/prompt-card-state.js +2 -2
- package/dist/server/src/lib/push/waiting-push-notifier.js +3 -4
- package/dist/server/src/lib/realtime/publisher-registry.js +3 -2
- package/dist/server/src/lib/realtime/terminal-broadcast.js +38 -5
- package/dist/server/src/lib/realtime/terminal-session-ownership.js +45 -0
- package/dist/server/src/lib/relay/relay-delivery.js +3 -2
- package/dist/server/src/lib/relay/relay-readiness.js +38 -1
- package/dist/server/src/lib/response-cleaner.js +24 -1
- package/dist/server/src/lib/response-extractor.js +16 -0
- package/dist/server/src/lib/schedule-manager.js +8 -10
- package/dist/server/src/lib/session/agent-event-ask-user-question.js +168 -0
- package/dist/server/src/lib/session/agent-event-awaiting-instruction.js +104 -0
- package/dist/server/src/lib/session/agent-event-dedup.js +539 -0
- package/dist/server/src/lib/session/agent-event-model.js +562 -0
- package/dist/server/src/lib/session/agent-event-record.js +10 -0
- package/dist/server/src/lib/session/agent-event-state.js +106 -1854
- package/dist/server/src/lib/session/agent-event-structured-state.js +109 -0
- package/dist/server/src/lib/session/agent-event-turn.js +811 -0
- package/dist/server/src/lib/session/agent-session-lifecycle.js +11 -2
- package/dist/server/src/lib/session/chat-turn-progress.js +322 -0
- package/dist/server/src/lib/session/claude-executor.js +45 -5
- package/dist/server/src/lib/session/claude-session.js +21 -65
- package/dist/server/src/lib/session/cli-session.js +17 -0
- package/dist/server/src/lib/session/current-output-builder.js +450 -1103
- package/dist/server/src/lib/session/current-output-history-writers.js +255 -0
- package/dist/server/src/lib/session/current-output-types.js +9 -0
- package/dist/server/src/lib/session/detection-divergence.js +55 -0
- package/dist/server/src/lib/session/global-session-constants.js +2 -13
- package/dist/server/src/lib/session/hook-turn-hold.js +102 -0
- package/dist/server/src/lib/session/layer-disagreement.js +2 -2
- package/dist/server/src/lib/session/opencode-session-store.js +2 -1
- package/dist/server/src/lib/session/prompt-view.js +26 -3
- package/dist/server/src/lib/session/prompt-waiting-composition.js +3 -2
- package/dist/server/src/lib/session/provisional-turn.js +3 -1
- package/dist/server/src/lib/session/send-user-message.js +1 -1
- package/dist/server/src/lib/session/session-starting-state.js +4 -6
- package/dist/server/src/lib/session/status-evidence-type.js +2 -0
- package/dist/server/src/lib/session/status-evidence.js +2 -2
- package/dist/server/src/lib/session/structured-status-merge.js +335 -0
- package/dist/server/src/lib/session/waiting-episode-state.js +3 -4
- package/dist/server/src/lib/session-cleanup.js +1 -5
- package/dist/server/src/lib/skills/install-plan.js +2 -1
- package/dist/server/src/lib/skills/snapshot-store.js +2 -2
- package/dist/server/src/lib/skills/uninstall-plan.js +2 -2
- package/dist/server/src/lib/timer-manager.js +6 -8
- package/dist/server/src/lib/tmux/geometry-delegation.js +3 -4
- package/dist/server/src/lib/tmux/legacy-session-alias.js +2 -1
- package/dist/server/src/lib/tmux/session-hooks.js +2 -1
- package/dist/server/src/lib/tmux/session-status-options.js +3 -3
- package/dist/server/src/lib/tmux/tmux-capture-cache.js +3 -2
- package/dist/server/src/lib/tmux/tmux.js +33 -59
- package/dist/server/src/lib/tui-accumulator.js +2 -2
- package/dist/server/src/lib/version-checker.js +6 -6
- package/dist/server/src/lib/ws-server.js +47 -11
- package/docs/en/user-guide/webapp-guide.md +27 -3
- package/package.json +1 -1
- package/release-notes/0.44.3.json +82 -0
- package/scripts/hooks/cmate-agent-event.sh +18 -0
- package/.next/server/chunks/1068.js +0 -1
- package/.next/server/chunks/4372.js +0 -5
- package/.next/server/chunks/6474.js +0 -14
- package/.next/server/chunks/967.js +0 -1
- package/.next/server/chunks/9798.js +0 -2
- package/.next/server/chunks/9882.js +0 -25
- package/.next/static/chunks/591-539fec878c9dec5e.js +0 -1
- package/.next/static/chunks/app/sessions/page-049e06335187aa91.js +0 -1
- package/.next/static/chunks/app/worktrees/[id]/page-2a81397e60f17e63.js +0 -1
- package/.next/static/css/38070d879efa1371.css +0 -1
- package/dist/server/src/lib/cli-tools/index.js +0 -41
- package/dist/server/src/lib/polling/global-session-poller.js +0 -140
- /package/.next/static/{QlCqLjorauDDBXPa-C7TK → tWwHQzmvc-rv9KKUtDWfQ}/_buildManifest.js +0 -0
- /package/.next/static/{QlCqLjorauDDBXPa-C7TK → tWwHQzmvc-rv9KKUtDWfQ}/_ssgManifest.js +0 -0
|
@@ -7,64 +7,20 @@
|
|
|
7
7
|
* over WebSocket (terminal streaming), keeping the pull (HTTP) and push (WS)
|
|
8
8
|
* paths byte-for-byte consistent (DRY).
|
|
9
9
|
*/
|
|
10
|
-
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
11
|
-
if (k2 === undefined) k2 = k;
|
|
12
|
-
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
13
|
-
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
14
|
-
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
15
|
-
}
|
|
16
|
-
Object.defineProperty(o, k2, desc);
|
|
17
|
-
}) : (function(o, m, k, k2) {
|
|
18
|
-
if (k2 === undefined) k2 = k;
|
|
19
|
-
o[k2] = m[k];
|
|
20
|
-
}));
|
|
21
|
-
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
22
|
-
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
23
|
-
}) : function(o, v) {
|
|
24
|
-
o["default"] = v;
|
|
25
|
-
});
|
|
26
|
-
var __importStar = (this && this.__importStar) || (function () {
|
|
27
|
-
var ownKeys = function(o) {
|
|
28
|
-
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
29
|
-
var ar = [];
|
|
30
|
-
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
31
|
-
return ar;
|
|
32
|
-
};
|
|
33
|
-
return ownKeys(o);
|
|
34
|
-
};
|
|
35
|
-
return function (mod) {
|
|
36
|
-
if (mod && mod.__esModule) return mod;
|
|
37
|
-
var result = {};
|
|
38
|
-
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
39
|
-
__setModuleDefault(result, mod);
|
|
40
|
-
return result;
|
|
41
|
-
};
|
|
42
|
-
})();
|
|
43
10
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
44
|
-
exports.mergeStructuredStatus =
|
|
11
|
+
exports.mergeStructuredStatus = exports.resetChatTurnProgressState = exports.emitChatTurnProgress = exports.buildChatTurnProgress = void 0;
|
|
45
12
|
exports.buildCurrentOutput = buildCurrentOutput;
|
|
46
|
-
exports.resetChatTurnProgressState = resetChatTurnProgressState;
|
|
47
|
-
exports.buildChatTurnProgress = buildChatTurnProgress;
|
|
48
|
-
exports.emitChatTurnProgress = emitChatTurnProgress;
|
|
49
13
|
const db_1 = require("../../lib/db");
|
|
50
|
-
// Issue #2429: the send ledger, read directly from `chat-db` rather than from
|
|
51
|
-
// the `@/lib/db` barrel because that barrel does not re-export it. It is the
|
|
52
|
-
// SAME row `commandmate wait` asks for over
|
|
53
|
-
// `GET /api/worktrees/:id/messages?limit=1&unit=pairs` (see `readNewestPromptAt`
|
|
54
|
-
// in `cli/commands/wait.ts`), so the server and the CLI cannot disagree about
|
|
55
|
-
// when this instance was last handed a prompt.
|
|
56
|
-
const chat_db_1 = require("../../lib/db/chat-db");
|
|
57
14
|
const unclassified_frame_tracker_1 = require("../../lib/detection/unclassified-frame-tracker");
|
|
58
15
|
const session_starting_state_1 = require("../../lib/session/session-starting-state");
|
|
59
16
|
const composer_text_1 = require("../../lib/detection/composer-text");
|
|
60
17
|
const upstream_faults_1 = require("../../lib/detection/upstream-faults");
|
|
61
18
|
const opencode_pane_obstruction_1 = require("../../lib/detection/opencode-pane-obstruction");
|
|
62
|
-
const types_1 = require("../../lib/realtime/types");
|
|
63
|
-
const models_1 = require("../../types/models");
|
|
64
19
|
const logger_1 = require("../../lib/logger");
|
|
65
20
|
const manager_1 = require("../../lib/cli-tools/manager");
|
|
66
|
-
const
|
|
21
|
+
const types_1 = require("../../lib/cli-tools/types");
|
|
67
22
|
const registry_1 = require("../../lib/hooks/sources/registry");
|
|
23
|
+
const hook_turn_hold_1 = require("../../lib/session/hook-turn-hold");
|
|
68
24
|
const define_source_1 = require("../../lib/hooks/sources/define-source");
|
|
69
25
|
const subscription_1 = require("../../lib/hooks/sources/opencode/subscription");
|
|
70
26
|
const permission_decision_state_1 = require("../../lib/hooks/permission-decision-state");
|
|
@@ -77,6 +33,7 @@ const cli_session_1 = require("../../lib/session/cli-session");
|
|
|
77
33
|
const status_detector_1 = require("../../lib/detection/status-detector");
|
|
78
34
|
const auto_yes_manager_1 = require("../../lib/polling/auto-yes-manager");
|
|
79
35
|
const auto_yes_suppression_state_1 = require("../../lib/polling/auto-yes-suppression-state");
|
|
36
|
+
const auto_yes_enter_fallback_1 = require("../../lib/polling/auto-yes-enter-fallback");
|
|
80
37
|
const prompt_dedup_state_1 = require("../../lib/polling/prompt-dedup-state");
|
|
81
38
|
const status_capture_config_1 = require("../../config/status-capture-config");
|
|
82
39
|
const tmux_capture_cache_1 = require("../../lib/tmux/tmux-capture-cache");
|
|
@@ -90,506 +47,21 @@ const prompt_view_1 = require("../../lib/session/prompt-view");
|
|
|
90
47
|
const auto_yes_dialog_gate_1 = require("../../lib/polling/auto-yes-dialog-gate");
|
|
91
48
|
const layer_disagreement_1 = require("../../lib/session/layer-disagreement");
|
|
92
49
|
const structured_prompt_1 = require("../../lib/session/structured-prompt");
|
|
50
|
+
const chat_turn_progress_1 = require("./chat-turn-progress");
|
|
51
|
+
const structured_status_merge_1 = require("./structured-status-merge");
|
|
52
|
+
const current_output_history_writers_1 = require("./current-output-history-writers");
|
|
53
|
+
const detection_divergence_1 = require("./detection-divergence");
|
|
54
|
+
// Issue #3215: chat turn progress lives in `chat-turn-progress`. Re-exported
|
|
55
|
+
// under the same names so every existing import of this module keeps resolving.
|
|
56
|
+
var chat_turn_progress_2 = require("./chat-turn-progress");
|
|
57
|
+
Object.defineProperty(exports, "buildChatTurnProgress", { enumerable: true, get: function () { return chat_turn_progress_2.buildChatTurnProgress; } });
|
|
58
|
+
Object.defineProperty(exports, "emitChatTurnProgress", { enumerable: true, get: function () { return chat_turn_progress_2.emitChatTurnProgress; } });
|
|
59
|
+
Object.defineProperty(exports, "resetChatTurnProgressState", { enumerable: true, get: function () { return chat_turn_progress_2.resetChatTurnProgressState; } });
|
|
60
|
+
// Issue #3215: the status merge lives in `structured-status-merge`. Re-exported
|
|
61
|
+
// under the same names so every existing import of this module keeps resolving.
|
|
62
|
+
var structured_status_merge_2 = require("./structured-status-merge");
|
|
63
|
+
Object.defineProperty(exports, "mergeStructuredStatus", { enumerable: true, get: function () { return structured_status_merge_2.mergeStructuredStatus; } });
|
|
93
64
|
const logger = (0, logger_1.createLogger)('current-output-builder');
|
|
94
|
-
/**
|
|
95
|
-
* Write the "detection failed on this frame" row (Issue #1708).
|
|
96
|
-
*
|
|
97
|
-
* Stored as a `prompt` message so `capture --prompts` — the audit trail that
|
|
98
|
-
* exists precisely to answer "why did this stall?" — lists it alongside the
|
|
99
|
-
* prompts that WERE detected. It must never read as one of them, so the
|
|
100
|
-
* promptData carries `type: 'unclassified'` and `status: 'unclassified'`; the
|
|
101
|
-
* latter is also what keeps it out of `markPendingPromptsAsAnswered()`, whose
|
|
102
|
-
* SQL selects `status = 'pending'`. A frame nobody could read must not end up
|
|
103
|
-
* stamped "(answered via terminal)" the moment the flag clears.
|
|
104
|
-
*
|
|
105
|
-
* Not broadcast: this is a record for after the fact, and the prompt-answering
|
|
106
|
-
* UI has nothing to render for a frame with no parsed options.
|
|
107
|
-
*
|
|
108
|
-
* REACH, stated plainly because it is a real limit: this is driven by
|
|
109
|
-
* observation, not by the server's own loops. `buildCurrentOutput` has exactly
|
|
110
|
-
* two callers — the current-output route and `broadcastTerminalSnapshot`, which
|
|
111
|
-
* returns immediately when the room has no subscribers. So a row is written
|
|
112
|
-
* while `commandmate wait` is polling (every POLL_INTERVAL_MS), while a browser
|
|
113
|
-
* has the terminal open, or on a `capture --json`. A stall that nobody is
|
|
114
|
-
* watching at all writes nothing, and `capture --prompts` afterwards will not
|
|
115
|
-
* show it. That is tolerable because the stalls this exists to explain are the
|
|
116
|
-
* ones something WAS waiting on — but it means the Auto-Yes poller running
|
|
117
|
-
* alone is not enough. Feeding the tracker from that loop would need a second
|
|
118
|
-
* producer of `isUnclassifiedActive`, i.e. either duplicating its definition or
|
|
119
|
-
* adding a detectSessionStatus pass to a hot path; deliberately not done here.
|
|
120
|
-
*
|
|
121
|
-
* Best effort — a failed insert must never break the payload the caller is
|
|
122
|
-
* waiting on. The tracker has already marked the run as recorded, so a failure
|
|
123
|
-
* costs this one row, not a retry storm.
|
|
124
|
-
*/
|
|
125
|
-
/**
|
|
126
|
-
* The sentence that turns "nothing could read this frame" into "here is what is
|
|
127
|
-
* on it, and here is the key that removes it" (Issue #2095).
|
|
128
|
-
*
|
|
129
|
-
* Empty string when there is no obstruction, so every caller can concatenate it
|
|
130
|
-
* unconditionally and the #1708 wording is byte-identical on a frame that has
|
|
131
|
-
* none.
|
|
132
|
-
*
|
|
133
|
-
* English, like the rest of {@link recordUnclassifiedFrame}'s row and unlike the
|
|
134
|
-
* UI banner this pairs with. The row is read in `capture --prompts` as often as
|
|
135
|
-
* in the history pane, and `commandmate` has no locale to read it in.
|
|
136
|
-
*/
|
|
137
|
-
function describePaneObstruction(obstruction) {
|
|
138
|
-
if (!obstruction)
|
|
139
|
-
return '';
|
|
140
|
-
return (` Cause: opencode's sidebar is sharing rows with the transcript ` +
|
|
141
|
-
`(paneObstruction=${obstruction.id}, second column reads ` +
|
|
142
|
-
`${JSON.stringify(obstruction.matchedText)}), which covers the marker that ends a ` +
|
|
143
|
-
`turn. Press \`${opencode_pane_obstruction_1.OPENCODE_SIDEBAR_RECOVERY_CHORD}\` in the pane to close it.`);
|
|
144
|
-
}
|
|
145
|
-
/**
|
|
146
|
-
* Push a history row this module just wrote to the worktree room (Issue #2214).
|
|
147
|
-
*
|
|
148
|
-
* Always `'message'`: both callers INSERT a brand-new row, so nothing has been
|
|
149
|
-
* delivered for it before and the client appends rather than replaces.
|
|
150
|
-
*
|
|
151
|
-
* Detached on purpose, and for two reasons that both matter here:
|
|
152
|
-
*
|
|
153
|
-
* - the `ws-server` import stays dynamic, which is the same bargain
|
|
154
|
-
* {@link emitChatTurnProgress} strikes — this module's *other* callers (the
|
|
155
|
-
* `/current-output` route and the terminal push) must not pull the WebSocket
|
|
156
|
-
* server into their graph just by building a payload;
|
|
157
|
-
* - the row is already committed when this runs, so a socket write can never
|
|
158
|
-
* turn a written row into a failed one. The two record functions below are
|
|
159
|
-
* best-effort by contract and their callers are waiting on a payload.
|
|
160
|
-
*
|
|
161
|
-
* #2214 recorded a limitation here — a route bundle holding its own empty copy
|
|
162
|
-
* of `ws-server`'s `rooms` under `next dev` — and added that "production runs
|
|
163
|
-
* one custom-server bundle and is unaffected". **That last clause was wrong.**
|
|
164
|
-
* `npm run dev` and `npm start` run the *same* custom server (`tsx server.ts`
|
|
165
|
-
* vs `node dist/server/server.js`); what differs is only whether Next evaluates
|
|
166
|
-
* a route handler from a dev compilation or from `.next/server`. Either way the
|
|
167
|
-
* route handler's copy of this module and of `ws-server` is not the custom
|
|
168
|
-
* server's, so this push was silent in production too. #2220 bridged it: the
|
|
169
|
-
* `broadcastMessage` below now reaches the socket owner through
|
|
170
|
-
* `lib/realtime/publisher-registry` regardless of which graph called it.
|
|
171
|
-
*/
|
|
172
|
-
function broadcastRecordedRow(worktreeId, message) {
|
|
173
|
-
void Promise.resolve().then(() => __importStar(require('../../lib/ws-server'))).then(({ broadcastMessage }) => {
|
|
174
|
-
broadcastMessage('message', { worktreeId, message });
|
|
175
|
-
})
|
|
176
|
-
.catch((error) => {
|
|
177
|
-
logger.warn('history-row-broadcast-failed', {
|
|
178
|
-
worktreeId,
|
|
179
|
-
error: error instanceof Error ? error.message : String(error),
|
|
180
|
-
});
|
|
181
|
-
});
|
|
182
|
-
}
|
|
183
|
-
function recordUnclassifiedFrame(db, params) {
|
|
184
|
-
const dwellSeconds = Math.round(params.dwellMs / 1000);
|
|
185
|
-
const statusReason = `${params.sessionStatus}/${params.sessionStatusReason}`;
|
|
186
|
-
const question = `Unclassified interactive frame (${statusReason}) held for ${dwellSeconds}s. ` +
|
|
187
|
-
`The detection layer could not parse it, so no prompt was published and ` +
|
|
188
|
-
`nothing could answer it. Inspect the raw pane with ` +
|
|
189
|
-
`\`commandmate capture ${params.worktreeId} --pane\`.` +
|
|
190
|
-
// Issue #2095: appended rather than substituted. Everything above is still
|
|
191
|
-
// true — the frame really was unreadable — and a caller matching on the
|
|
192
|
-
// #1708 wording keeps matching. What follows turns "we could not read it"
|
|
193
|
-
// into "here is why, and here is the key that fixes it".
|
|
194
|
-
describePaneObstruction(params.obstruction);
|
|
195
|
-
const record = {
|
|
196
|
-
type: models_1.UNCLASSIFIED_PROMPT_TYPE,
|
|
197
|
-
status: 'unclassified',
|
|
198
|
-
question,
|
|
199
|
-
options: [],
|
|
200
|
-
dwellSeconds,
|
|
201
|
-
sessionStatusReason: statusReason,
|
|
202
|
-
};
|
|
203
|
-
try {
|
|
204
|
-
const message = (0, db_1.createMessage)(db, {
|
|
205
|
-
worktreeId: params.worktreeId,
|
|
206
|
-
role: 'assistant',
|
|
207
|
-
content: question,
|
|
208
|
-
messageType: 'prompt',
|
|
209
|
-
// Not a PromptData: nothing may answer this row, which is why the record
|
|
210
|
-
// type is kept out of that union (see UnclassifiedFrameRecord). The column
|
|
211
|
-
// is shared, so the cast is confined to this one write.
|
|
212
|
-
promptData: record,
|
|
213
|
-
timestamp: new Date(),
|
|
214
|
-
cliToolId: params.cliToolId,
|
|
215
|
-
instanceId: params.instanceId,
|
|
216
|
-
});
|
|
217
|
-
logger.info('unclassified-frame-recorded', {
|
|
218
|
-
worktreeId: params.worktreeId,
|
|
219
|
-
cliToolId: params.cliToolId,
|
|
220
|
-
dwellSeconds,
|
|
221
|
-
statusReason,
|
|
222
|
-
});
|
|
223
|
-
// `cliToolId` / `instanceId` are already the explicit values passed above —
|
|
224
|
-
// `createMessage` hands back the caller's own object — so the published row
|
|
225
|
-
// addresses the same instance the column does.
|
|
226
|
-
broadcastRecordedRow(params.worktreeId, message);
|
|
227
|
-
}
|
|
228
|
-
catch (error) {
|
|
229
|
-
logger.warn('unclassified-frame-record-failed', {
|
|
230
|
-
worktreeId: params.worktreeId,
|
|
231
|
-
error: error instanceof Error ? error.message : String(error),
|
|
232
|
-
});
|
|
233
|
-
}
|
|
234
|
-
}
|
|
235
|
-
/**
|
|
236
|
-
* Write the "the agent said a dialog is open and we could not read it" row
|
|
237
|
-
* (Issue #1725, continuing #1708's proposal 2).
|
|
238
|
-
*
|
|
239
|
-
* Written once per waiting episode, and ONLY while the scraper is publishing no
|
|
240
|
-
* prompt of its own. Both halves matter:
|
|
241
|
-
*
|
|
242
|
-
* - once, because `buildCurrentOutput` runs on every poll and a row per poll
|
|
243
|
-
* would turn one blocked dialog into a wall of identical history;
|
|
244
|
-
* - only when the scraper is blind, because when it is not, the existing
|
|
245
|
-
* prompt writers already record that prompt with its options and its answer.
|
|
246
|
-
* A second row would double-count the audit trail `capture --prompts` prints
|
|
247
|
-
* and put a "nobody could read this" line next to the parsed prompt that
|
|
248
|
-
* proves somebody could.
|
|
249
|
-
*
|
|
250
|
-
* So a row here means exactly one thing, which is the thing #1708 asked to be
|
|
251
|
-
* recorded: a prompt existed and the detection layer did not see it.
|
|
252
|
-
*
|
|
253
|
-
* Best effort, for the same reason as {@link recordUnclassifiedFrame}: the
|
|
254
|
-
* caller is waiting on a payload, and a failed insert must cost this row and
|
|
255
|
-
* nothing else.
|
|
256
|
-
*/
|
|
257
|
-
function recordStructuredPrompt(db, params) {
|
|
258
|
-
const record = (0, structured_prompt_1.buildStructuredPromptHistoryRecord)(params.worktreeId, params.facts);
|
|
259
|
-
try {
|
|
260
|
-
const message = (0, db_1.createMessage)(db, {
|
|
261
|
-
worktreeId: params.worktreeId,
|
|
262
|
-
role: 'assistant',
|
|
263
|
-
content: record.question,
|
|
264
|
-
summary: `structured prompt · source=${params.state.source}${params.state.toolName ? ` · tool=${params.state.toolName}` : ''}`,
|
|
265
|
-
messageType: 'prompt',
|
|
266
|
-
// Not a PromptData: it has no options and nothing may answer it by
|
|
267
|
-
// number. The column is shared, so the cast is confined to this write —
|
|
268
|
-
// the same arrangement UnclassifiedFrameRecord uses.
|
|
269
|
-
promptData: record,
|
|
270
|
-
timestamp: new Date(),
|
|
271
|
-
cliToolId: params.cliToolId,
|
|
272
|
-
instanceId: params.instanceId,
|
|
273
|
-
});
|
|
274
|
-
logger.info('structured-prompt-recorded', {
|
|
275
|
-
worktreeId: params.worktreeId,
|
|
276
|
-
cliToolId: params.cliToolId,
|
|
277
|
-
instanceId: params.instanceId,
|
|
278
|
-
source: params.state.source,
|
|
279
|
-
toolName: params.state.toolName,
|
|
280
|
-
});
|
|
281
|
-
broadcastRecordedRow(params.worktreeId, message);
|
|
282
|
-
}
|
|
283
|
-
catch (error) {
|
|
284
|
-
logger.warn('structured-prompt-record-failed', {
|
|
285
|
-
worktreeId: params.worktreeId,
|
|
286
|
-
error: error instanceof Error ? error.message : String(error),
|
|
287
|
-
});
|
|
288
|
-
}
|
|
289
|
-
}
|
|
290
|
-
/**
|
|
291
|
-
* The `AskUserQuestion` call as the degraded prompt can describe it
|
|
292
|
-
* (Issue #1726).
|
|
293
|
-
*
|
|
294
|
-
* Only the first question, and no option numbers — see
|
|
295
|
-
* {@link StructuredAskUserQuestionSummary} for why a layer that cannot see the
|
|
296
|
-
* screen must not publish numbers for it.
|
|
297
|
-
*/
|
|
298
|
-
function summarizeAskUserQuestion(episode) {
|
|
299
|
-
const first = episode?.spec.questions[0];
|
|
300
|
-
if (!first)
|
|
301
|
-
return null;
|
|
302
|
-
return {
|
|
303
|
-
question: first.question,
|
|
304
|
-
labels: first.choices.map((choice) => choice.label),
|
|
305
|
-
questionCount: episode.spec.questions.length,
|
|
306
|
-
// Issue #2951: a question that takes a typed answer, so the panel and the
|
|
307
|
-
// phone sheet can offer an input for it.
|
|
308
|
-
...(first.custom ? { custom: true } : {}),
|
|
309
|
-
};
|
|
310
|
-
}
|
|
311
|
-
/**
|
|
312
|
-
* Prefer the agent's own account of what it is doing over the screen scrape
|
|
313
|
-
* (Issue #1723).
|
|
314
|
-
*
|
|
315
|
-
* Pure, and exported so the whole precedence table can be tested without a tmux
|
|
316
|
-
* session behind it.
|
|
317
|
-
*
|
|
318
|
-
* ## Why the scraper still wins in two cases
|
|
319
|
-
*
|
|
320
|
-
* **`waiting` on the scraper's side always wins.** A frame the detector reads
|
|
321
|
-
* as a prompt, a selection list or a pager is a frame a human has to act on,
|
|
322
|
-
* and this Issue deliberately does not touch `isPromptWaiting` / `promptData` /
|
|
323
|
-
* `isSelectionListActive` (they are #1725's). Letting a structured `running`
|
|
324
|
-
* overwrite `sessionStatus` while those three still said "answer me" would
|
|
325
|
-
* publish a self-contradicting payload. It is also the empirically necessary
|
|
326
|
-
* rule: the live capture found that Claude emits **no event at all** while an
|
|
327
|
-
* `AskUserQuestion` selection or "Ready to submit your answers?" screen is up
|
|
328
|
-
* (`agent-hooks-live-verification.md` §5.6), so the newest structured fact
|
|
329
|
-
* there is the `user_prompt_submit` that opened the turn — `running` — while
|
|
330
|
-
* the truth on screen is "waiting for a human". That is precisely the #1708
|
|
331
|
-
* stall, and the scraper is the only layer that can see it.
|
|
332
|
-
*
|
|
333
|
-
* **A structured `waiting` is applied only through the prompt-waiting state**
|
|
334
|
-
* (Issue #1725). #1723 recorded `Notification(permission_prompt)` and stopped
|
|
335
|
-
* there, for a reason that had to be answered before it could be promoted:
|
|
336
|
-
* nothing marks a permission prompt as *answered*, so a verdict read off the
|
|
337
|
-
* last event alone would stick until the next `Stop` and describe a session
|
|
338
|
-
* that went back to work minutes ago. `getStructuredPromptWaiting` is that
|
|
339
|
-
* answer — it is released by `Stop` / `user_prompt_submit` / a generation
|
|
340
|
-
* change, by the scraper reporting the frame it corroborated has cleared, and
|
|
341
|
-
* by expiry. So the `waiting` promoted here is the one that survives all of
|
|
342
|
-
* those, passed in explicitly; the raw `structured.status === 'waiting'` still
|
|
343
|
-
* decides nothing on its own.
|
|
344
|
-
*
|
|
345
|
-
* ## `isUnclassifiedActive` is not this function's to move (Issue #2011)
|
|
346
|
-
*
|
|
347
|
-
* The flag is carried through from the scraper on every path. It answers "could
|
|
348
|
-
* the detection layer read this frame?", and no amount of knowledge about the
|
|
349
|
-
* TURN changes the answer for the FRAME:
|
|
350
|
-
*
|
|
351
|
-
* - a static overlay left on screen after a turn (`/help`, #1497) reads as
|
|
352
|
-
* `running`/`no_recent_output` while the structured layer says `ready` — the
|
|
353
|
-
* structured layer adds nothing about the screen, and clearing the flag would
|
|
354
|
-
* take away the navigation hatch the user needs to escape;
|
|
355
|
-
* - a frame nobody can classify while the structured layer says `running` is
|
|
356
|
-
* still a frame nobody can classify. `wait`'s unclassified dwell (exit 10)
|
|
357
|
-
* is the last hatch for a screen that produces no events at all, and #1708
|
|
358
|
-
* exists because it was missing. A structured `running` does not prove a
|
|
359
|
-
* human is not needed — see the AskUserQuestion case above.
|
|
360
|
-
*
|
|
361
|
-
* `#1723`'s reported case — `Stop` arrives while the spinner is still painted —
|
|
362
|
-
* needs no help from here: that frame reads `running`/`thinking_indicator`, which
|
|
363
|
-
* is classified, so the flag was already down and `wait` completes on the
|
|
364
|
-
* structured `ready` alone.
|
|
365
|
-
*
|
|
366
|
-
* What this function DOES move is `evidence`, and only upwards: see
|
|
367
|
-
* {@link hookClosedTurn}.
|
|
368
|
-
*
|
|
369
|
-
* @param turn - The published turn record for this instance, or null. Read only
|
|
370
|
-
* for {@link hookClosedTurn}; `structured` remains the status authority.
|
|
371
|
-
*/
|
|
372
|
-
function mergeStructuredStatus(scraper, structured, promptWaiting = null, turn = null, lastPromptAt = null) {
|
|
373
|
-
if (scraper.status === 'waiting') {
|
|
374
|
-
return { ...scraper, structuredApplied: false };
|
|
375
|
-
}
|
|
376
|
-
// Issue #1725, the OR rule at the status level: the scraper reads this frame
|
|
377
|
-
// as running or ready, and the agent has told us a dialog is in front of it.
|
|
378
|
-
// Publishing `running` next to `isPromptWaiting: true` would be a payload
|
|
379
|
-
// that contradicts itself, and every consumer of `sessionStatus` — the
|
|
380
|
-
// sidebar dot, `deriveSessionStatus`, the worktrees API — would show a worker
|
|
381
|
-
// that needs a human as one that is busy.
|
|
382
|
-
if (promptWaiting !== null) {
|
|
383
|
-
return {
|
|
384
|
-
status: 'waiting',
|
|
385
|
-
reason: (0, prompt_waiting_composition_1.structuredWaitingReason)(promptWaiting),
|
|
386
|
-
thinking: false,
|
|
387
|
-
// Untouched: what the scraper could read about this frame does not change
|
|
388
|
-
// because the agent told us a dialog is open (Issue #1924).
|
|
389
|
-
evidence: scraper.evidence,
|
|
390
|
-
isUnclassifiedActive: scraper.isUnclassifiedActive,
|
|
391
|
-
structuredApplied: true,
|
|
392
|
-
};
|
|
393
|
-
}
|
|
394
|
-
if (structured === null || structured.status === 'waiting') {
|
|
395
|
-
return { ...scraper, structuredApplied: false };
|
|
396
|
-
}
|
|
397
|
-
// Issue #2429: the structured `ready` is about a turn older than the prompt
|
|
398
|
-
// this instance is currently answering, and the screen says so. See
|
|
399
|
-
// {@link structuredReadyPredatesPrompt} for why that combination is the only
|
|
400
|
-
// one in which the scraper wins a `running`.
|
|
401
|
-
if (structuredReadyPredatesPrompt(scraper, structured, turn, lastPromptAt)) {
|
|
402
|
-
return { ...scraper, structuredApplied: false };
|
|
403
|
-
}
|
|
404
|
-
const thinking = structured.status === 'running';
|
|
405
|
-
// Issue #2011 (対応 2): the agent's own turn close IS positive evidence.
|
|
406
|
-
//
|
|
407
|
-
// #1927 (DR2-003) guarded this override with `scraper.evidence === 'positive'`
|
|
408
|
-
// to stop a structured `ready` clearing `isUnclassifiedActive` on a frame
|
|
409
|
-
// nobody could read. It over-corrected twice over. First, the conjunct made
|
|
410
|
-
// the whole expression a no-op: `evidence = cond ? 'positive' : scraper.evidence`
|
|
411
|
-
// where `cond` requires `scraper.evidence === 'positive'` returns
|
|
412
|
-
// `scraper.evidence` on both arms, so the branch could not change a value in
|
|
413
|
-
// any direction. Second, the fix it was reaching for belonged one field over —
|
|
414
|
-
// the hatch is held open by {@link ScraperVerdict.isUnclassifiedActive}, which
|
|
415
|
-
// is carried through below rather than re-derived, so the evidence no longer
|
|
416
|
-
// has to be understated to protect it.
|
|
417
|
-
//
|
|
418
|
-
// What that leaves is a payload that stopped contradicting itself. A pane
|
|
419
|
-
// whose `reason` reads `hook_stop` while `statusEvidence` reads `'none'` is
|
|
420
|
-
// denying, in one field, the strongest positive signal this server can get: a
|
|
421
|
-
// `Stop` from the agent, for a turn this server watched open. `closedAt >
|
|
422
|
-
// openedAt` is what narrows it to that case — a `Stop` that arrived with no
|
|
423
|
-
// turn open publishes `openedAt: null` (see `recordAgentEvent`), and a close
|
|
424
|
-
// the SCREEN inferred carries one of the other {@link TurnCloseReason} values.
|
|
425
|
-
// Freshness needs no bound of its own: `getStructuredSessionState` answered
|
|
426
|
-
// null above once the display event passed STRUCTURED_STATE_MAX_AGE_MS.
|
|
427
|
-
//
|
|
428
|
-
// `scraper.status === 'running'` is deliberately NOT required. The session this
|
|
429
|
-
// Issue was reported from sat at `ready`/`input_prompt` with `closedBy: 'stop'`
|
|
430
|
-
// — the agent had said it was done and the frame agreed — and the old conjunct
|
|
431
|
-
// is what kept publishing `'none'` for it.
|
|
432
|
-
const evidence = structured.status === 'ready' && hookClosedTurn(turn) ? 'positive' : scraper.evidence;
|
|
433
|
-
return {
|
|
434
|
-
status: structured.status,
|
|
435
|
-
reason: structured.reason,
|
|
436
|
-
thinking,
|
|
437
|
-
evidence,
|
|
438
|
-
// Issue #2011: what the structured layer knows about the TURN says nothing
|
|
439
|
-
// about whether this FRAME could be read. #1708's dwell and the nav hatch
|
|
440
|
-
// are the last way out of a pane nothing can drive, and a `Stop` does not
|
|
441
|
-
// make an unreadable pane readable — see the `/help` overlay fixture in
|
|
442
|
-
// `tests/unit/lib/detection/fixtures/claude-live-2011/`.
|
|
443
|
-
isUnclassifiedActive: scraper.isUnclassifiedActive,
|
|
444
|
-
structuredApplied: true,
|
|
445
|
-
};
|
|
446
|
-
}
|
|
447
|
-
/**
|
|
448
|
-
* Whether the agent's own `Stop` closed a turn this server watched open
|
|
449
|
-
* (Issue #2011, 対応 2).
|
|
450
|
-
*
|
|
451
|
-
* Both halves are required. `closedBy: 'stop'` rules out the five close reasons
|
|
452
|
-
* that are the server's inference rather than the agent's word (`stale`,
|
|
453
|
-
* `generation`, `session_end`, `scraper_evidence`, `resync_idle`), and
|
|
454
|
-
* `closedAt > openedAt` rules out a `Stop` whose turn was never observed
|
|
455
|
-
* opening — `recordAgentEvent` publishes `openedAt: null` for that, and a close
|
|
456
|
-
* with nothing behind it is not a completion anybody watched.
|
|
457
|
-
*/
|
|
458
|
-
function hookClosedTurn(turn) {
|
|
459
|
-
if (turn === null)
|
|
460
|
-
return false;
|
|
461
|
-
const { closedBy, closedAt, openedAt } = turn;
|
|
462
|
-
return closedBy === 'stop' && closedAt !== null && openedAt !== null && closedAt > openedAt;
|
|
463
|
-
}
|
|
464
|
-
/**
|
|
465
|
-
* Whether a structured `ready` could be about a turn that ended before the
|
|
466
|
-
* prompt now on screen (Issue #2429).
|
|
467
|
-
*
|
|
468
|
-
* The cheap half of {@link structuredReadyPredatesPrompt}, split out because
|
|
469
|
-
* `buildPayload` asks it FIRST: it is the gate on the one database read this
|
|
470
|
-
* rule needs, and a session that is not in this state must not pay for the send
|
|
471
|
-
* ledger on every poll. One expression, two callers — the alternative is the
|
|
472
|
-
* "two expressions for one fact" the merge's own docblock says is how this
|
|
473
|
-
* layer drifts.
|
|
474
|
-
*
|
|
475
|
-
* Three conjuncts, and all three are about the same instant:
|
|
476
|
-
*
|
|
477
|
-
* - **the screen is positively generating.** `ScraperVerdict.thinking` is
|
|
478
|
-
* `isGeneratingStatus`, i.e. `running` with `thinking_indicator` /
|
|
479
|
-
* `opencode_processing_indicator` behind it — a spinner, `✻ Thinking…` or
|
|
480
|
-
* `esc to interrupt` that a tool's own pattern matched. It is deliberately
|
|
481
|
-
* NOT `scraper.status === 'running'`: the `no_recent_output` and
|
|
482
|
-
* `unknown_frame` floors are also `running`, and neither is evidence of
|
|
483
|
-
* anything. A frame that merely stopped changing must never retire a `Stop`
|
|
484
|
-
* the agent actually sent.
|
|
485
|
-
* - **the structured layer says `ready`.** `running` and `waiting` already win
|
|
486
|
-
* on their own paths and are not in question here.
|
|
487
|
-
* - **that `ready` came from the agent's own `Stop`.**
|
|
488
|
-
* `getStructuredSessionState` publishes `ready / hook_stop` exactly when the
|
|
489
|
-
* turn record carries `closedBy: 'stop'` with a `closedAt`, so the record is
|
|
490
|
-
* read rather than the folded reason.
|
|
491
|
-
*
|
|
492
|
-
* `openedAt` is deliberately not required, which is what separates this from
|
|
493
|
-
* {@link hookClosedTurn}. A `Stop` that arrived with no turn open publishes
|
|
494
|
-
* `openedAt: null`, and for the tool this Issue was reported from that is the
|
|
495
|
-
* ORDINARY shape: Command Code cannot emit `UserPromptSubmit` (its loader
|
|
496
|
-
* validates the event name against a closed list) and emits no
|
|
497
|
-
* `PreToolUse` / `PostToolUse` on a turn that calls no tool, so nothing ever
|
|
498
|
-
* opens the turn. Requiring an `openedAt` here would exclude precisely the case
|
|
499
|
-
* this rule exists for.
|
|
500
|
-
*/
|
|
501
|
-
function staleReadyCandidate(scraper, structured, turn) {
|
|
502
|
-
if (!scraper.thinking)
|
|
503
|
-
return false;
|
|
504
|
-
if (structured === null || structured.status !== 'ready')
|
|
505
|
-
return false;
|
|
506
|
-
if (turn === null)
|
|
507
|
-
return false;
|
|
508
|
-
return turn.closedBy === 'stop' && turn.closedAt !== null;
|
|
509
|
-
}
|
|
510
|
-
/**
|
|
511
|
-
* Whether the structured layer's `ready` has been outlived by the newest prompt
|
|
512
|
-
* this instance was handed (Issue #2429).
|
|
513
|
-
*
|
|
514
|
-
* ## The defect this closes
|
|
515
|
-
*
|
|
516
|
-
* The merge above prefers the agent's own account to the screen, and until this
|
|
517
|
-
* Issue the only exception was a scraper `waiting` (#1708). That is correct for
|
|
518
|
-
* every tool that can say when a turn BEGINS — claude, codex, copilot, gemini
|
|
519
|
-
* and antigravity all post a turn-opening event, so a live turn replaces the
|
|
520
|
-
* previous turn's `stop` before the first generating frame is ever read.
|
|
521
|
-
*
|
|
522
|
-
* Command Code can post neither: no `UserPromptSubmit`, and no `PreToolUse` /
|
|
523
|
-
* `PostToolUse` on a turn that calls no tool. The previous turn's `ready /
|
|
524
|
-
* hook_stop` therefore stays the newest structured fact for the whole of the
|
|
525
|
-
* next turn, and the merge published it over a pane that was visibly generating.
|
|
526
|
-
* Measured 2026-09-08 on a 19 s turn: `sessionStatus` read `ready / hook_stop`
|
|
527
|
-
* from 4 s to 18 s while the status row said `esc to interrupt`. `commandmate
|
|
528
|
-
* wait` reads that field — not the frame — for "is at its composer", so #1975's
|
|
529
|
-
* 60 s unanswered-prompt hold was the only thing standing between a long turn
|
|
530
|
-
* and a `basis=scraper_ready` completion reported before the reply existed.
|
|
531
|
-
*
|
|
532
|
-
* ## Why a comparison rather than a timer
|
|
533
|
-
*
|
|
534
|
-
* The same comparison `wait` already makes (`outstandingPrompt`), against the
|
|
535
|
-
* same two server-stamped facts: the turn's `closedAt`, and the timestamp of
|
|
536
|
-
* the newest user row in the chat ledger. A `Stop` that POSTDATES the newest
|
|
537
|
-
* prompt is this turn's own — the agent finished, the spinner on the frame is a
|
|
538
|
-
* repaint that has not settled, and the structured `ready` keeps winning. A
|
|
539
|
-
* `Stop` that predates it has reported neither the start nor the end of the
|
|
540
|
-
* work now on screen, and about that work it says nothing at all.
|
|
541
|
-
*
|
|
542
|
-
* Shortening #1975's hold would have been the other repair, and the Issue rules
|
|
543
|
-
* it out for a reason worth restating here: the hold is what stops a false
|
|
544
|
-
* completion during a turn nobody has reported yet, so a shorter one moves the
|
|
545
|
-
* false completion earlier rather than removing it.
|
|
546
|
-
*
|
|
547
|
-
* @param lastPromptAt - Epoch ms of the newest prompt handed to this instance,
|
|
548
|
-
* or null when the ledger was not read or holds none. Null decides nothing:
|
|
549
|
-
* an unreadable ledger is not evidence that nothing was sent, so the
|
|
550
|
-
* structured verdict stands exactly as it did before this Issue.
|
|
551
|
-
*/
|
|
552
|
-
function structuredReadyPredatesPrompt(scraper, structured, turn, lastPromptAt) {
|
|
553
|
-
if (lastPromptAt === null)
|
|
554
|
-
return false;
|
|
555
|
-
if (!staleReadyCandidate(scraper, structured, turn))
|
|
556
|
-
return false;
|
|
557
|
-
const closedAt = turn?.closedAt ?? null;
|
|
558
|
-
return closedAt !== null && closedAt < lastPromptAt;
|
|
559
|
-
}
|
|
560
|
-
/**
|
|
561
|
-
* When this instance was last handed a prompt, or null (Issue #2429).
|
|
562
|
-
*
|
|
563
|
-
* The server's half of the comparison `commandmate wait` makes over HTTP. Both
|
|
564
|
-
* sides read the same row — the newest `role: 'user'` message scoped to this
|
|
565
|
-
* (worktree, tool, instance) — so the field this function feeds and the notice
|
|
566
|
-
* `wait` prints cannot describe different sends.
|
|
567
|
-
*
|
|
568
|
-
* Best effort, like {@link recordUnclassifiedFrame} and
|
|
569
|
-
* {@link recordStructuredPrompt}: the caller is building a payload, and a
|
|
570
|
-
* ledger that cannot be read must cost this one comparison and nothing else.
|
|
571
|
-
* Null is reported rather than a guess, and null leaves the pre-#2429
|
|
572
|
-
* precedence in place — an unreadable ledger is not evidence that nothing was
|
|
573
|
-
* sent, which is the position `wait` takes on the same read.
|
|
574
|
-
*/
|
|
575
|
-
function readNewestPromptAt(db, worktreeId, cliToolId, instanceId) {
|
|
576
|
-
try {
|
|
577
|
-
const row = (0, chat_db_1.getLastUserMessageForInstance)(db, worktreeId, cliToolId, instanceId);
|
|
578
|
-
if (!row)
|
|
579
|
-
return null;
|
|
580
|
-
const at = row.timestamp instanceof Date ? row.timestamp.getTime() : NaN;
|
|
581
|
-
return Number.isFinite(at) ? at : null;
|
|
582
|
-
}
|
|
583
|
-
catch (error) {
|
|
584
|
-
logger.debug('newest-prompt-read-failed', {
|
|
585
|
-
worktreeId,
|
|
586
|
-
cliToolId,
|
|
587
|
-
instanceId,
|
|
588
|
-
error: error instanceof Error ? error.message : String(error),
|
|
589
|
-
});
|
|
590
|
-
return null;
|
|
591
|
-
}
|
|
592
|
-
}
|
|
593
65
|
/**
|
|
594
66
|
* Build the current-output payload for a worktree session.
|
|
595
67
|
*
|
|
@@ -617,7 +89,7 @@ async function buildCurrentOutput(db, worktreeId, cliToolId, instanceId, resolut
|
|
|
617
89
|
// inside, so what is waited for is a worktree row and a Map lookup. What it
|
|
618
90
|
// buys is that the frame's outcome is logged against the tick that produced
|
|
619
91
|
// it rather than whenever the event loop got round to the detached tail.
|
|
620
|
-
await publishChatTurnProgress(db, worktreeId, cliToolId, instanceId);
|
|
92
|
+
await (0, chat_turn_progress_1.publishChatTurnProgress)(db, worktreeId, cliToolId, instanceId);
|
|
621
93
|
}
|
|
622
94
|
if (!resolution)
|
|
623
95
|
return payload;
|
|
@@ -631,17 +103,12 @@ async function buildCurrentOutput(db, worktreeId, cliToolId, instanceId, resolut
|
|
|
631
103
|
};
|
|
632
104
|
}
|
|
633
105
|
/**
|
|
634
|
-
* The
|
|
635
|
-
*
|
|
636
|
-
*
|
|
106
|
+
* The agent's own events for this session, read before the pane is looked at
|
|
107
|
+
* (Issue #3215). Split out of {@link buildPayload} in the order it ran there:
|
|
108
|
+
* nothing here awaits, and `structuredEvents` is the object both of its return
|
|
109
|
+
* paths publish.
|
|
637
110
|
*/
|
|
638
|
-
|
|
639
|
-
const resolvedInstanceId = instanceId ?? cliToolId;
|
|
640
|
-
const manager = manager_1.CLIToolManager.getInstance();
|
|
641
|
-
const cliTool = manager.getTool(cliToolId);
|
|
642
|
-
// Issue #2886: read once, independent of `isRunning`, so both return paths
|
|
643
|
-
// below publish the same value the pane is (or would be) reached at.
|
|
644
|
-
const sessionName = cliTool.getSessionName(worktreeId, instanceId);
|
|
111
|
+
function readAgentEvents(worktreeId, cliToolId, instanceId, resolvedInstanceId) {
|
|
645
112
|
const stopEventAt = (0, agent_event_state_1.getLastStopEventAt)(worktreeId, cliToolId, instanceId);
|
|
646
113
|
const lastEvent = (0, agent_event_state_1.getLastAgentEvent)(worktreeId, cliToolId, instanceId);
|
|
647
114
|
// Issue #1924: the registry answers for every tool — a real implementation
|
|
@@ -745,12 +212,377 @@ async function buildPayload(db, worktreeId, cliToolId, instanceId) {
|
|
|
745
212
|
// tool's status poll grows a field or a request.
|
|
746
213
|
sessionDiff: (0, diff_1.ensureOpencodeSessionDiff)({ worktreeId, cliToolId, instanceId }),
|
|
747
214
|
};
|
|
215
|
+
return { stopEventAt, eventSource, askUserQuestion, structuredEvents };
|
|
216
|
+
}
|
|
217
|
+
/**
|
|
218
|
+
* The prompt this payload publishes and the replies an approval accepts
|
|
219
|
+
* (Issue #3215). Split out of {@link buildPayload} in the order it ran there;
|
|
220
|
+
* every value below is derived from the arguments, and nothing is assigned
|
|
221
|
+
* outside this function.
|
|
222
|
+
*/
|
|
223
|
+
function composePromptData({ worktreeId, cliToolId, output, statusResult, startingSince, promptWaiting, askUserQuestion, eventSource, }) {
|
|
224
|
+
const scraperPromptWaiting = statusResult.hasActivePrompt;
|
|
225
|
+
// Issue #1726: the agent's own account of what it asked. It contributes only
|
|
226
|
+
// where some other layer has already established that a dialog is on screen —
|
|
227
|
+
// this record decides no status of its own, because Claude emits nothing at
|
|
228
|
+
// all while an AskUserQuestion picker is up (§5.6) and a record that asserted
|
|
229
|
+
// `waiting` from the invocation would go on asserting it long after a human
|
|
230
|
+
// answered in the terminal.
|
|
231
|
+
// Issue #2040 moved the read to the top of `buildPayload` — `readAgentEvents`
|
|
232
|
+
// takes it there today (the decision entries publish the same episode's
|
|
233
|
+
// choices) — and this line now re-uses it, so the two surfaces cannot describe
|
|
234
|
+
// different instants.
|
|
235
|
+
const scraperPromptData = statusResult.promptDetection.promptData;
|
|
236
|
+
const correctedPromptData = scraperPromptData && askUserQuestion
|
|
237
|
+
? (0, ask_user_question_prompt_1.applyAskUserQuestion)(scraperPromptData, askUserQuestion.spec)
|
|
238
|
+
: null;
|
|
239
|
+
// The degraded form, for a dialog only the structured layer can see. Enriched
|
|
240
|
+
// with the question text when one is in flight — that turns "a dialog is open
|
|
241
|
+
// and nobody could read it" into "a dialog is open and here is what it asks".
|
|
242
|
+
//
|
|
243
|
+
// Issue #1898 adds the replies the dialog accepts, for the sources that can be
|
|
244
|
+
// answered without touching the pane. The gate is `eventIdentity`: a source
|
|
245
|
+
// that publishes a per-decision id is a source whose approval can be answered
|
|
246
|
+
// by that id, which is what makes an option NUMBER here mean something other
|
|
247
|
+
// than a line on a screen nobody parsed. `source === 'notification'` narrows
|
|
248
|
+
// it to a dialog the agent actually reported — a `permission-request` record
|
|
249
|
+
// is a prediction, and a question is answered with a choice rather than with
|
|
250
|
+
// one of these three verdicts.
|
|
251
|
+
//
|
|
252
|
+
// Issue #2031 adds the fourth conjunct and folds the whole gate into ONE
|
|
253
|
+
// value — today `promptWaiting !== null` here and the other three inside
|
|
254
|
+
// `isApiAnswerableStructuredWait` (Issue #3215).
|
|
255
|
+
// The three verdicts and the id they are delivered to are now derived
|
|
256
|
+
// from the same expression, so they cannot be published apart — and "apart"
|
|
257
|
+
// is not hypothetical, it is the state #1932 shipped: options were published
|
|
258
|
+
// on the capability alone while `decisionId` was published nowhere at all, so
|
|
259
|
+
// the panel drew no buttons and the only way out of an opencode approval in a
|
|
260
|
+
// browser was the arrow-key safety net. Options WITHOUT an id is the worse
|
|
261
|
+
// half of that pair — the numbers would reach the keystroke path, where a
|
|
262
|
+
// bare "1" selects whatever the picker happens to be highlighting (#1681).
|
|
263
|
+
const addressableDecisionId = promptWaiting !== null &&
|
|
264
|
+
(0, prompt_waiting_composition_1.isApiAnswerableStructuredWait)(promptWaiting, eventSource.capabilities.eventIdentity)
|
|
265
|
+
? promptWaiting.decisionId
|
|
266
|
+
: null;
|
|
267
|
+
// Issue #2100 splits the two halves of #2031's single expression, because an
|
|
268
|
+
// addressable decision is now of two KINDS. The id is published for both — it
|
|
269
|
+
// is what `POST /respond` names, and a question needs it exactly as much as an
|
|
270
|
+
// approval does — but the three verdicts belong to an approval alone.
|
|
271
|
+
//
|
|
272
|
+
// Publishing both for a question is not a cosmetic error: it is precisely what
|
|
273
|
+
// #2039's third gate refuses. `readPromptQuestionChoices` returns null the
|
|
274
|
+
// moment `decisionOptions` is non-empty, so a question carrying them would
|
|
275
|
+
// draw `Allow once / Allow always / Reject` over the agent's own choices, and
|
|
276
|
+
// a verdict sent to a question is refused at the source
|
|
277
|
+
// (`question-needs-answer-verdict`). The kind is recovered from the record the
|
|
278
|
+
// same way `structuredEvents.pendingDecisions[].kind` is, through the one
|
|
279
|
+
// reader in `pending-decision-kind`.
|
|
280
|
+
const addressesQuestion = promptWaiting !== null && (0, pending_decision_kind_1.pendingDecisionKind)(promptWaiting.toolName) === 'question';
|
|
281
|
+
const decisionOptions = addressableDecisionId !== null && !addressesQuestion
|
|
282
|
+
? // Issue #2951: in the tool's own words (OpenCode V2: `Always allow`).
|
|
283
|
+
(0, structured_prompt_1.structuredDecisionOptionsFor)(cliToolId)
|
|
284
|
+
: null;
|
|
285
|
+
const structuredFacts = promptWaiting === null
|
|
286
|
+
? null
|
|
287
|
+
: {
|
|
288
|
+
...promptWaiting,
|
|
289
|
+
askUserQuestion: (0, structured_status_merge_1.summarizeAskUserQuestion)(askUserQuestion),
|
|
290
|
+
decisionOptions,
|
|
291
|
+
// Explicit, though the spread above already carries a `decisionId`
|
|
292
|
+
// off the record: the spread's copy is the raw one, and what may be
|
|
293
|
+
// published is the GATED one. Letting the record's value through
|
|
294
|
+
// would put an id on a payload whose verdicts were withheld, which is
|
|
295
|
+
// the biconditional this Issue exists to hold.
|
|
296
|
+
decisionId: addressableDecisionId,
|
|
297
|
+
// Gated on the same value, for a reason of its own: `patterns` is
|
|
298
|
+
// what the `Allow always` BUTTON grants, so publishing it where no
|
|
299
|
+
// button is drawn adds a rule list to a panel that is telling the
|
|
300
|
+
// user to go and answer in the terminal. Every source but opencode
|
|
301
|
+
// therefore keeps the exact payload it had before this Issue, plus
|
|
302
|
+
// the one `decisionId: null`.
|
|
303
|
+
//
|
|
304
|
+
// Issue #2100 moves the gate onto `decisionOptions` rather than the
|
|
305
|
+
// id, which is the same value for every approval and the honest one
|
|
306
|
+
// for a question: a question draws no `Allow always`, so it may not
|
|
307
|
+
// carry the rules one would have saved. (`reportQuestionPending`
|
|
308
|
+
// records `patterns: null` anyway; the two agree by construction now
|
|
309
|
+
// instead of by coincidence.)
|
|
310
|
+
patterns: decisionOptions !== null ? promptWaiting.patterns : null,
|
|
311
|
+
};
|
|
312
|
+
const promptData = startingSince !== null
|
|
313
|
+
? null
|
|
314
|
+
: scraperPromptWaiting
|
|
315
|
+
? correctedPromptData ??
|
|
316
|
+
scraperPromptData ??
|
|
317
|
+
(structuredFacts ? (0, structured_prompt_1.buildStructuredPromptData)(worktreeId, structuredFacts) : null)
|
|
318
|
+
: structuredFacts
|
|
319
|
+
? (0, structured_prompt_1.buildStructuredPromptData)(worktreeId, structuredFacts)
|
|
320
|
+
: null;
|
|
321
|
+
// Issue #2870: whether `/prompt-response` would answer the prompt published
|
|
322
|
+
// above, read by the SAME function it re-verifies with. In #2868 `promptData`
|
|
323
|
+
// came off the generic parser alone, the route refused it, and the UI's Send
|
|
324
|
+
// did nothing. Only for a parser-read prompt: the structured form (hook
|
|
325
|
+
// `decisionId`, degraded) is answered by id or not by number at all, so the
|
|
326
|
+
// key is left out there, as it is when no prompt is up. `isPromptWaiting` /
|
|
327
|
+
// `promptData` / `sessionStatus` are untouched — `wait`'s exit 10, Auto-Yes
|
|
328
|
+
// and push notifications keep reading exactly what they read before.
|
|
329
|
+
const promptAnswerable = scraperPromptWaiting && (correctedPromptData ?? scraperPromptData)
|
|
330
|
+
? (0, auto_yes_dialog_gate_1.assessPromptAnswerability)(cliToolId, output).refusal === null
|
|
331
|
+
: undefined;
|
|
332
|
+
return { structuredFacts, promptData, promptAnswerable };
|
|
333
|
+
}
|
|
334
|
+
/**
|
|
335
|
+
* The scraper's verdict on the frame and the flags read off it (Issue #3272).
|
|
336
|
+
* Split out of {@link buildPayload} in the order it ran there; nothing here
|
|
337
|
+
* awaits.
|
|
338
|
+
*/
|
|
339
|
+
function judgeFrame(worktreeId, cliToolId, instanceId, output) {
|
|
340
|
+
// The sequence from here to `startingStatusResult` is the same as in
|
|
341
|
+
// worktree-status-helper.ts; what follows differs on purpose and is not shared
|
|
342
|
+
// (#3215 "not touched").
|
|
343
|
+
// These six expressions are deliberately kept identical to the same sequence in
|
|
344
|
+
// worktree-status-helper.ts, not shared (#3319 item 37 decision).
|
|
345
|
+
const compositeKey = (0, auto_yes_manager_1.buildCompositeKey)(worktreeId, cliToolId, instanceId);
|
|
346
|
+
const lastServerResponseTimestamp = (0, auto_yes_manager_1.getLastServerResponseTimestamp)(compositeKey);
|
|
347
|
+
const lastOutputTimestamp = lastServerResponseTimestamp ? new Date(lastServerResponseTimestamp) : undefined;
|
|
348
|
+
const rawStatusResult = (0, status_detector_1.detectSessionStatus)(output, cliToolId, lastOutputTimestamp);
|
|
349
|
+
// Issue #3179: a launch in progress. The frame is a shell prompt and the
|
|
350
|
+
// launch line, or a dialog the launch is about to answer, and none of the
|
|
351
|
+
// flags below may be read off it: the floor verdict raised the Navigate pad,
|
|
352
|
+
// the trust dialog raised the selection list / prompt sheet. A dialog that
|
|
353
|
+
// outstays the launch's own answer releases the record (a login, an unknown
|
|
354
|
+
// dialog), and from then on this is the ordinary verdict again.
|
|
355
|
+
const startingSince = (0, session_starting_state_1.observeSessionStartingFrame)(worktreeId, cliToolId, instanceId, rawStatusResult.status === 'waiting' || rawStatusResult.hasActivePrompt);
|
|
356
|
+
const statusResult = startingSince === null
|
|
357
|
+
? rawStatusResult
|
|
358
|
+
: (0, session_starting_state_1.startingStatusResult)(rawStatusResult);
|
|
359
|
+
// Issue #1912: every `running` reason that means "the agent is producing
|
|
360
|
+
// output", not just `thinking_indicator`. opencode answers
|
|
361
|
+
// `opencode_processing_indicator` for its `esc interrupt` footer, which is
|
|
362
|
+
// the only signal it gives between the submitted prompt and the first
|
|
363
|
+
// transcript row — on a scraper-only session that stretch showed no
|
|
364
|
+
// thinking indicator at all.
|
|
365
|
+
const thinking = (0, status_detector_1.isGeneratingStatus)(statusResult);
|
|
366
|
+
const scraperPromptWaiting = statusResult.hasActivePrompt;
|
|
367
|
+
const isSelectionListActive = statusResult.status === 'waiting' && status_detector_1.SELECTION_LIST_REASONS.has(statusResult.reason);
|
|
368
|
+
const isPagerActive = statusResult.reason === status_detector_1.STATUS_REASON.CODEX_PAGER;
|
|
369
|
+
// Issue #2369: an overlay whose ONLY exit is the dismiss — Command Code's
|
|
370
|
+
// `/usage` panel, whose last row is `Press Esc to close`.
|
|
371
|
+
//
|
|
372
|
+
// A THIRD flag rather than a member of `SELECTION_LIST_REASONS`, and the
|
|
373
|
+
// separation is the fix rather than a nicety: `isSelectionListActive` is what
|
|
374
|
+
// the arrow pad is drawn from, and this screen has no highlight to move. Nor
|
|
375
|
+
// is it `isUnclassifiedActive` any more — the detector answers `waiting` for
|
|
376
|
+
// it, and `isUnclassifiedFrame` only ever says yes to `running` — which is
|
|
377
|
+
// exactly what stops the chat surface offering the eighteen-button
|
|
378
|
+
// hatch + answer-key card to a panel that accepts one key.
|
|
379
|
+
//
|
|
380
|
+
// `status === 'waiting'` is checked as well as the reason, the same shape
|
|
381
|
+
// `isSelectionListActive` above uses, so a future producer that publishes the
|
|
382
|
+
// token with some other status cannot silently turn the card into an Esc
|
|
383
|
+
// button.
|
|
384
|
+
const isDismissablePanelActive = statusResult.status === 'waiting'
|
|
385
|
+
&& statusResult.reason === status_detector_1.STATUS_REASON.COMMAND_CODE_DISMISSABLE_PANEL;
|
|
386
|
+
// Issue #1497: the detection-independent nav hatch (#1017/#1494) is gated on
|
|
387
|
+
// isUnclassifiedActive. A static, unrecognized TUI overlay (e.g. Claude `/help`)
|
|
388
|
+
// whose frame stops changing degrades from `running`/`default` to
|
|
389
|
+
// `no_recent_output` once the Auto-Yes poller has stamped lastOutputTimestamp
|
|
390
|
+
// (its sole writer, auto-yes-poller.ts). That is still an interactive-but-
|
|
391
|
+
// unclassified frame — a real idle prompt (`❯`) is classified earlier as
|
|
392
|
+
// `input_prompt`, never as `no_recent_output` — so treat the timed-out fallback
|
|
393
|
+
// as unclassified too and keep the hatch open instead of stranding the user.
|
|
394
|
+
// Issue #1924, §4 D1 decision 2: stated as evidence. Issue #1927 moved that
|
|
395
|
+
// PRODUCER into the detector, because only the detector knows which rule
|
|
396
|
+
// answered — `input_prompt` is positive for a tool whose idle rule vouched for
|
|
397
|
+
// the frame and `'none'` for one whose rule declined, with the same status and
|
|
398
|
+
// the same reason on the wire.
|
|
399
|
+
const evidence = statusResult.evidence;
|
|
400
|
+
// Issue #2011: the flag is NOT that fact, and deriving it from `evidence` is
|
|
401
|
+
// the regression this Issue fixes. `'none'` is "I could not prove this pane is
|
|
402
|
+
// idle" — an ordinary Claude composer with no completion marker above it
|
|
403
|
+
// qualifies, and 7 of 8 live idle panes did on 2026-08-24. The flag's three
|
|
404
|
+
// consumers all ask the older, narrower question instead: `TerminalEscapeHatch`
|
|
405
|
+
// opens on a frame a human has to drive by hand, `wait` suppresses its
|
|
406
|
+
// completion check and eventually exits 10 on one, and `unclassified_frames`
|
|
407
|
+
// records it as a detection failure worth capturing as a fixture. That is
|
|
408
|
+
// `isUnclassifiedFrame` — the reason vocabulary, not the strength of the
|
|
409
|
+
// evidence behind a readable verdict.
|
|
410
|
+
const isUnclassifiedActive = (0, status_evidence_1.isUnclassifiedFrame)(statusResult.status, statusResult.reason);
|
|
411
|
+
return {
|
|
412
|
+
compositeKey,
|
|
413
|
+
lastServerResponseTimestamp,
|
|
414
|
+
statusResult,
|
|
415
|
+
startingSince,
|
|
416
|
+
thinking,
|
|
417
|
+
scraperPromptWaiting,
|
|
418
|
+
isSelectionListActive,
|
|
419
|
+
isPagerActive,
|
|
420
|
+
isDismissablePanelActive,
|
|
421
|
+
evidence,
|
|
422
|
+
isUnclassifiedActive,
|
|
423
|
+
};
|
|
424
|
+
}
|
|
425
|
+
/**
|
|
426
|
+
* The detection-divergence log lines (Issue #3272). Split out of
|
|
427
|
+
* {@link buildPayload} in the order it ran there; it only logs and tracks.
|
|
428
|
+
*/
|
|
429
|
+
function logDetectionDivergence(worktreeId, cliToolId, resolvedInstanceId, statusResult, structured, merged) {
|
|
430
|
+
// Issue #1723 §3: the field data this Epic is being built on. Every line is
|
|
431
|
+
// one poll where the screen and the agent disagreed about what the agent was
|
|
432
|
+
// doing, which is the only way to answer "how wrong was the scraper?" with a
|
|
433
|
+
// number instead of an anecdote. Emitted only on disagreement — a session
|
|
434
|
+
// where the two layers agree is silent — and including the disagreements this
|
|
435
|
+
// merge deliberately does NOT act on (`applied: false`), because those are
|
|
436
|
+
// exactly the cases the next Issues in the Epic have to decide about.
|
|
437
|
+
const diverging = structured !== null && structured.status !== statusResult.status;
|
|
438
|
+
if (diverging) {
|
|
439
|
+
logger.info('detection-divergence', {
|
|
440
|
+
worktreeId,
|
|
441
|
+
cliToolId,
|
|
442
|
+
instanceId: resolvedInstanceId,
|
|
443
|
+
scraperStatus: statusResult.status,
|
|
444
|
+
scraperReason: statusResult.reason,
|
|
445
|
+
structuredStatus: structured.status,
|
|
446
|
+
structuredReason: structured.reason,
|
|
447
|
+
structuredEvent: structured.event,
|
|
448
|
+
structuredEventAt: structured.at,
|
|
449
|
+
applied: merged.structuredApplied,
|
|
450
|
+
});
|
|
451
|
+
}
|
|
452
|
+
// Issue #3311: the length of a disagreement, said once when it ends. The line
|
|
453
|
+
// above is per poll and cannot carry it — see `detection-divergence.ts`.
|
|
454
|
+
const resolvedDivergence = (0, detection_divergence_1.trackDetectionDivergence)(worktreeId, cliToolId, resolvedInstanceId, diverging);
|
|
455
|
+
if (resolvedDivergence !== null) {
|
|
456
|
+
logger.info('detection-divergence-resolved', {
|
|
457
|
+
worktreeId,
|
|
458
|
+
cliToolId,
|
|
459
|
+
instanceId: resolvedInstanceId,
|
|
460
|
+
durationMs: resolvedDivergence.durationMs,
|
|
461
|
+
polls: resolvedDivergence.polls,
|
|
462
|
+
});
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
/**
|
|
466
|
+
* The payload of a running session (Issue #3272). Split out of
|
|
467
|
+
* {@link buildPayload} as it stood there, so the keys keep their order and the
|
|
468
|
+
* reads inside it keep theirs.
|
|
469
|
+
*/
|
|
470
|
+
function buildRunningPayload({ worktreeId, cliToolId, instanceId, sessionName, published, merged, newContent, output, realtimeSnippet, totalLines, lastCapturedLine, isPromptWaiting, promptData, promptAnswerable, autoYesState, lastEnterFallback, isSelectionListActive, isPagerActive, isDismissablePanelActive, startingSince, lastKnown, lastServerResponseTimestamp, compositeKey, stopEventAt, structuredEvents, model, effort, upstreamFaultMatch, paneObstruction, composer, }) {
|
|
471
|
+
return {
|
|
472
|
+
isRunning: true,
|
|
473
|
+
sessionName,
|
|
474
|
+
cliToolId,
|
|
475
|
+
sessionStatus: published.status,
|
|
476
|
+
sessionStatusReason: published.reason,
|
|
477
|
+
content: newContent,
|
|
478
|
+
fullOutput: output,
|
|
479
|
+
realtimeSnippet,
|
|
480
|
+
lineCount: totalLines,
|
|
481
|
+
lastCapturedLine,
|
|
482
|
+
isComplete: isPromptWaiting,
|
|
483
|
+
isGenerating: published.thinking,
|
|
484
|
+
thinking: published.thinking,
|
|
485
|
+
// Issue #2607: named after the tool actually running. A fixed "Claude" was
|
|
486
|
+
// published for every agent, and `capture --json` readers took it at its word.
|
|
487
|
+
thinkingMessage: published.thinking ? `${(0, types_1.getCliToolDisplayName)(cliToolId)} is thinking...` : null,
|
|
488
|
+
isPromptWaiting,
|
|
489
|
+
promptData,
|
|
490
|
+
...(promptAnswerable !== undefined ? { promptAnswerable } : {}),
|
|
491
|
+
// Issue #3184: appended next to the value it is derived from, not at the end
|
|
492
|
+
// — it is a reading of `promptData`, and only of it.
|
|
493
|
+
promptView: (0, prompt_view_1.derivePromptView)(promptData),
|
|
494
|
+
autoYes: {
|
|
495
|
+
enabled: autoYesState?.enabled ?? false,
|
|
496
|
+
expiresAt: autoYesState?.enabled ? autoYesState.expiresAt : null,
|
|
497
|
+
stopReason: autoYesState?.stopReason,
|
|
498
|
+
lastSuppression: (0, auto_yes_suppression_state_1.getLastPolicySuppression)(worktreeId, cliToolId, instanceId),
|
|
499
|
+
// Issue #1694: undefined (so the key is absent from the JSON) unless a
|
|
500
|
+
// stop pattern actually fired — the state clears it on every other path.
|
|
501
|
+
stopMatchedText: autoYesState?.stopMatchedText,
|
|
502
|
+
// Issue #3397: appended last, after the published fields above. Null when
|
|
503
|
+
// Auto-Yes never sent its Enter to an unreadable choice screen here.
|
|
504
|
+
lastEnterFallback,
|
|
505
|
+
},
|
|
506
|
+
isSelectionListActive,
|
|
507
|
+
isPagerActive,
|
|
508
|
+
isDismissablePanelActive,
|
|
509
|
+
isUnclassifiedActive: startingSince === null && merged.isUnclassifiedActive,
|
|
510
|
+
startingSince,
|
|
511
|
+
// Issue #1926: evidence strength, named the way §4 D1 names it. Not the same
|
|
512
|
+
// fact as `isUnclassifiedActive` (Issue #2011); published from the merged
|
|
513
|
+
// verdict so it agrees with the status it accompanies.
|
|
514
|
+
statusEvidence: published.evidence,
|
|
515
|
+
lastKnownStatus: lastKnown?.status ?? null,
|
|
516
|
+
lastKnownStatusAt: lastKnown?.at ?? null,
|
|
517
|
+
lastServerResponseTimestamp,
|
|
518
|
+
serverPollerActive: (0, auto_yes_manager_1.isPollerActive)(compositeKey),
|
|
519
|
+
lastStopEventAt: stopEventAt,
|
|
520
|
+
structuredEvents,
|
|
521
|
+
// Issue #1785: straight from the retention layer, unparsed. See the field
|
|
522
|
+
// docs on CurrentOutputPayload for why nothing is normalised on the way out.
|
|
523
|
+
model,
|
|
524
|
+
reasoningEffort: effort,
|
|
525
|
+
// Issue #1695: appended last on purpose — every field above is a published
|
|
526
|
+
// CLI contract and reordering them churns the diff for no reader's benefit.
|
|
527
|
+
promptDedup: (0, prompt_dedup_state_1.getPromptDedupSkips)(worktreeId, cliToolId, instanceId),
|
|
528
|
+
// Issue #1839: read from `realtimeSnippet`, the same rows an operator sees
|
|
529
|
+
// in `capture --json`, so what the field claims can be checked against what
|
|
530
|
+
// is printed next to it.
|
|
531
|
+
upstreamFault: upstreamFaultMatch
|
|
532
|
+
? {
|
|
533
|
+
id: upstreamFaultMatch.fault.id,
|
|
534
|
+
matchedText: upstreamFaultMatch.matchedText,
|
|
535
|
+
at: Date.now(),
|
|
536
|
+
}
|
|
537
|
+
: null,
|
|
538
|
+
// Issue #2095: the same three keys as `upstreamFault`, read from the same
|
|
539
|
+
// rows, so an operator comparing the two is comparing like with like. The
|
|
540
|
+
// detector's `boxRight` / `rows` stay off the wire — they are how the rule
|
|
541
|
+
// decided, not what a caller acts on, and the payload is already a published
|
|
542
|
+
// contract wide enough to be hard to change.
|
|
543
|
+
paneObstruction: paneObstruction
|
|
544
|
+
? {
|
|
545
|
+
id: paneObstruction.id,
|
|
546
|
+
matchedText: paneObstruction.matchedText,
|
|
547
|
+
at: Date.now(),
|
|
548
|
+
}
|
|
549
|
+
: null,
|
|
550
|
+
// Issue #1879: read from `output` — the RAW capture, still carrying the SGR
|
|
551
|
+
// attributes `capture-pane -e` fetched. Everything else in this function
|
|
552
|
+
// works on stripped text; this one deliberately does not, because dim is the
|
|
553
|
+
// only thing that separates Claude's ghost suggestion from text a human
|
|
554
|
+
// typed. Do not "tidy" this to read a stripped variable.
|
|
555
|
+
composerText: composer.state === 'content' ? composer.text : null,
|
|
556
|
+
composerState: composer.state,
|
|
557
|
+
};
|
|
558
|
+
}
|
|
559
|
+
/**
|
|
560
|
+
* The payload itself, with no knowledge of how its (tool, instance) pair was
|
|
561
|
+
* chosen. Split from {@link buildCurrentOutput} so the resolution fields are
|
|
562
|
+
* appended in one place instead of at both of the two return sites below.
|
|
563
|
+
*/
|
|
564
|
+
async function buildPayload(db, worktreeId, cliToolId, instanceId) {
|
|
565
|
+
const resolvedInstanceId = instanceId ?? cliToolId;
|
|
566
|
+
const manager = manager_1.CLIToolManager.getInstance();
|
|
567
|
+
const cliTool = manager.getTool(cliToolId);
|
|
568
|
+
// Issue #2886: read once, independent of `isRunning`, so both return paths
|
|
569
|
+
// below publish the same value the pane is (or would be) reached at.
|
|
570
|
+
const sessionName = cliTool.getSessionName(worktreeId, instanceId);
|
|
571
|
+
const { stopEventAt, eventSource, askUserQuestion, structuredEvents } = readAgentEvents(worktreeId, cliToolId, instanceId, resolvedInstanceId);
|
|
748
572
|
const running = await cliTool.isRunning(worktreeId, instanceId);
|
|
749
573
|
if (!running) {
|
|
750
574
|
// Issue #1926: the latch describes a process, and this one is gone. Dropped
|
|
751
575
|
// rather than aged out so the next session on this key starts with no
|
|
752
576
|
// history instead of inheriting the last one's verdict.
|
|
753
577
|
(0, status_evidence_1.forgetLastKnownStatus)((0, auto_yes_manager_1.buildCompositeKey)(worktreeId, cliToolId, instanceId));
|
|
578
|
+
// Issue #3300: nothing read off a frame is published here, and that includes
|
|
579
|
+
// `autoYes` — the key is absent, not `enabled: false`. Auto-Yes can still be
|
|
580
|
+
// armed for this instance (it is kept per worktree x instance, not per
|
|
581
|
+
// session); `GET /api/worktrees/:id/auto-yes` answers for it, which is what
|
|
582
|
+
// `commandmate instances` reads and what the browser re-seeds its toggles
|
|
583
|
+
// from (#902). Publishing the key here would make the browser's poll
|
|
584
|
+
// re-seed the toggle, and raise its stop-reason toast, from a session that
|
|
585
|
+
// is not running.
|
|
754
586
|
return {
|
|
755
587
|
isRunning: false,
|
|
756
588
|
// Issue #3179: `beginAgentSession` runs before the pane is created, so a
|
|
@@ -822,82 +654,17 @@ async function buildPayload(db, worktreeId, cliToolId, instanceId) {
|
|
|
822
654
|
// Issue #1910: saturation of the capture WINDOW is only one of the two ways
|
|
823
655
|
// the cursor dies, and it is the one that never fires for the alternate-screen
|
|
824
656
|
// tools — their pane is 1000 rows (claude / copilot) or 200 (opencode), so a
|
|
825
|
-
// 10000-line window is never reached and the branch above stayed false
|
|
826
|
-
// forever. Meanwhile the poller stores `lastCapturedLine` for them too
|
|
827
|
-
// (`updateSessionState` is called unconditionally; only the DEDUP comparison
|
|
828
|
-
// is gated on the tool, response-checker.ts), so after one turn the stored
|
|
829
|
-
// value is the pane height, `slice()` starts past the last row, and `content`
|
|
830
|
-
// — the field `commandmate capture <id>` prints — was an empty string.
|
|
831
|
-
// `capturedLineCountIsCursor` states both conditions in one place.
|
|
832
|
-
const lineCountIsCursor = (0,
|
|
833
|
-
const newLines = lineCountIsCursor ? lines.slice(Math.max(0, lastCapturedLine)) : lines;
|
|
834
|
-
const newContent = newLines.join('\n');
|
|
835
|
-
const compositeKey
|
|
836
|
-
const lastServerResponseTimestamp = (0, auto_yes_manager_1.getLastServerResponseTimestamp)(compositeKey);
|
|
837
|
-
const lastOutputTimestamp = lastServerResponseTimestamp ? new Date(lastServerResponseTimestamp) : undefined;
|
|
838
|
-
const rawStatusResult = (0, status_detector_1.detectSessionStatus)(output, cliToolId, lastOutputTimestamp);
|
|
839
|
-
// Issue #3179: a launch in progress. The frame is a shell prompt and the
|
|
840
|
-
// launch line, or a dialog the launch is about to answer, and none of the
|
|
841
|
-
// flags below may be read off it: the floor verdict raised the Navigate pad,
|
|
842
|
-
// the trust dialog raised the selection list / prompt sheet. A dialog that
|
|
843
|
-
// outstays the launch's own answer releases the record (a login, an unknown
|
|
844
|
-
// dialog), and from then on this is the ordinary verdict again.
|
|
845
|
-
const startingSince = (0, session_starting_state_1.observeSessionStartingFrame)(worktreeId, cliToolId, instanceId, rawStatusResult.status === 'waiting' || rawStatusResult.hasActivePrompt);
|
|
846
|
-
const statusResult = startingSince === null
|
|
847
|
-
? rawStatusResult
|
|
848
|
-
: (0, session_starting_state_1.startingStatusResult)(rawStatusResult);
|
|
849
|
-
// Issue #1912: every `running` reason that means "the agent is producing
|
|
850
|
-
// output", not just `thinking_indicator`. opencode answers
|
|
851
|
-
// `opencode_processing_indicator` for its `esc interrupt` footer, which is
|
|
852
|
-
// the only signal it gives between the submitted prompt and the first
|
|
853
|
-
// transcript row — on a scraper-only session that stretch showed no
|
|
854
|
-
// thinking indicator at all.
|
|
855
|
-
const thinking = (0, status_detector_1.isGeneratingStatus)(statusResult);
|
|
856
|
-
const scraperPromptWaiting = statusResult.hasActivePrompt;
|
|
857
|
-
const isSelectionListActive = statusResult.status === 'waiting' && status_detector_1.SELECTION_LIST_REASONS.has(statusResult.reason);
|
|
858
|
-
const isPagerActive = statusResult.reason === status_detector_1.STATUS_REASON.CODEX_PAGER;
|
|
859
|
-
// Issue #2369: an overlay whose ONLY exit is the dismiss — Command Code's
|
|
860
|
-
// `/usage` panel, whose last row is `Press Esc to close`.
|
|
861
|
-
//
|
|
862
|
-
// A THIRD flag rather than a member of `SELECTION_LIST_REASONS`, and the
|
|
863
|
-
// separation is the fix rather than a nicety: `isSelectionListActive` is what
|
|
864
|
-
// the arrow pad is drawn from, and this screen has no highlight to move. Nor
|
|
865
|
-
// is it `isUnclassifiedActive` any more — the detector answers `waiting` for
|
|
866
|
-
// it, and `isUnclassifiedFrame` only ever says yes to `running` — which is
|
|
867
|
-
// exactly what stops the chat surface offering the eighteen-button
|
|
868
|
-
// hatch + answer-key card to a panel that accepts one key.
|
|
869
|
-
//
|
|
870
|
-
// `status === 'waiting'` is checked as well as the reason, the same shape
|
|
871
|
-
// `isSelectionListActive` above uses, so a future producer that publishes the
|
|
872
|
-
// token with some other status cannot silently turn the card into an Esc
|
|
873
|
-
// button.
|
|
874
|
-
const isDismissablePanelActive = statusResult.status === 'waiting'
|
|
875
|
-
&& statusResult.reason === status_detector_1.STATUS_REASON.COMMAND_CODE_DISMISSABLE_PANEL;
|
|
876
|
-
// Issue #1497: the detection-independent nav hatch (#1017/#1494) is gated on
|
|
877
|
-
// isUnclassifiedActive. A static, unrecognized TUI overlay (e.g. Claude `/help`)
|
|
878
|
-
// whose frame stops changing degrades from `running`/`default` to
|
|
879
|
-
// `no_recent_output` once the Auto-Yes poller has stamped lastOutputTimestamp
|
|
880
|
-
// (its sole writer, auto-yes-poller.ts). That is still an interactive-but-
|
|
881
|
-
// unclassified frame — a real idle prompt (`❯`) is classified earlier as
|
|
882
|
-
// `input_prompt`, never as `no_recent_output` — so treat the timed-out fallback
|
|
883
|
-
// as unclassified too and keep the hatch open instead of stranding the user.
|
|
884
|
-
// Issue #1924, §4 D1 decision 2: stated as evidence. Issue #1927 moved that
|
|
885
|
-
// PRODUCER into the detector, because only the detector knows which rule
|
|
886
|
-
// answered — `input_prompt` is positive for a tool whose idle rule vouched for
|
|
887
|
-
// the frame and `'none'` for one whose rule declined, with the same status and
|
|
888
|
-
// the same reason on the wire.
|
|
889
|
-
const evidence = statusResult.evidence;
|
|
890
|
-
// Issue #2011: the flag is NOT that fact, and deriving it from `evidence` is
|
|
891
|
-
// the regression this Issue fixes. `'none'` is "I could not prove this pane is
|
|
892
|
-
// idle" — an ordinary Claude composer with no completion marker above it
|
|
893
|
-
// qualifies, and 7 of 8 live idle panes did on 2026-08-24. The flag's three
|
|
894
|
-
// consumers all ask the older, narrower question instead: `TerminalEscapeHatch`
|
|
895
|
-
// opens on a frame a human has to drive by hand, `wait` suppresses its
|
|
896
|
-
// completion check and eventually exits 10 on one, and `unclassified_frames`
|
|
897
|
-
// records it as a detection failure worth capturing as a fixture. That is
|
|
898
|
-
// `isUnclassifiedFrame` — the reason vocabulary, not the strength of the
|
|
899
|
-
// evidence behind a readable verdict.
|
|
900
|
-
const isUnclassifiedActive = (0, status_evidence_1.isUnclassifiedFrame)(statusResult.status, statusResult.reason);
|
|
657
|
+
// 10000-line window is never reached and the branch above stayed false
|
|
658
|
+
// forever. Meanwhile the poller stores `lastCapturedLine` for them too
|
|
659
|
+
// (`updateSessionState` is called unconditionally; only the DEDUP comparison
|
|
660
|
+
// is gated on the tool, response-checker.ts), so after one turn the stored
|
|
661
|
+
// value is the pane height, `slice()` starts past the last row, and `content`
|
|
662
|
+
// — the field `commandmate capture <id>` prints — was an empty string.
|
|
663
|
+
// `capturedLineCountIsCursor` states both conditions in one place.
|
|
664
|
+
const lineCountIsCursor = (0, types_1.capturedLineCountIsCursor)(cliToolId, captureWindowSaturated);
|
|
665
|
+
const newLines = lineCountIsCursor ? lines.slice(Math.max(0, lastCapturedLine)) : lines;
|
|
666
|
+
const newContent = newLines.join('\n');
|
|
667
|
+
const { compositeKey, lastServerResponseTimestamp, statusResult, startingSince, thinking, scraperPromptWaiting, isSelectionListActive, isPagerActive, isDismissablePanelActive, evidence, isUnclassifiedActive, } = judgeFrame(worktreeId, cliToolId, instanceId, output);
|
|
901
668
|
// Issue #1723: the two-layer merge. Everything above this line is the string
|
|
902
669
|
// analysis, unchanged and still the only source on a machine where no hook
|
|
903
670
|
// ever fires — `getStructuredSessionState` answers null there, and
|
|
@@ -914,8 +681,18 @@ async function buildPayload(db, worktreeId, cliToolId, instanceId) {
|
|
|
914
681
|
// the scraper's to describe. What it deliberately does NOT do is complete a
|
|
915
682
|
// `commandmate wait` — see SCRAPER_COMPLETION_POLLS, and #1839's measurement
|
|
916
683
|
// of a 529 storm returning Claude to exactly this frame having run nothing.
|
|
917
|
-
|
|
918
|
-
|
|
684
|
+
//
|
|
685
|
+
// Issue #3337: and, for codex, not while its hooks speak for the pane,
|
|
686
|
+
// unless the frame shows the turn was interrupted (`■ Conversation
|
|
687
|
+
// interrupted`, after which no `Stop` comes). A misread frame of a live codex
|
|
688
|
+
// turn closed it here and published `ready` mid-turn; the hook's own `Stop`
|
|
689
|
+
// is the end of the turn there, and `stale` stays the bound on a lost `Stop`.
|
|
690
|
+
// Other tools, Claude included, are closed by the screen as before.
|
|
691
|
+
// The rule is `hook-turn-hold`'s, which the relay's readiness check reads too.
|
|
692
|
+
(0, agent_event_state_1.observeScraperCompletionEvidence)(worktreeId, cliToolId, instanceId, statusResult.status === 'ready' && evidence === 'positive', undefined, (0, hook_turn_hold_1.screenMayEndTurn)(structuredEvents.source.kind, cliToolId, output));
|
|
693
|
+
// Issue #3377: on a frame that shows the hook turn was abandoned, the record's
|
|
694
|
+
// `running` is not published — the rule the list reads too.
|
|
695
|
+
const structured = (0, hook_turn_hold_1.structuredStateForFrame)(worktreeId, cliToolId, instanceId, structuredEvents.source.kind, output);
|
|
919
696
|
// Issue #1725: the open-dialog half of the same merge, resolved before the
|
|
920
697
|
// status merge because it decides one of its inputs. The rule itself — the
|
|
921
698
|
// asymmetric release and the OR below — lives in `prompt-waiting-composition`
|
|
@@ -948,10 +725,10 @@ async function buildPayload(db, worktreeId, cliToolId, instanceId) {
|
|
|
948
725
|
// cannot disagree about when this read is needed — and every other poll (an
|
|
949
726
|
// idle pane, a turn the agent has opened, a tool that reports its own start)
|
|
950
727
|
// costs exactly what it did before this Issue, which is nothing.
|
|
951
|
-
const lastPromptAt = staleReadyCandidate(scraperVerdict, structured, structuredEvents)
|
|
952
|
-
? readNewestPromptAt(db, worktreeId, cliToolId, resolvedInstanceId)
|
|
728
|
+
const lastPromptAt = (0, structured_status_merge_1.staleReadyCandidate)(scraperVerdict, structured, structuredEvents)
|
|
729
|
+
? (0, structured_status_merge_1.readNewestPromptAt)(db, worktreeId, cliToolId, resolvedInstanceId)
|
|
953
730
|
: null;
|
|
954
|
-
const merged = mergeStructuredStatus(scraperVerdict, structured, promptWaiting,
|
|
731
|
+
const merged = (0, structured_status_merge_1.mergeStructuredStatus)(scraperVerdict, structured, promptWaiting,
|
|
955
732
|
// Issue #2011: the same record `structuredEvents` publishes, read once. The
|
|
956
733
|
// merge needs `closedBy` / `closedAt` / `openedAt`, which
|
|
957
734
|
// `StructuredSessionState` folds away into `hook_stop` — and the folded form
|
|
@@ -966,133 +743,17 @@ async function buildPayload(db, worktreeId, cliToolId, instanceId) {
|
|
|
966
743
|
// Issue #3179: not while a launch is in progress. The scraper half is already
|
|
967
744
|
// neutralised above; this also drops a structured wait the launch inherited.
|
|
968
745
|
const isPromptWaiting = startingSince === null && promptResolution.waiting;
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
? (0, ask_user_question_prompt_1.applyAskUserQuestion)(scraperPromptData, askUserQuestion.spec)
|
|
981
|
-
: null;
|
|
982
|
-
// The degraded form, for a dialog only the structured layer can see. Enriched
|
|
983
|
-
// with the question text when one is in flight — that turns "a dialog is open
|
|
984
|
-
// and nobody could read it" into "a dialog is open and here is what it asks".
|
|
985
|
-
//
|
|
986
|
-
// Issue #1898 adds the replies the dialog accepts, for the sources that can be
|
|
987
|
-
// answered without touching the pane. The gate is `eventIdentity`: a source
|
|
988
|
-
// that publishes a per-decision id is a source whose approval can be answered
|
|
989
|
-
// by that id, which is what makes an option NUMBER here mean something other
|
|
990
|
-
// than a line on a screen nobody parsed. `source === 'notification'` narrows
|
|
991
|
-
// it to a dialog the agent actually reported — a `permission-request` record
|
|
992
|
-
// is a prediction, and a question is answered with a choice rather than with
|
|
993
|
-
// one of these three verdicts.
|
|
994
|
-
//
|
|
995
|
-
// Issue #2031 adds the fourth conjunct and folds the whole gate into ONE
|
|
996
|
-
// value. The three verdicts and the id they are delivered to are now derived
|
|
997
|
-
// from the same expression, so they cannot be published apart — and "apart"
|
|
998
|
-
// is not hypothetical, it is the state #1932 shipped: options were published
|
|
999
|
-
// on the capability alone while `decisionId` was published nowhere at all, so
|
|
1000
|
-
// the panel drew no buttons and the only way out of an opencode approval in a
|
|
1001
|
-
// browser was the arrow-key safety net. Options WITHOUT an id is the worse
|
|
1002
|
-
// half of that pair — the numbers would reach the keystroke path, where a
|
|
1003
|
-
// bare "1" selects whatever the picker happens to be highlighting (#1681).
|
|
1004
|
-
const addressableDecisionId = promptWaiting !== null &&
|
|
1005
|
-
promptWaiting.source === 'notification' &&
|
|
1006
|
-
eventSource.capabilities.eventIdentity === 'permission-id' &&
|
|
1007
|
-
(0, structured_prompt_1.isAddressableDecision)(promptWaiting.decisionId)
|
|
1008
|
-
? promptWaiting.decisionId
|
|
1009
|
-
: null;
|
|
1010
|
-
// Issue #2100 splits the two halves of #2031's single expression, because an
|
|
1011
|
-
// addressable decision is now of two KINDS. The id is published for both — it
|
|
1012
|
-
// is what `POST /respond` names, and a question needs it exactly as much as an
|
|
1013
|
-
// approval does — but the three verdicts belong to an approval alone.
|
|
1014
|
-
//
|
|
1015
|
-
// Publishing both for a question is not a cosmetic error: it is precisely what
|
|
1016
|
-
// #2039's third gate refuses. `readPromptQuestionChoices` returns null the
|
|
1017
|
-
// moment `decisionOptions` is non-empty, so a question carrying them would
|
|
1018
|
-
// draw `Allow once / Allow always / Reject` over the agent's own choices, and
|
|
1019
|
-
// a verdict sent to a question is refused at the source
|
|
1020
|
-
// (`question-needs-answer-verdict`). The kind is recovered from the record the
|
|
1021
|
-
// same way `structuredEvents.pendingDecisions[].kind` is, through the one
|
|
1022
|
-
// reader in `pending-decision-kind`.
|
|
1023
|
-
const addressesQuestion = promptWaiting !== null && (0, pending_decision_kind_1.pendingDecisionKind)(promptWaiting.toolName) === 'question';
|
|
1024
|
-
const decisionOptions = addressableDecisionId !== null && !addressesQuestion
|
|
1025
|
-
? // Issue #2951: in the tool's own words (OpenCode V2: `Always allow`).
|
|
1026
|
-
(0, structured_prompt_1.structuredDecisionOptionsFor)(cliToolId)
|
|
1027
|
-
: null;
|
|
1028
|
-
const structuredFacts = promptWaiting === null
|
|
1029
|
-
? null
|
|
1030
|
-
: {
|
|
1031
|
-
...promptWaiting,
|
|
1032
|
-
askUserQuestion: summarizeAskUserQuestion(askUserQuestion),
|
|
1033
|
-
decisionOptions,
|
|
1034
|
-
// Explicit, though the spread above already carries a `decisionId`
|
|
1035
|
-
// off the record: the spread's copy is the raw one, and what may be
|
|
1036
|
-
// published is the GATED one. Letting the record's value through
|
|
1037
|
-
// would put an id on a payload whose verdicts were withheld, which is
|
|
1038
|
-
// the biconditional this Issue exists to hold.
|
|
1039
|
-
decisionId: addressableDecisionId,
|
|
1040
|
-
// Gated on the same value, for a reason of its own: `patterns` is
|
|
1041
|
-
// what the `Allow always` BUTTON grants, so publishing it where no
|
|
1042
|
-
// button is drawn adds a rule list to a panel that is telling the
|
|
1043
|
-
// user to go and answer in the terminal. Every source but opencode
|
|
1044
|
-
// therefore keeps the exact payload it had before this Issue, plus
|
|
1045
|
-
// the one `decisionId: null`.
|
|
1046
|
-
//
|
|
1047
|
-
// Issue #2100 moves the gate onto `decisionOptions` rather than the
|
|
1048
|
-
// id, which is the same value for every approval and the honest one
|
|
1049
|
-
// for a question: a question draws no `Allow always`, so it may not
|
|
1050
|
-
// carry the rules one would have saved. (`reportQuestionPending`
|
|
1051
|
-
// records `patterns: null` anyway; the two agree by construction now
|
|
1052
|
-
// instead of by coincidence.)
|
|
1053
|
-
patterns: decisionOptions !== null ? promptWaiting.patterns : null,
|
|
1054
|
-
};
|
|
1055
|
-
const promptData = startingSince !== null
|
|
1056
|
-
? null
|
|
1057
|
-
: scraperPromptWaiting
|
|
1058
|
-
? correctedPromptData ??
|
|
1059
|
-
scraperPromptData ??
|
|
1060
|
-
(structuredFacts ? (0, structured_prompt_1.buildStructuredPromptData)(worktreeId, structuredFacts) : null)
|
|
1061
|
-
: structuredFacts
|
|
1062
|
-
? (0, structured_prompt_1.buildStructuredPromptData)(worktreeId, structuredFacts)
|
|
1063
|
-
: null;
|
|
1064
|
-
// Issue #2870: whether `/prompt-response` would answer the prompt published
|
|
1065
|
-
// above, read by the SAME function it re-verifies with. In #2868 `promptData`
|
|
1066
|
-
// came off the generic parser alone, the route refused it, and the UI's Send
|
|
1067
|
-
// did nothing. Only for a parser-read prompt: the structured form (hook
|
|
1068
|
-
// `decisionId`, degraded) is answered by id or not by number at all, so the
|
|
1069
|
-
// key is left out there, as it is when no prompt is up. `isPromptWaiting` /
|
|
1070
|
-
// `promptData` / `sessionStatus` are untouched — `wait`'s exit 10, Auto-Yes
|
|
1071
|
-
// and push notifications keep reading exactly what they read before.
|
|
1072
|
-
const promptAnswerable = scraperPromptWaiting && (correctedPromptData ?? scraperPromptData)
|
|
1073
|
-
? (0, auto_yes_dialog_gate_1.assessPromptAnswerability)(cliToolId, output).refusal === null
|
|
1074
|
-
: undefined;
|
|
1075
|
-
// Issue #1723 §3: the field data this Epic is being built on. Every line is
|
|
1076
|
-
// one poll where the screen and the agent disagreed about what the agent was
|
|
1077
|
-
// doing, which is the only way to answer "how wrong was the scraper?" with a
|
|
1078
|
-
// number instead of an anecdote. Emitted only on disagreement — a session
|
|
1079
|
-
// where the two layers agree is silent — and including the disagreements this
|
|
1080
|
-
// merge deliberately does NOT act on (`applied: false`), because those are
|
|
1081
|
-
// exactly the cases the next Issues in the Epic have to decide about.
|
|
1082
|
-
if (structured !== null && structured.status !== statusResult.status) {
|
|
1083
|
-
logger.info('detection-divergence', {
|
|
1084
|
-
worktreeId,
|
|
1085
|
-
cliToolId,
|
|
1086
|
-
instanceId: resolvedInstanceId,
|
|
1087
|
-
scraperStatus: statusResult.status,
|
|
1088
|
-
scraperReason: statusResult.reason,
|
|
1089
|
-
structuredStatus: structured.status,
|
|
1090
|
-
structuredReason: structured.reason,
|
|
1091
|
-
structuredEvent: structured.event,
|
|
1092
|
-
structuredEventAt: structured.at,
|
|
1093
|
-
applied: merged.structuredApplied,
|
|
1094
|
-
});
|
|
1095
|
-
}
|
|
746
|
+
const { structuredFacts, promptData, promptAnswerable } = composePromptData({
|
|
747
|
+
worktreeId,
|
|
748
|
+
cliToolId,
|
|
749
|
+
output,
|
|
750
|
+
statusResult,
|
|
751
|
+
startingSince,
|
|
752
|
+
promptWaiting,
|
|
753
|
+
askUserQuestion,
|
|
754
|
+
eventSource,
|
|
755
|
+
});
|
|
756
|
+
logDetectionDivergence(worktreeId, cliToolId, resolvedInstanceId, statusResult, structured, merged);
|
|
1096
757
|
// Issue #1839 defined this window and Issue #2095 reuses it, so it is computed
|
|
1097
758
|
// once, here, above the first reader. The 100 rows are also what the payload
|
|
1098
759
|
// publishes, which is the property both fields rest on: what they claim can be
|
|
@@ -1136,7 +797,7 @@ async function buildPayload(db, worktreeId, cliToolId, instanceId) {
|
|
|
1136
797
|
// the shell and the launch line — so no row, and the run starts afresh after.
|
|
1137
798
|
const unclassifiedVerdict = (0, unclassified_frame_tracker_1.observeUnclassifiedFrame)(compositeKey, merged.isUnclassifiedActive && !answerableOverAgentApi && startingSince === null);
|
|
1138
799
|
if (unclassifiedVerdict.shouldRecord) {
|
|
1139
|
-
recordUnclassifiedFrame(db, {
|
|
800
|
+
(0, current_output_history_writers_1.recordUnclassifiedFrame)(db, {
|
|
1140
801
|
worktreeId,
|
|
1141
802
|
cliToolId,
|
|
1142
803
|
instanceId: resolvedInstanceId,
|
|
@@ -1185,7 +846,7 @@ async function buildPayload(db, worktreeId, cliToolId, instanceId) {
|
|
|
1185
846
|
!promptWaiting.recorded &&
|
|
1186
847
|
!answerableOverAgentApi) {
|
|
1187
848
|
(0, agent_event_state_1.markStructuredPromptRecorded)(worktreeId, cliToolId, instanceId);
|
|
1188
|
-
recordStructuredPrompt(db, {
|
|
849
|
+
(0, current_output_history_writers_1.recordStructuredPrompt)(db, {
|
|
1189
850
|
worktreeId,
|
|
1190
851
|
cliToolId,
|
|
1191
852
|
instanceId: resolvedInstanceId,
|
|
@@ -1231,353 +892,39 @@ async function buildPayload(db, worktreeId, cliToolId, instanceId) {
|
|
|
1231
892
|
evidence: published.evidence,
|
|
1232
893
|
});
|
|
1233
894
|
const lastKnown = (0, status_evidence_1.getLastKnownStatus)(compositeKey);
|
|
1234
|
-
return {
|
|
1235
|
-
|
|
1236
|
-
sessionName,
|
|
895
|
+
return buildRunningPayload({
|
|
896
|
+
worktreeId,
|
|
1237
897
|
cliToolId,
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
898
|
+
instanceId,
|
|
899
|
+
sessionName,
|
|
900
|
+
published,
|
|
901
|
+
merged,
|
|
902
|
+
newContent,
|
|
903
|
+
output,
|
|
1242
904
|
realtimeSnippet,
|
|
1243
|
-
|
|
905
|
+
totalLines,
|
|
1244
906
|
lastCapturedLine,
|
|
1245
|
-
isComplete: isPromptWaiting,
|
|
1246
|
-
isGenerating: published.thinking,
|
|
1247
|
-
thinking: published.thinking,
|
|
1248
|
-
// Issue #2607: named after the tool actually running. A fixed "Claude" was
|
|
1249
|
-
// published for every agent, and `capture --json` readers took it at its word.
|
|
1250
|
-
thinkingMessage: published.thinking ? `${(0, types_2.getCliToolDisplayName)(cliToolId)} is thinking...` : null,
|
|
1251
907
|
isPromptWaiting,
|
|
1252
908
|
promptData,
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
//
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
expiresAt: autoYesState?.enabled ? autoYesState.expiresAt : null,
|
|
1260
|
-
stopReason: autoYesState?.stopReason,
|
|
1261
|
-
lastSuppression: (0, auto_yes_suppression_state_1.getLastPolicySuppression)(worktreeId, cliToolId, instanceId),
|
|
1262
|
-
// Issue #1694: undefined (so the key is absent from the JSON) unless a
|
|
1263
|
-
// stop pattern actually fired — the state clears it on every other path.
|
|
1264
|
-
stopMatchedText: autoYesState?.stopMatchedText,
|
|
1265
|
-
},
|
|
909
|
+
promptAnswerable,
|
|
910
|
+
autoYesState,
|
|
911
|
+
// Issue #3397: judged against the screen-read prompt only — the one
|
|
912
|
+
// `promptAnswerable` is published for, and the one the poller sent its
|
|
913
|
+
// Enter to. A structured (hook / degraded) prompt is never that screen.
|
|
914
|
+
lastEnterFallback: (0, auto_yes_enter_fallback_1.publishEnterFallback)((0, auto_yes_enter_fallback_1.getLastEnterFallback)(worktreeId, cliToolId, instanceId), promptAnswerable !== undefined ? statusResult.promptDetection.promptData : null),
|
|
1266
915
|
isSelectionListActive,
|
|
1267
916
|
isPagerActive,
|
|
1268
917
|
isDismissablePanelActive,
|
|
1269
|
-
isUnclassifiedActive: startingSince === null && merged.isUnclassifiedActive,
|
|
1270
918
|
startingSince,
|
|
1271
|
-
|
|
1272
|
-
// §4 D1 names it. Published from the merged verdict so the two cannot
|
|
1273
|
-
// disagree on the wire.
|
|
1274
|
-
statusEvidence: published.evidence,
|
|
1275
|
-
lastKnownStatus: lastKnown?.status ?? null,
|
|
1276
|
-
lastKnownStatusAt: lastKnown?.at ?? null,
|
|
919
|
+
lastKnown,
|
|
1277
920
|
lastServerResponseTimestamp,
|
|
1278
|
-
|
|
1279
|
-
|
|
921
|
+
compositeKey,
|
|
922
|
+
stopEventAt,
|
|
1280
923
|
structuredEvents,
|
|
1281
|
-
// Issue #1785: straight from the retention layer, unparsed. See the field
|
|
1282
|
-
// docs on CurrentOutputPayload for why nothing is normalised on the way out.
|
|
1283
924
|
model,
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
// Issue #1839: read from `realtimeSnippet`, the same rows an operator sees
|
|
1289
|
-
// in `capture --json`, so what the field claims can be checked against what
|
|
1290
|
-
// is printed next to it.
|
|
1291
|
-
upstreamFault: upstreamFaultMatch
|
|
1292
|
-
? {
|
|
1293
|
-
id: upstreamFaultMatch.fault.id,
|
|
1294
|
-
matchedText: upstreamFaultMatch.matchedText,
|
|
1295
|
-
at: Date.now(),
|
|
1296
|
-
}
|
|
1297
|
-
: null,
|
|
1298
|
-
// Issue #2095: the same three keys as `upstreamFault`, read from the same
|
|
1299
|
-
// rows, so an operator comparing the two is comparing like with like. The
|
|
1300
|
-
// detector's `boxRight` / `rows` stay off the wire — they are how the rule
|
|
1301
|
-
// decided, not what a caller acts on, and the payload is already a published
|
|
1302
|
-
// contract wide enough to be hard to change.
|
|
1303
|
-
paneObstruction: paneObstruction
|
|
1304
|
-
? {
|
|
1305
|
-
id: paneObstruction.id,
|
|
1306
|
-
matchedText: paneObstruction.matchedText,
|
|
1307
|
-
at: Date.now(),
|
|
1308
|
-
}
|
|
1309
|
-
: null,
|
|
1310
|
-
// Issue #1879: read from `output` — the RAW capture, still carrying the SGR
|
|
1311
|
-
// attributes `capture-pane -e` fetched. Everything else in this function
|
|
1312
|
-
// works on stripped text; this one deliberately does not, because dim is the
|
|
1313
|
-
// only thing that separates Claude's ghost suggestion from text a human
|
|
1314
|
-
// typed. Do not "tidy" this to read a stripped variable.
|
|
1315
|
-
composerText: composer.state === 'content' ? composer.text : null,
|
|
1316
|
-
composerState: composer.state,
|
|
1317
|
-
};
|
|
1318
|
-
}
|
|
1319
|
-
function newChatTurnProgressState() {
|
|
1320
|
-
return {
|
|
1321
|
-
version: 0,
|
|
1322
|
-
askedAt: Number.NEGATIVE_INFINITY,
|
|
1323
|
-
turnKey: null,
|
|
1324
|
-
body: null,
|
|
1325
|
-
loggedOutcome: null,
|
|
1326
|
-
loggedTurnKey: null,
|
|
1327
|
-
};
|
|
1328
|
-
}
|
|
1329
|
-
/**
|
|
1330
|
-
* On `globalThis` for the reason every shared map in this subsystem is (#1736):
|
|
1331
|
-
* under `next dev` the poller's bundle and the opencode subscription's bundle
|
|
1332
|
-
* would each get a private copy, and two producers throttling against two
|
|
1333
|
-
* different maps is no throttle at all.
|
|
1334
|
-
*/
|
|
1335
|
-
const chatTurnProgressState = (globalThis.__chatTurnProgressState ??= new Map());
|
|
1336
|
-
function chatTurnProgressKey(target) {
|
|
1337
|
-
return `${target.worktreeId}:${target.cliToolId}:${target.instanceId ?? target.cliToolId}`;
|
|
1338
|
-
}
|
|
1339
|
-
/** Forget every instance's progress bookkeeping. Test seam. */
|
|
1340
|
-
function resetChatTurnProgressState() {
|
|
1341
|
-
chatTurnProgressState.clear();
|
|
1342
|
-
}
|
|
1343
|
-
/**
|
|
1344
|
-
* Say once, at info, what happened to this instance's progress frames (Issue #2248).
|
|
1345
|
-
*
|
|
1346
|
-
* ## Why info, and why this file had none
|
|
1347
|
-
*
|
|
1348
|
-
* Every outcome here was `logger.debug`, including the two that mean the reader
|
|
1349
|
-
* is watching a blank space: a push that threw, and a room with no subscribers.
|
|
1350
|
-
* Issue #2248 was opened after a live session where the body reached the browser
|
|
1351
|
-
* — or did not — and the server logs could not answer which, because debug is
|
|
1352
|
-
* off in the builds people run. An unobservable push is a feature that can only
|
|
1353
|
-
* be debugged by reproducing it.
|
|
1354
|
-
*
|
|
1355
|
-
* ## Why once
|
|
1356
|
-
*
|
|
1357
|
-
* The publisher runs on EVERY poll tick of a generating session. Logging each
|
|
1358
|
-
* tick at info would put a line per second per agent into the operator's log and
|
|
1359
|
-
* make the level useless, so this collapses a run of identical ticks into its
|
|
1360
|
-
* first: the outcome and the turn it happened on are the identity, and a repeat
|
|
1361
|
-
* of the pair says nothing the first line did not.
|
|
1362
|
-
*
|
|
1363
|
-
* The consequences of that identity, both deliberate:
|
|
1364
|
-
*
|
|
1365
|
-
* - **a new turn always logs**, because `turnKey` is part of the pair. That is
|
|
1366
|
-
* the acceptance criterion — one info line per turn — and the reason
|
|
1367
|
-
* `loggedTurnKey` exists at all rather than a bare boolean;
|
|
1368
|
-
* - **a CHANGE of outcome always logs**, so "the subscriber left" and "it
|
|
1369
|
-
* started failing" are both visible the tick they happen, without the
|
|
1370
|
-
* steady state that follows repeating them.
|
|
1371
|
-
*
|
|
1372
|
-
* `no-subscribers` and `failed` carry no turn key — neither knows one; the first
|
|
1373
|
-
* returns before the source is asked and the second is why there is no frame —
|
|
1374
|
-
* so for them the pair is `(outcome, null)` and the run collapses to one line
|
|
1375
|
-
* until something else happens.
|
|
1376
|
-
*/
|
|
1377
|
-
function logChatTurnProgressOutcome(target, outcome, turnKey, extra = {}) {
|
|
1378
|
-
const key = chatTurnProgressKey(target);
|
|
1379
|
-
const state = chatTurnProgressState.get(key) ?? newChatTurnProgressState();
|
|
1380
|
-
chatTurnProgressState.set(key, state);
|
|
1381
|
-
if (state.loggedOutcome === outcome && state.loggedTurnKey === turnKey)
|
|
1382
|
-
return;
|
|
1383
|
-
state.loggedOutcome = outcome;
|
|
1384
|
-
state.loggedTurnKey = turnKey;
|
|
1385
|
-
// Written out rather than interpolated: these strings are what an operator
|
|
1386
|
-
// greps the log for, and an interpolated name cannot be found in the source.
|
|
1387
|
-
const message = outcome === 'published'
|
|
1388
|
-
? 'chat-turn-progress-published'
|
|
1389
|
-
: outcome === 'no-subscribers'
|
|
1390
|
-
? 'chat-turn-progress-no-subscribers'
|
|
1391
|
-
: 'chat-turn-progress-failed';
|
|
1392
|
-
logger.info(message, {
|
|
1393
|
-
worktreeId: target.worktreeId,
|
|
1394
|
-
cliToolId: target.cliToolId,
|
|
1395
|
-
instanceId: target.instanceId ?? target.cliToolId,
|
|
1396
|
-
turnKey,
|
|
1397
|
-
...extra,
|
|
925
|
+
effort,
|
|
926
|
+
upstreamFaultMatch,
|
|
927
|
+
paneObstruction,
|
|
928
|
+
composer,
|
|
1398
929
|
});
|
|
1399
930
|
}
|
|
1400
|
-
/**
|
|
1401
|
-
* Decide whether this instance has a new progress frame, and build it.
|
|
1402
|
-
*
|
|
1403
|
-
* The single generator both tools go through, on the same argument
|
|
1404
|
-
* {@link buildCurrentOutput} makes for the terminal payload: two producers of one
|
|
1405
|
-
* wire shape drift, and the drift is invisible because each of them is
|
|
1406
|
-
* individually correct. Three rules, in this order, and each of them is a way
|
|
1407
|
-
* this feature goes wrong if it is missing:
|
|
1408
|
-
*
|
|
1409
|
-
* 1. **Throttle.** At most one *ask* per {@link CHAT_TURN_PROGRESS_MIN_INTERVAL_MS}
|
|
1410
|
-
* per instance. It gates the ask rather than the send because the source is
|
|
1411
|
-
* the expensive half (see {@link ChatTurnProgressSource}); a gate on the send
|
|
1412
|
-
* alone would re-read a 4 MiB transcript to discover it had not changed.
|
|
1413
|
-
* 2. **No-change suppression.** Same turn, same body → no frame. A reply that
|
|
1414
|
-
* has stopped growing must not keep waking every subscribed browser.
|
|
1415
|
-
* 3. **Monotonic version**, per instance and NOT per turn. A client drops
|
|
1416
|
-
* anything at or below what it has already rendered, and restarting the
|
|
1417
|
-
* counter on a new turn would make the first frame of turn N+1 look stale
|
|
1418
|
-
* against the last frame of turn N.
|
|
1419
|
-
*
|
|
1420
|
-
* The body is bounded by {@link truncateChatTurnProgressBody}, whose cut is
|
|
1421
|
-
* folded into `partial` together with the reader's own — see
|
|
1422
|
-
* {@link ChatTurnProgressEvent.partial} for why one flag answers both.
|
|
1423
|
-
*
|
|
1424
|
-
* Never throws: a source that throws is a turn with nothing to show, not a
|
|
1425
|
-
* broken poll tick.
|
|
1426
|
-
*
|
|
1427
|
-
* @param target - The instance
|
|
1428
|
-
* @param source - Asked only when the throttle allows it
|
|
1429
|
-
* @param now - Epoch ms; injected by the tests
|
|
1430
|
-
* @returns The frame to broadcast, or null when there is nothing new to say
|
|
1431
|
-
*/
|
|
1432
|
-
async function buildChatTurnProgress(target, source, now = Date.now()) {
|
|
1433
|
-
const key = chatTurnProgressKey(target);
|
|
1434
|
-
const state = chatTurnProgressState.get(key) ?? newChatTurnProgressState();
|
|
1435
|
-
if (now - state.askedAt < types_1.CHAT_TURN_PROGRESS_MIN_INTERVAL_MS)
|
|
1436
|
-
return null;
|
|
1437
|
-
state.askedAt = now;
|
|
1438
|
-
chatTurnProgressState.set(key, state);
|
|
1439
|
-
let draft;
|
|
1440
|
-
try {
|
|
1441
|
-
draft = await source();
|
|
1442
|
-
}
|
|
1443
|
-
catch {
|
|
1444
|
-
// The readers already promise never to throw; this is the belt for the
|
|
1445
|
-
// dynamic import that reaches them.
|
|
1446
|
-
return null;
|
|
1447
|
-
}
|
|
1448
|
-
if (!draft || draft.body.length === 0)
|
|
1449
|
-
return null;
|
|
1450
|
-
const bounded = (0, types_1.truncateChatTurnProgressBody)(draft.body);
|
|
1451
|
-
if (draft.turnKey === state.turnKey && bounded.body === state.body)
|
|
1452
|
-
return null;
|
|
1453
|
-
state.turnKey = draft.turnKey;
|
|
1454
|
-
state.body = bounded.body;
|
|
1455
|
-
state.version += 1;
|
|
1456
|
-
return {
|
|
1457
|
-
type: types_1.CHAT_TURN_PROGRESS_EVENT_TYPE,
|
|
1458
|
-
worktreeId: target.worktreeId,
|
|
1459
|
-
cliToolId: target.cliToolId,
|
|
1460
|
-
instanceId: target.instanceId ?? target.cliToolId,
|
|
1461
|
-
turnKey: draft.turnKey,
|
|
1462
|
-
body: bounded.body,
|
|
1463
|
-
partial: bounded.truncated || draft.partial === true,
|
|
1464
|
-
version: state.version,
|
|
1465
|
-
done: false,
|
|
1466
|
-
};
|
|
1467
|
-
}
|
|
1468
|
-
/**
|
|
1469
|
-
* Build a progress frame and push it to the worktree room.
|
|
1470
|
-
*
|
|
1471
|
-
* Subscribers are checked FIRST, before the throttle state is touched and long
|
|
1472
|
-
* before the source is asked, so a session nobody is watching costs one map
|
|
1473
|
-
* lookup per tick — the same bargain `broadcastTerminalSnapshot` strikes, and
|
|
1474
|
-
* the reason a 4 MiB transcript read does not happen for every claude session on
|
|
1475
|
-
* the machine.
|
|
1476
|
-
*
|
|
1477
|
-
* The `ws-server` import is dynamic so this module's *other* callers — the
|
|
1478
|
-
* `/current-output` route and `commandmate capture` — do not pull the WebSocket
|
|
1479
|
-
* server into their graph just by building a payload.
|
|
1480
|
-
*
|
|
1481
|
-
* Never throws; a failed push is a moment of staleness on a surface that has the
|
|
1482
|
-
* settled row coming anyway.
|
|
1483
|
-
*
|
|
1484
|
-
* @returns Whether a frame was broadcast
|
|
1485
|
-
*/
|
|
1486
|
-
async function emitChatTurnProgress(target, source, now = Date.now()) {
|
|
1487
|
-
try {
|
|
1488
|
-
const { broadcast, hasRoomSubscribers } = await Promise.resolve().then(() => __importStar(require('../../lib/ws-server')));
|
|
1489
|
-
if (!hasRoomSubscribers(target.worktreeId)) {
|
|
1490
|
-
// Issue #2248. Reported, not silent: "the body never reached the browser"
|
|
1491
|
-
// and "nobody was listening" look identical from a screenshot, and this is
|
|
1492
|
-
// the only place that can tell them apart.
|
|
1493
|
-
logChatTurnProgressOutcome(target, 'no-subscribers', null);
|
|
1494
|
-
return false;
|
|
1495
|
-
}
|
|
1496
|
-
const event = await buildChatTurnProgress(target, source, now);
|
|
1497
|
-
// Not logged: null is the throttle, an unchanged body, or a turn with no
|
|
1498
|
-
// text yet — the quiet, correct majority of ticks.
|
|
1499
|
-
if (!event)
|
|
1500
|
-
return false;
|
|
1501
|
-
broadcast(target.worktreeId, event);
|
|
1502
|
-
logChatTurnProgressOutcome(target, 'published', event.turnKey, {
|
|
1503
|
-
version: event.version,
|
|
1504
|
-
bodyLength: event.body.length,
|
|
1505
|
-
partial: event.partial,
|
|
1506
|
-
});
|
|
1507
|
-
return true;
|
|
1508
|
-
}
|
|
1509
|
-
catch (error) {
|
|
1510
|
-
logChatTurnProgressOutcome(target, 'failed', null, {
|
|
1511
|
-
error: error instanceof Error ? error.message : String(error),
|
|
1512
|
-
});
|
|
1513
|
-
return false;
|
|
1514
|
-
}
|
|
1515
|
-
}
|
|
1516
|
-
/**
|
|
1517
|
-
* Ask the tool that has a transcript for the body of its open turn (Issue #2199).
|
|
1518
|
-
*
|
|
1519
|
-
* claude only, and that is a statement about which tools have a *pull* reader
|
|
1520
|
-
* rather than a list to extend by hand. opencode's body arrives on its own SSE
|
|
1521
|
-
* stream and is published from `sources/opencode/history` at the moment a part
|
|
1522
|
-
* lands — polling for it here would be a second producer of the same frames.
|
|
1523
|
-
* codex and antigravity have neither, and stay on the indicator (#2197 / #2198).
|
|
1524
|
-
*
|
|
1525
|
-
* The reader is imported dynamically so `sources/claude/history` — and through
|
|
1526
|
-
* it `fs/promises`, the session-pointer latch and `user-turn-recorder` — stays
|
|
1527
|
-
* out of the module graph of the `/current-output` route and of `commandmate
|
|
1528
|
-
* capture`, both of which reach this file for a payload and nothing else.
|
|
1529
|
-
*
|
|
1530
|
-
* ## Which half the caller waits for (Issue #2248)
|
|
1531
|
-
*
|
|
1532
|
-
* The caller awaits the CHEAP half — the worktree row and "is anybody watching"
|
|
1533
|
-
* — and not the expensive one. That split is what `void` was protecting in the
|
|
1534
|
-
* first place: the 4 MiB transcript read, the JSONL parse, the render and the
|
|
1535
|
-
* broadcast still run detached, so no poll tick and no WebSocket snapshot waits
|
|
1536
|
-
* on them.
|
|
1537
|
-
*
|
|
1538
|
-
* What awaiting the prelude buys is the reason this Issue exists: the line that
|
|
1539
|
-
* says whether anybody received the frame is written while the tick it belongs
|
|
1540
|
-
* to is still the tick in progress. Left inside the detached half it landed
|
|
1541
|
-
* wherever the event loop happened to get to it, next to some unrelated
|
|
1542
|
-
* request's output, which is not a log an operator can read backwards.
|
|
1543
|
-
*
|
|
1544
|
-
* Never throws: `emitChatTurnProgress` swallows, and so does this.
|
|
1545
|
-
*/
|
|
1546
|
-
async function publishChatTurnProgress(db, worktreeId, cliToolId, instanceId) {
|
|
1547
|
-
if (cliToolId !== 'claude')
|
|
1548
|
-
return;
|
|
1549
|
-
const target = { worktreeId, cliToolId, instanceId };
|
|
1550
|
-
try {
|
|
1551
|
-
// The slug the transcript directory is named after is a function of the
|
|
1552
|
-
// worktree's path, so the row is what makes the file findable at all. Read
|
|
1553
|
-
// before the throttle only because it is a single indexed lookup against a
|
|
1554
|
-
// connection this function was handed; everything expensive is behind the
|
|
1555
|
-
// gate.
|
|
1556
|
-
const { getWorktreeById } = await Promise.resolve().then(() => __importStar(require('../../lib/db/worktree-db')));
|
|
1557
|
-
const worktreePath = getWorktreeById(db, worktreeId)?.path;
|
|
1558
|
-
if (!worktreePath)
|
|
1559
|
-
return;
|
|
1560
|
-
// Issue #2248. Hoisted out of `emitChatTurnProgress` — which still makes the
|
|
1561
|
-
// same check for its other caller — so the answer, and the line that reports
|
|
1562
|
-
// it, are settled before this function returns. Costs one Map lookup on the
|
|
1563
|
-
// sessions nobody is watching, which is the bargain that was already being
|
|
1564
|
-
// struck one frame later.
|
|
1565
|
-
const { hasRoomSubscribers } = await Promise.resolve().then(() => __importStar(require('../../lib/ws-server')));
|
|
1566
|
-
if (!hasRoomSubscribers(worktreeId)) {
|
|
1567
|
-
logChatTurnProgressOutcome(target, 'no-subscribers', null);
|
|
1568
|
-
return;
|
|
1569
|
-
}
|
|
1570
|
-
// Detached on purpose: everything past this point is the transcript read.
|
|
1571
|
-
void emitChatTurnProgress(target, async () => {
|
|
1572
|
-
const { readClaudeTurnProgress } = await Promise.resolve().then(() => __importStar(require('../../lib/hooks/sources/claude/history')));
|
|
1573
|
-
return readClaudeTurnProgress({ worktreeId, cliToolId, instanceId: instanceId ?? cliToolId }, { worktreePath });
|
|
1574
|
-
});
|
|
1575
|
-
}
|
|
1576
|
-
catch (error) {
|
|
1577
|
-
// Issue #2248: the reader's half of the same failure — the worktree row or
|
|
1578
|
-
// one of the dynamic imports — on the same once-per-run gate as the push's.
|
|
1579
|
-
logChatTurnProgressOutcome(target, 'failed', null, {
|
|
1580
|
-
error: error instanceof Error ? error.message : String(error),
|
|
1581
|
-
});
|
|
1582
|
-
}
|
|
1583
|
-
}
|