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
|
@@ -39,604 +39,75 @@
|
|
|
39
39
|
* @module lib/session/agent-event-state
|
|
40
40
|
*/
|
|
41
41
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
42
|
-
exports.
|
|
43
|
-
exports.isSameAgentModelName = isSameAgentModelName;
|
|
44
|
-
exports.onAgentModelChange = onAgentModelChange;
|
|
45
|
-
exports.getAgentModelBaseline = getAgentModelBaseline;
|
|
46
|
-
exports.getAgentEventDropCounts = getAgentEventDropCounts;
|
|
47
|
-
exports.getPendingDecisions = getPendingDecisions;
|
|
48
|
-
exports.getAgentTurn = getAgentTurn;
|
|
49
|
-
exports.getPublishedAgentTurn = getPublishedAgentTurn;
|
|
50
|
-
exports.closeAgentTurn = closeAgentTurn;
|
|
51
|
-
exports.observeScraperCompletionEvidence = observeScraperCompletionEvidence;
|
|
42
|
+
exports.STRUCTURED_PROMPT_PROVISIONAL_MAX_AGE_MS = exports.recordAskUserQuestion = exports.getAskUserQuestion = exports.clearAskUserQuestion = exports.isAwaitingInstruction = exports.getAwaitingInstruction = exports.STRUCTURED_STATE_MAX_AGE_MS = exports.getStructuredSessionState = exports.observeScraperCompletionEvidence = exports.joinOpenTurnFromDuplicate = exports.getPublishedAgentTurn = exports.getPendingDecisions = exports.getAgentTurn = exports.closeAgentTurn = exports.recordCapturedModelInfo = exports.recordAgentReportedModel = exports.recordAgentReportedEffort = exports.onAgentModelChange = exports.isSameAgentModelName = exports.getResolvedAgentModelInfo = exports.getLastReportedAgentEffort = exports.getLastKnownAgentModel = exports.getLastKnownAgentEffort = exports.getLastCapturedModelInfo = exports.getAgentModelBaseline = exports.AGENT_MODEL_EFFORT_SUFFIX_PATTERN = exports.shortSessionTag = exports.LIFECYCLE_AGENT_EVENT_TYPES = exports.isDuplicateAgentEvent = exports.getRecentEventKeyCount = exports.getRecentEventIdentityCount = exports.getAgentEventDropCounts = exports.classifyAgentEventDelivery = exports.agentEventKeyClaimedAt = exports.AGENT_EVENT_DEDUP_WINDOW_MS = void 0;
|
|
52
43
|
exports.recordAgentStopEvent = recordAgentStopEvent;
|
|
53
44
|
exports.getLastStopEventAt = getLastStopEventAt;
|
|
54
45
|
exports.recordAgentEvent = recordAgentEvent;
|
|
55
|
-
exports.getAwaitingInstruction = getAwaitingInstruction;
|
|
56
|
-
exports.isAwaitingInstruction = isAwaitingInstruction;
|
|
57
46
|
exports.getLastAgentEvent = getLastAgentEvent;
|
|
58
|
-
exports.getLastKnownAgentModel = getLastKnownAgentModel;
|
|
59
|
-
exports.recordAgentReportedModel = recordAgentReportedModel;
|
|
60
|
-
exports.recordCapturedModelInfo = recordCapturedModelInfo;
|
|
61
|
-
exports.getLastCapturedModelInfo = getLastCapturedModelInfo;
|
|
62
|
-
exports.getResolvedAgentModelInfo = getResolvedAgentModelInfo;
|
|
63
|
-
exports.recordAgentReportedEffort = recordAgentReportedEffort;
|
|
64
|
-
exports.getLastReportedAgentEffort = getLastReportedAgentEffort;
|
|
65
|
-
exports.getLastKnownAgentEffort = getLastKnownAgentEffort;
|
|
66
47
|
exports.beginAgentEventGeneration = beginAgentEventGeneration;
|
|
67
48
|
exports.getAgentEventGenerationStartedAt = getAgentEventGenerationStartedAt;
|
|
68
49
|
exports.discardAgentEventState = discardAgentEventState;
|
|
69
|
-
exports.getStructuredSessionState = getStructuredSessionState;
|
|
70
50
|
exports.reportPermissionRequestPending = reportPermissionRequestPending;
|
|
71
51
|
exports.reportQuestionPending = reportQuestionPending;
|
|
72
52
|
exports.getStructuredPromptWaiting = getStructuredPromptWaiting;
|
|
73
53
|
exports.corroborateStructuredPromptWaiting = corroborateStructuredPromptWaiting;
|
|
74
54
|
exports.markStructuredPromptRecorded = markStructuredPromptRecorded;
|
|
75
55
|
exports.clearStructuredPromptWaiting = clearStructuredPromptWaiting;
|
|
76
|
-
exports.recordAskUserQuestion = recordAskUserQuestion;
|
|
77
|
-
exports.getAskUserQuestion = getAskUserQuestion;
|
|
78
|
-
exports.clearAskUserQuestion = clearAskUserQuestion;
|
|
79
|
-
exports.isDuplicateAgentEvent = isDuplicateAgentEvent;
|
|
80
|
-
exports.getRecentEventKeyCount = getRecentEventKeyCount;
|
|
81
|
-
exports.classifyAgentEventDelivery = classifyAgentEventDelivery;
|
|
82
|
-
exports.getRecentEventIdentityCount = getRecentEventIdentityCount;
|
|
83
56
|
exports.clearAgentStopEvents = clearAgentStopEvents;
|
|
84
57
|
const auto_yes_state_1 = require("../../lib/auto-yes-state");
|
|
85
|
-
const
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
// this module already reached for `ASK_USER_QUESTION_TOOL` — and importing it
|
|
89
|
-
// here rather than repeating `'question'` is what keeps the release rule below
|
|
90
|
-
// and `pendingDecisionKind`'s reader from drifting apart.
|
|
91
|
-
const pending_decision_kind_1 = require("../../lib/hooks/pending-decision-kind");
|
|
92
|
-
// Issue #1784: the terminal-frame half of "which model / effort is this on".
|
|
93
|
-
const model_info_extractor_1 = require("../../lib/detection/model-info-extractor");
|
|
94
|
-
const status_mapping_1 = require("../../lib/session/status-mapping");
|
|
95
|
-
// Issue #1930: the turn model. Pure, so importing it here costs nothing at
|
|
96
|
-
// runtime beyond the two bounds it owns.
|
|
58
|
+
const auto_yes_enter_fallback_state_1 = require("../../lib/polling/auto-yes-enter-fallback-state");
|
|
59
|
+
const agent_event_dedup_1 = require("../../lib/session/agent-event-dedup");
|
|
60
|
+
const agent_event_model_1 = require("../../lib/session/agent-event-model");
|
|
97
61
|
const provisional_turn_1 = require("../../lib/session/provisional-turn");
|
|
62
|
+
const agent_event_turn_1 = require("../../lib/session/agent-event-turn");
|
|
63
|
+
const agent_event_structured_state_1 = require("../../lib/session/agent-event-structured-state");
|
|
64
|
+
const agent_event_awaiting_instruction_1 = require("../../lib/session/agent-event-awaiting-instruction");
|
|
65
|
+
const agent_event_ask_user_question_1 = require("../../lib/session/agent-event-ask-user-question");
|
|
66
|
+
const global_state_1 = require("../global-state");
|
|
67
|
+
var agent_event_dedup_2 = require("../../lib/session/agent-event-dedup");
|
|
68
|
+
Object.defineProperty(exports, "AGENT_EVENT_DEDUP_WINDOW_MS", { enumerable: true, get: function () { return agent_event_dedup_2.AGENT_EVENT_DEDUP_WINDOW_MS; } });
|
|
69
|
+
Object.defineProperty(exports, "agentEventKeyClaimedAt", { enumerable: true, get: function () { return agent_event_dedup_2.agentEventKeyClaimedAt; } });
|
|
70
|
+
Object.defineProperty(exports, "classifyAgentEventDelivery", { enumerable: true, get: function () { return agent_event_dedup_2.classifyAgentEventDelivery; } });
|
|
71
|
+
Object.defineProperty(exports, "getAgentEventDropCounts", { enumerable: true, get: function () { return agent_event_dedup_2.getAgentEventDropCounts; } });
|
|
72
|
+
Object.defineProperty(exports, "getRecentEventIdentityCount", { enumerable: true, get: function () { return agent_event_dedup_2.getRecentEventIdentityCount; } });
|
|
73
|
+
Object.defineProperty(exports, "getRecentEventKeyCount", { enumerable: true, get: function () { return agent_event_dedup_2.getRecentEventKeyCount; } });
|
|
74
|
+
Object.defineProperty(exports, "isDuplicateAgentEvent", { enumerable: true, get: function () { return agent_event_dedup_2.isDuplicateAgentEvent; } });
|
|
75
|
+
Object.defineProperty(exports, "LIFECYCLE_AGENT_EVENT_TYPES", { enumerable: true, get: function () { return agent_event_dedup_2.LIFECYCLE_AGENT_EVENT_TYPES; } });
|
|
76
|
+
Object.defineProperty(exports, "shortSessionTag", { enumerable: true, get: function () { return agent_event_dedup_2.shortSessionTag; } });
|
|
77
|
+
var agent_event_model_2 = require("../../lib/session/agent-event-model");
|
|
78
|
+
Object.defineProperty(exports, "AGENT_MODEL_EFFORT_SUFFIX_PATTERN", { enumerable: true, get: function () { return agent_event_model_2.AGENT_MODEL_EFFORT_SUFFIX_PATTERN; } });
|
|
79
|
+
Object.defineProperty(exports, "getAgentModelBaseline", { enumerable: true, get: function () { return agent_event_model_2.getAgentModelBaseline; } });
|
|
80
|
+
Object.defineProperty(exports, "getLastCapturedModelInfo", { enumerable: true, get: function () { return agent_event_model_2.getLastCapturedModelInfo; } });
|
|
81
|
+
Object.defineProperty(exports, "getLastKnownAgentEffort", { enumerable: true, get: function () { return agent_event_model_2.getLastKnownAgentEffort; } });
|
|
82
|
+
Object.defineProperty(exports, "getLastKnownAgentModel", { enumerable: true, get: function () { return agent_event_model_2.getLastKnownAgentModel; } });
|
|
83
|
+
Object.defineProperty(exports, "getLastReportedAgentEffort", { enumerable: true, get: function () { return agent_event_model_2.getLastReportedAgentEffort; } });
|
|
84
|
+
Object.defineProperty(exports, "getResolvedAgentModelInfo", { enumerable: true, get: function () { return agent_event_model_2.getResolvedAgentModelInfo; } });
|
|
85
|
+
Object.defineProperty(exports, "isSameAgentModelName", { enumerable: true, get: function () { return agent_event_model_2.isSameAgentModelName; } });
|
|
86
|
+
Object.defineProperty(exports, "onAgentModelChange", { enumerable: true, get: function () { return agent_event_model_2.onAgentModelChange; } });
|
|
87
|
+
Object.defineProperty(exports, "recordAgentReportedEffort", { enumerable: true, get: function () { return agent_event_model_2.recordAgentReportedEffort; } });
|
|
88
|
+
Object.defineProperty(exports, "recordAgentReportedModel", { enumerable: true, get: function () { return agent_event_model_2.recordAgentReportedModel; } });
|
|
89
|
+
Object.defineProperty(exports, "recordCapturedModelInfo", { enumerable: true, get: function () { return agent_event_model_2.recordCapturedModelInfo; } });
|
|
90
|
+
var agent_event_turn_2 = require("../../lib/session/agent-event-turn");
|
|
91
|
+
Object.defineProperty(exports, "closeAgentTurn", { enumerable: true, get: function () { return agent_event_turn_2.closeAgentTurn; } });
|
|
92
|
+
Object.defineProperty(exports, "getAgentTurn", { enumerable: true, get: function () { return agent_event_turn_2.getAgentTurn; } });
|
|
93
|
+
Object.defineProperty(exports, "getPendingDecisions", { enumerable: true, get: function () { return agent_event_turn_2.getPendingDecisions; } });
|
|
94
|
+
Object.defineProperty(exports, "getPublishedAgentTurn", { enumerable: true, get: function () { return agent_event_turn_2.getPublishedAgentTurn; } });
|
|
95
|
+
Object.defineProperty(exports, "joinOpenTurnFromDuplicate", { enumerable: true, get: function () { return agent_event_turn_2.joinOpenTurnFromDuplicate; } });
|
|
96
|
+
Object.defineProperty(exports, "observeScraperCompletionEvidence", { enumerable: true, get: function () { return agent_event_turn_2.observeScraperCompletionEvidence; } });
|
|
97
|
+
var agent_event_structured_state_2 = require("../../lib/session/agent-event-structured-state");
|
|
98
|
+
Object.defineProperty(exports, "getStructuredSessionState", { enumerable: true, get: function () { return agent_event_structured_state_2.getStructuredSessionState; } });
|
|
99
|
+
Object.defineProperty(exports, "STRUCTURED_STATE_MAX_AGE_MS", { enumerable: true, get: function () { return agent_event_structured_state_2.STRUCTURED_STATE_MAX_AGE_MS; } });
|
|
100
|
+
var agent_event_awaiting_instruction_2 = require("../../lib/session/agent-event-awaiting-instruction");
|
|
101
|
+
Object.defineProperty(exports, "getAwaitingInstruction", { enumerable: true, get: function () { return agent_event_awaiting_instruction_2.getAwaitingInstruction; } });
|
|
102
|
+
Object.defineProperty(exports, "isAwaitingInstruction", { enumerable: true, get: function () { return agent_event_awaiting_instruction_2.isAwaitingInstruction; } });
|
|
103
|
+
var agent_event_ask_user_question_2 = require("../../lib/session/agent-event-ask-user-question");
|
|
104
|
+
Object.defineProperty(exports, "clearAskUserQuestion", { enumerable: true, get: function () { return agent_event_ask_user_question_2.clearAskUserQuestion; } });
|
|
105
|
+
Object.defineProperty(exports, "getAskUserQuestion", { enumerable: true, get: function () { return agent_event_ask_user_question_2.getAskUserQuestion; } });
|
|
106
|
+
Object.defineProperty(exports, "recordAskUserQuestion", { enumerable: true, get: function () { return agent_event_ask_user_question_2.recordAskUserQuestion; } });
|
|
98
107
|
/** compositeKey -> epoch ms of the most recent stop event. */
|
|
99
|
-
const lastStopEventAt =
|
|
100
|
-
(globalThis.__agentEventLastStopAt = new Map());
|
|
108
|
+
const lastStopEventAt = (0, global_state_1.getOrInitGlobal)('__agentEventLastStopAt', () => new Map());
|
|
101
109
|
/** compositeKey -> the most recent event of any kind. */
|
|
102
|
-
const lastAgentEvent =
|
|
103
|
-
(globalThis.__agentEventLast = new Map());
|
|
104
|
-
/** compositeKey -> epoch ms the current generation began. See {@link beginAgentEventGeneration}. */
|
|
105
|
-
const generationStartedAt = globalThis.__agentEventGenerationStartedAt ??
|
|
106
|
-
(globalThis.__agentEventGenerationStartedAt = new Map());
|
|
107
|
-
/**
|
|
108
|
-
* compositeKey -> the turn this instance is in, or the last one it was in
|
|
109
|
-
* (Issue #1930).
|
|
110
|
-
*
|
|
111
|
-
* The map that replaced "the newest event is the verdict". `lastAgentEvent`
|
|
112
|
-
* above is still written on every delivery and is still what
|
|
113
|
-
* `structuredEvents.lastEventType` publishes — it answers "did anything reach
|
|
114
|
-
* this server, and for the right instance?", which is a diagnostic question.
|
|
115
|
-
* This map answers the state question, and the two deliberately disagree
|
|
116
|
-
* whenever an event carries no verdict.
|
|
117
|
-
*
|
|
118
|
-
* The open dialogs #1725 kept in a map of their own live on the turn now
|
|
119
|
-
* ({@link TurnRecord.pendingDecisions}), because a dialog only ever happens
|
|
120
|
-
* *inside* a turn and holding them apart is what let a generation bump retire
|
|
121
|
-
* one and not the other.
|
|
122
|
-
*/
|
|
123
|
-
const agentTurns = globalThis.__agentEventTurns ??
|
|
124
|
-
(globalThis.__agentEventTurns = new Map());
|
|
125
|
-
/** compositeKey -> what this instance has had dropped, and why (Issue #1930). */
|
|
126
|
-
const dropCounts = globalThis.__agentEventDrops ??
|
|
127
|
-
(globalThis.__agentEventDrops = new Map());
|
|
128
|
-
/**
|
|
129
|
-
* Monotonic suffix for {@link TurnRecord.turnId}.
|
|
130
|
-
*
|
|
131
|
-
* Two turns can open in the same millisecond — a `post_tool_use` closing one
|
|
132
|
-
* agent's turn while another's `user_prompt_submit` lands — and an id built from
|
|
133
|
-
* the timestamp alone would then compare equal across instances. `wait` reads a
|
|
134
|
-
* change of id as "a new turn began", so the collision would be a missed turn
|
|
135
|
-
* boundary rather than a cosmetic clash.
|
|
136
|
-
*/
|
|
137
|
-
const turnSequence = globalThis.__agentEventTurnSeq ??
|
|
138
|
-
(globalThis.__agentEventTurnSeq = { value: 0 });
|
|
139
|
-
/** compositeKey -> the `AskUserQuestion` call currently in flight (#1726). */
|
|
140
|
-
const askUserQuestion = globalThis.__agentEventAskUserQuestion ??
|
|
141
|
-
(globalThis.__agentEventAskUserQuestion = new Map());
|
|
142
|
-
/** dedup key -> epoch ms it was first seen. See {@link isDuplicateAgentEvent}. */
|
|
143
|
-
const recentEventKeys = globalThis.__agentEventRecentKeys ??
|
|
144
|
-
(globalThis.__agentEventRecentKeys = new Map());
|
|
145
|
-
/**
|
|
146
|
-
* compositeKey -> (identity key -> epoch ms it was first seen) (Issue #1899).
|
|
147
|
-
*
|
|
148
|
-
* Nested rather than flat so the bound is *per instance*, which the flat
|
|
149
|
-
* {@link recentEventKeys} is not: one chatty agent must not be able to evict
|
|
150
|
-
* another's ids and let a genuine re-delivery through on a quiet pane. See
|
|
151
|
-
* {@link claimEventIdentity}.
|
|
152
|
-
*/
|
|
153
|
-
const recentEventIdentities = globalThis.__agentEventRecentIdentities ??
|
|
154
|
-
(globalThis.__agentEventRecentIdentities = new Map());
|
|
155
|
-
/** compositeKey -> the agent's own "I am waiting for instructions" (#1786). */
|
|
156
|
-
const awaitingInstruction = globalThis.__agentEventAwaitingInstruction ??
|
|
157
|
-
(globalThis.__agentEventAwaitingInstruction = new Map());
|
|
158
|
-
/**
|
|
159
|
-
* compositeKey -> the last non-null model this instance reported (Issue #1783).
|
|
160
|
-
*
|
|
161
|
-
* A *separate* map, not a field read off {@link lastAgentEvent}, and that is the
|
|
162
|
-
* whole point of it. `lastAgentEvent` is replaced wholesale on every delivery,
|
|
163
|
-
* and Claude puts the model on `SessionStart` and on a `PostModelSwitch`
|
|
164
|
-
* (Issue #2363) and on nothing else — so the very next `UserPromptSubmit` would
|
|
165
|
-
* overwrite the only record that ever knew it, and
|
|
166
|
-
* the UI would show the model for the fraction of a second between session start
|
|
167
|
-
* and the first prompt. Keeping the last *non-null* sighting separately is what
|
|
168
|
-
* makes "which model is this session on" answerable at all for that tool.
|
|
169
|
-
*
|
|
170
|
-
* Never written with null: absent means "nothing has ever said", which is the
|
|
171
|
-
* honest state for gemini, copilot, and any session that predates this server
|
|
172
|
-
* process. See {@link getLastKnownAgentModel}.
|
|
173
|
-
*/
|
|
174
|
-
const lastAgentModel = globalThis.__agentEventLastModel ??
|
|
175
|
-
(globalThis.__agentEventLastModel = new Map());
|
|
176
|
-
/**
|
|
177
|
-
* compositeKey -> epoch ms of the report that last wrote {@link lastAgentModel}
|
|
178
|
-
* (Issue #2361).
|
|
179
|
-
*
|
|
180
|
-
* Kept so the frame can be judged NEWER than the hook. When #2361 was measured
|
|
181
|
-
* on 2.1.263, Claude named its model on `SessionStart` and on nothing
|
|
182
|
-
* CommandMate registered — `/model` fired none of the seven events, and the
|
|
183
|
-
* `SessionStart` a `/clear` emits carries no `model` key — so after a switch
|
|
184
|
-
* the hook latch was a true statement about a process that had since changed
|
|
185
|
-
* its mind, and the screen was the only channel that heard it. Issue #2363
|
|
186
|
-
* registers `PostModelSwitch`, which re-stamps this on every switch; the stamp
|
|
187
|
-
* now matters for the sessions where that hook does not arrive. See
|
|
188
|
-
* {@link resolveAgentModel}.
|
|
189
|
-
*/
|
|
190
|
-
const lastAgentModelAt = globalThis.__agentEventLastModelAt ??
|
|
191
|
-
(globalThis.__agentEventLastModelAt = new Map());
|
|
192
|
-
/**
|
|
193
|
-
* compositeKey -> what the terminal frame last showed for this instance (#1784).
|
|
194
|
-
*
|
|
195
|
-
* The second source, and a strictly different kind of fact from
|
|
196
|
-
* {@link lastAgentModel}: that one is the agent naming itself over the hook
|
|
197
|
-
* channel, this one is the TUI's own chrome read back off the screen. It is
|
|
198
|
-
* kept apart rather than folded into the first map because the two have a
|
|
199
|
-
* precedence between them ({@link getResolvedAgentModelInfo}) — merging on write
|
|
200
|
-
* would let a scraped display name ("Gemini 3.7 Flash") overwrite the exact id
|
|
201
|
-
* the agent reported, with nothing left to recover it from.
|
|
202
|
-
*
|
|
203
|
-
* Latched the same way and for a sharper reason: Claude prints its model in the
|
|
204
|
-
* startup banner and nowhere else, and tmux keeps 2000 lines of history, so on
|
|
205
|
-
* any session that has been talking for a while the banner is simply gone. The
|
|
206
|
-
* screen going quiet is not the model changing, so the last non-null sighting
|
|
207
|
-
* stands until the process it described does not (see
|
|
208
|
-
* {@link beginAgentEventGeneration} / {@link discardAgentEventState}).
|
|
209
|
-
*/
|
|
210
|
-
const capturedModelInfo = globalThis.__agentCapturedModelInfo ??
|
|
211
|
-
(globalThis.__agentCapturedModelInfo = new Map());
|
|
212
|
-
/**
|
|
213
|
-
* compositeKey -> the effort the *agent itself* last reported (Issue #2048).
|
|
214
|
-
*
|
|
215
|
-
* The third source, and until #2048 there was no such thing: #1784's whole
|
|
216
|
-
* premise was that "no hook payload of any tool carries an effort field", which
|
|
217
|
-
* was true of the five push tools and stayed true of opencode's own screen —
|
|
218
|
-
* `model-info-extractor` still records that opencode prints no effort anywhere
|
|
219
|
-
* in its pane, across 17 live frames.
|
|
220
|
-
*
|
|
221
|
-
* What changed is the *channel*, not the screen. opencode calls it a **variant**
|
|
222
|
-
* and publishes it on the frames CommandMate is subscribed to —
|
|
223
|
-
* `Session.model.variant` on `session.updated` and `info.variant` on
|
|
224
|
-
* `message.updated`, measured on 1.18.22 in an isolated `HOME`
|
|
225
|
-
* (`docs/design/opencode-server-live-verification.md` §20.4). The names in that
|
|
226
|
-
* catalogue are `low` / `medium` / `high` / `max` / `minimal` / `none` /
|
|
227
|
-
* `xhigh`, and each one's entry carries an `effort` (or `reasoningEffort`) equal
|
|
228
|
-
* to itself — so the value is an effort in opencode's own vocabulary as well as
|
|
229
|
-
* in this map's.
|
|
230
|
-
*
|
|
231
|
-
* Kept apart from {@link capturedModelInfo} for the reason that map is kept
|
|
232
|
-
* apart from {@link lastAgentModel}: there is a precedence between them
|
|
233
|
-
* ({@link getResolvedAgentModelInfo}), and merging on write would lose it.
|
|
234
|
-
* Latched the same way, and never written with null — a turn that reports no
|
|
235
|
-
* variant is opencode running the model's default, not the previous choice
|
|
236
|
-
* being withdrawn.
|
|
237
|
-
*/
|
|
238
|
-
const reportedEffort = globalThis.__agentEventLastEffort ??
|
|
239
|
-
(globalThis.__agentEventLastEffort = new Map());
|
|
240
|
-
/**
|
|
241
|
-
* compositeKey -> the model this instance was last observed on (Issue #2357).
|
|
242
|
-
*
|
|
243
|
-
* A *fourth* map beside the three latches rather than a comparison against
|
|
244
|
-
* them, because the latches are what they are for a reason: each remembers the
|
|
245
|
-
* last non-null value its channel produced, and none of them can say "and the
|
|
246
|
-
* value before that". This map holds exactly that — the previous answer of
|
|
247
|
-
* {@link getResolvedAgentModelInfo} — so a write to either latch can be judged
|
|
248
|
-
* as "same model", "first sighting" or "changed" without re-deriving history
|
|
249
|
-
* from maps that never kept it.
|
|
250
|
-
*
|
|
251
|
-
* Cleared wherever the latches are cleared (generation, discard, test seam) and
|
|
252
|
-
* on `session_start`, so the first model a new agent process reports is
|
|
253
|
-
* recorded as its starting model and announced to nobody.
|
|
254
|
-
*/
|
|
255
|
-
const modelBaseline = globalThis.__agentModelBaseline ??
|
|
256
|
-
(globalThis.__agentModelBaseline = new Map());
|
|
257
|
-
/**
|
|
258
|
-
* Listeners for the model edge, on `globalThis` for the reason the maps above
|
|
259
|
-
* are: under `next dev` each route bundle evaluates this module once, and the
|
|
260
|
-
* hook route (which writes the hook latch) and the status poll (which writes
|
|
261
|
-
* the frame latch) are different bundles. A module-scoped set would let each
|
|
262
|
-
* bundle notify only the listeners registered in the same bundle — which is
|
|
263
|
-
* none, for the one that receives the hook.
|
|
264
|
-
*/
|
|
265
|
-
const modelChangeListeners = globalThis.__agentModelChangeListeners ??
|
|
266
|
-
(globalThis.__agentModelChangeListeners = new Set());
|
|
267
|
-
/**
|
|
268
|
-
* A trailing reasoning-effort token in a model id (`gemini-3.7-flash-high`).
|
|
269
|
-
*
|
|
270
|
-
* The same five words `model-info-extractor` recognises, matched only at the
|
|
271
|
-
* very end and only after a separator, so `gpt-5-mini` keeps its `mini` and a
|
|
272
|
-
* model that merely ends in these letters is untouched. agy encodes the effort
|
|
273
|
-
* in the id it reports on every event (`deriveEffortFromModelId`), and a user
|
|
274
|
-
* moving the same model from `high` to `low` has not changed model.
|
|
275
|
-
*/
|
|
276
|
-
exports.AGENT_MODEL_EFFORT_SUFFIX_PATTERN = /[-_ ](?:minimal|low|medium|high|xhigh)$/i;
|
|
277
|
-
/**
|
|
278
|
-
* Tokens a display label carries that the id does not, dropped before comparing.
|
|
279
|
-
*
|
|
280
|
-
* Measured pairs only: claude's banner reads `Opus 5 (1M context)` for the id
|
|
281
|
-
* `claude-opus-5[1m]` — the `1m` is part of the model, the word is not.
|
|
282
|
-
*/
|
|
283
|
-
const MODEL_LABEL_NOISE_PATTERN = /\bcontext\b/gi;
|
|
284
|
-
/** Lowercase alphanumerics, effort suffix and label-only words removed. */
|
|
285
|
-
function modelNameKey(value) {
|
|
286
|
-
return value
|
|
287
|
-
.replace(exports.AGENT_MODEL_EFFORT_SUFFIX_PATTERN, '')
|
|
288
|
-
.replace(MODEL_LABEL_NOISE_PATTERN, '')
|
|
289
|
-
.toLowerCase()
|
|
290
|
-
.replace(/[^a-z0-9]/g, '');
|
|
291
|
-
}
|
|
292
|
-
/**
|
|
293
|
-
* Do two model names name the same model? (Issue #2357)
|
|
294
|
-
*
|
|
295
|
-
* Within one channel the answer is exact on alphanumerics — `GPT-5 mini` and
|
|
296
|
-
* `gpt-5-mini` are one model rendered two ways (copilot's bar and its notice,
|
|
297
|
-
* both read off the frame), while `gpt-5` and `gpt-5-mini` are two models and
|
|
298
|
-
* the second is precisely the downgrade this Issue exists to announce. The
|
|
299
|
-
* effort suffix is stripped first, because agy's id carries it and an effort
|
|
300
|
-
* change is not a model change.
|
|
301
|
-
*
|
|
302
|
-
* *Across* channels the rule is widened to containment: the hook reports an
|
|
303
|
-
* id, the screen prints a label, and every measured pair is the label's key
|
|
304
|
-
* inside the id's (`gemini37flash` ⊂ `gemini37flashhigh`, `opus51m` ⊂
|
|
305
|
-
* `claudeopus51m`, `claudesonnet46` ⊂ `anthropicclaudesonnet46`). Containment
|
|
306
|
-
* is NOT applied within a channel — it would fold `gpt-5` into `gpt-5-mini` —
|
|
307
|
-
* and a caller has to say which case it is in.
|
|
308
|
-
*
|
|
309
|
-
* Exported for the tests and for anything that has to agree with this module
|
|
310
|
-
* about identity; deliberately local rather than the extractor's `sameModel`,
|
|
311
|
-
* which is a single-channel exact match and is shared with #2358's edit.
|
|
312
|
-
*/
|
|
313
|
-
function isSameAgentModelName(a, b, options = {}) {
|
|
314
|
-
const ka = modelNameKey(a);
|
|
315
|
-
const kb = modelNameKey(b);
|
|
316
|
-
if (ka === kb)
|
|
317
|
-
return true;
|
|
318
|
-
if (!options.crossSource)
|
|
319
|
-
return false;
|
|
320
|
-
if (ka.length === 0 || kb.length === 0)
|
|
321
|
-
return false;
|
|
322
|
-
return ka.includes(kb) || kb.includes(ka);
|
|
323
|
-
}
|
|
324
|
-
/**
|
|
325
|
-
* Subscribe to the model edge (Issue #2357).
|
|
326
|
-
*
|
|
327
|
-
* The listener receives every transition {@link observeAgentModel} judged to be
|
|
328
|
-
* a change — never a first sighting, never a value going quiet. `#2357`'s
|
|
329
|
-
* realtime broadcaster and push notifier are the two subscribers; neither keeps
|
|
330
|
-
* a "what was it last time?" of its own, which is the point of this being an
|
|
331
|
-
* edge rather than a level.
|
|
332
|
-
*
|
|
333
|
-
* @returns The unsubscribe function
|
|
334
|
-
*/
|
|
335
|
-
function onAgentModelChange(listener) {
|
|
336
|
-
modelChangeListeners.add(listener);
|
|
337
|
-
return () => {
|
|
338
|
-
modelChangeListeners.delete(listener);
|
|
339
|
-
};
|
|
340
|
-
}
|
|
341
|
-
/** Deliver one change; a listener that throws must not disturb the write that observed it. */
|
|
342
|
-
function emitAgentModelChange(change) {
|
|
343
|
-
for (const listener of modelChangeListeners) {
|
|
344
|
-
try {
|
|
345
|
-
listener(change);
|
|
346
|
-
}
|
|
347
|
-
catch {
|
|
348
|
-
// Advisory. The hook route and the status poll are the callers, and a
|
|
349
|
-
// notification failure is not a reason to fail either.
|
|
350
|
-
}
|
|
351
|
-
}
|
|
352
|
-
}
|
|
353
|
-
/**
|
|
354
|
-
* The model this instance was last observed on, or null. Test seam.
|
|
355
|
-
*/
|
|
356
|
-
function getAgentModelBaseline(worktreeId, cliToolId, instanceId) {
|
|
357
|
-
return modelBaseline.get((0, auto_yes_state_1.buildCompositeKey)(worktreeId, cliToolId, instanceId)) ?? null;
|
|
358
|
-
}
|
|
359
|
-
/**
|
|
360
|
-
* Judge the resolved model after a latch wrote, and announce a change
|
|
361
|
-
* (Issue #2357).
|
|
362
|
-
*
|
|
363
|
-
* Called after every write to {@link lastAgentModel} and
|
|
364
|
-
* {@link capturedModelInfo}, with the channel that wrote. What it compares is
|
|
365
|
-
* {@link getResolvedAgentModelInfo}'s *model* — the merged value the UI shows —
|
|
366
|
-
* against the previous merged value this map remembers. The suppression rules,
|
|
367
|
-
* each of which is a measured false positive:
|
|
368
|
-
*
|
|
369
|
-
* - **`null → value`** (first report, server restart): recorded as the
|
|
370
|
-
* starting model, announced to nobody.
|
|
371
|
-
* - **`value → null`**: cannot reach here — the latches never write null, so
|
|
372
|
-
* a frame that stopped showing the banner leaves the merged value where it
|
|
373
|
-
* was. A generation reset clears this map along with the latches, so the
|
|
374
|
-
* next value is a first sighting again.
|
|
375
|
-
* - **same name, either channel**: `GPT-5 mini` after `gpt-5-mini` is not a
|
|
376
|
-
* change. Compared with {@link isSameAgentModelName}; the cross-channel
|
|
377
|
-
* widening applies only when the merged value's source actually moved.
|
|
378
|
-
* - **`session_start`**: {@link recordAgentEvent} drops the baseline first, so
|
|
379
|
-
* the model a relaunched process reports is that process's starting model.
|
|
380
|
-
*
|
|
381
|
-
* Everything else is a change and is emitted exactly once — the baseline is
|
|
382
|
-
* advanced before the listeners run, so a listener that re-enters this module
|
|
383
|
-
* cannot see the same edge twice.
|
|
384
|
-
*/
|
|
385
|
-
function observeAgentModel(worktreeId, cliToolId, instanceId, source, at) {
|
|
386
|
-
const key = (0, auto_yes_state_1.buildCompositeKey)(worktreeId, cliToolId, instanceId);
|
|
387
|
-
// Where the merged value came from is decided in one place
|
|
388
|
-
// (`resolveAgentModel`, Issue #2361): the hook's, unless the frame overtook
|
|
389
|
-
// it — and that is independent of which channel just wrote.
|
|
390
|
-
const { info, source: resolvedSource } = resolveAgentModel(key, cliToolId);
|
|
391
|
-
const resolved = info.model;
|
|
392
|
-
if (resolved === null || resolvedSource === null)
|
|
393
|
-
return;
|
|
394
|
-
const previous = modelBaseline.get(key);
|
|
395
|
-
const next = { model: resolved, source: resolvedSource };
|
|
396
|
-
if (previous === undefined) {
|
|
397
|
-
modelBaseline.set(key, next);
|
|
398
|
-
trimOldestEntries(modelBaseline, MAX_RECENT_EVENT_KEYS);
|
|
399
|
-
return;
|
|
400
|
-
}
|
|
401
|
-
if (isSameAgentModelName(previous.model, resolved, {
|
|
402
|
-
crossSource: previous.source !== resolvedSource,
|
|
403
|
-
})) {
|
|
404
|
-
// Same model, possibly a new spelling or a new channel: follow it so the
|
|
405
|
-
// next comparison is against the current representation.
|
|
406
|
-
modelBaseline.set(key, next);
|
|
407
|
-
return;
|
|
408
|
-
}
|
|
409
|
-
modelBaseline.set(key, next);
|
|
410
|
-
emitAgentModelChange({
|
|
411
|
-
worktreeId,
|
|
412
|
-
cliToolId,
|
|
413
|
-
instanceId: instanceId ?? cliToolId,
|
|
414
|
-
from: previous.model,
|
|
415
|
-
to: resolved,
|
|
416
|
-
source,
|
|
417
|
-
at,
|
|
418
|
-
});
|
|
419
|
-
}
|
|
420
|
-
/**
|
|
421
|
-
* How long two identical events count as one delivery.
|
|
422
|
-
*
|
|
423
|
-
* Issue #1722 injects hooks at session start, and `--settings` hooks are
|
|
424
|
-
* *concatenated* with the user's own rather than replacing them, so anyone who
|
|
425
|
-
* followed the #1549 manual setup now has two `Stop` hooks posting the same
|
|
426
|
-
* turn. `applyAgentStopEvent` is idempotent for the timestamp, but
|
|
427
|
-
* `applyTaskEvent` is not: each delivery writes its own `agent_idle` row, and a
|
|
428
|
-
* reader counting rows would see one turn as two.
|
|
429
|
-
*
|
|
430
|
-
* A turn cannot end twice in three seconds, and both deliveries carry the same
|
|
431
|
-
* `session_id`, so the window is generous relative to the real signal and tight
|
|
432
|
-
* relative to anything it could wrongly swallow.
|
|
433
|
-
*/
|
|
434
|
-
exports.AGENT_EVENT_DEDUP_WINDOW_MS = 3000;
|
|
435
|
-
/** Cap on retained dedup keys, so a long-lived server cannot grow one per turn. */
|
|
436
|
-
const MAX_RECENT_EVENT_KEYS = 512;
|
|
437
|
-
/** A zeroed tally. */
|
|
438
|
-
function emptyDropCounts() {
|
|
439
|
-
return {
|
|
440
|
-
dedupDropped: { identity: 0, timeWindow: 0 },
|
|
441
|
-
decisionEvicted: 0,
|
|
442
|
-
idsDiscarded: 0,
|
|
443
|
-
dialogTimedOut: 0,
|
|
444
|
-
decisionOverflow: 0,
|
|
445
|
-
};
|
|
446
|
-
}
|
|
447
|
-
/** The live tally for one instance, created on first use. */
|
|
448
|
-
function dropsFor(key) {
|
|
449
|
-
let counts = dropCounts.get(key);
|
|
450
|
-
if (!counts) {
|
|
451
|
-
counts = emptyDropCounts();
|
|
452
|
-
dropCounts.set(key, counts);
|
|
453
|
-
// The composite key is (worktree, tool, instance) and a long-lived server
|
|
454
|
-
// accumulates worktrees; bounded like every other map here (DR4-009).
|
|
455
|
-
trimOldestEntries(dropCounts, MAX_RECENT_EVENT_KEYS);
|
|
456
|
-
}
|
|
457
|
-
return counts;
|
|
458
|
-
}
|
|
459
|
-
/**
|
|
460
|
-
* What this instance has had dropped, or a zeroed tally when nothing has.
|
|
461
|
-
*
|
|
462
|
-
* A copy, not the live object: this is published on the hot path and a caller
|
|
463
|
-
* that could mutate it would be able to erase the evidence.
|
|
464
|
-
*/
|
|
465
|
-
function getAgentEventDropCounts(worktreeId, cliToolId, instanceId) {
|
|
466
|
-
const counts = dropCounts.get((0, auto_yes_state_1.buildCompositeKey)(worktreeId, cliToolId, instanceId));
|
|
467
|
-
if (!counts)
|
|
468
|
-
return emptyDropCounts();
|
|
469
|
-
return { ...counts, dedupDropped: { ...counts.dedupDropped } };
|
|
470
|
-
}
|
|
471
|
-
/** Epoch ms the current generation began, or null when none was opened. */
|
|
472
|
-
function currentGeneration(key) {
|
|
473
|
-
return generationStartedAt.get(key) ?? null;
|
|
474
|
-
}
|
|
475
|
-
/**
|
|
476
|
-
* The turn this instance is in, with the generation fence applied.
|
|
477
|
-
*
|
|
478
|
-
* A turn whose {@link TurnRecord.generationAt} is not the current generation was
|
|
479
|
-
* opened by a process that has been replaced. It is not deleted — the fence is
|
|
480
|
-
* cheap and deleting would lose the eviction tally — it is simply not this
|
|
481
|
-
* instance's turn any more.
|
|
482
|
-
*/
|
|
483
|
-
function fencedTurn(key) {
|
|
484
|
-
const turn = agentTurns.get(key);
|
|
485
|
-
if (!turn)
|
|
486
|
-
return null;
|
|
487
|
-
if (turn.generationAt !== currentGeneration(key))
|
|
488
|
-
return null;
|
|
489
|
-
return turn;
|
|
490
|
-
}
|
|
491
|
-
/**
|
|
492
|
-
* {@link fencedTurn} with the staleness bound applied.
|
|
493
|
-
*
|
|
494
|
-
* The bound is applied by *closing* the turn rather than by hiding it, so
|
|
495
|
-
* `capture --json` says `closedBy: 'stale'` instead of going quiet — "the agent
|
|
496
|
-
* never reported the end of this turn" and "nothing has been reported at all"
|
|
497
|
-
* are different problems and an operator has to be able to tell them apart.
|
|
498
|
-
*
|
|
499
|
-
* Writes on read, which this module already does for corroboration, and is
|
|
500
|
-
* idempotent: the close is stamped from the event's own clock, so it does not
|
|
501
|
-
* move with the reader's `now`.
|
|
502
|
-
*/
|
|
503
|
-
function effectiveTurn(key, now) {
|
|
504
|
-
const turn = fencedTurn(key);
|
|
505
|
-
if (turn === null)
|
|
506
|
-
return null;
|
|
507
|
-
if (turn.closedAt === null && now - turn.displayEvent.at >= provisional_turn_1.TURN_STALE_AFTER_MS) {
|
|
508
|
-
turn.closedAt = turn.displayEvent.at + provisional_turn_1.TURN_STALE_AFTER_MS;
|
|
509
|
-
turn.closedBy = 'stale';
|
|
510
|
-
}
|
|
511
|
-
return turn;
|
|
512
|
-
}
|
|
513
|
-
/** Next {@link TurnRecord.turnId}. See {@link turnSequence}. */
|
|
514
|
-
function nextTurnId(at) {
|
|
515
|
-
turnSequence.value += 1;
|
|
516
|
-
return `turn-${at}-${turnSequence.value}`;
|
|
517
|
-
}
|
|
518
|
-
/**
|
|
519
|
-
* Drop the decisions this turn is holding, counting them.
|
|
520
|
-
*
|
|
521
|
-
* The generation path is the one §4 D3 決定 2 names: a process that has been
|
|
522
|
-
* replaced cannot have its approvals answered, and leaving them behind would
|
|
523
|
-
* publish `waiting` for a pane whose dialog went away with the process that
|
|
524
|
-
* drew it.
|
|
525
|
-
*/
|
|
526
|
-
function evictDecisions(key, turn) {
|
|
527
|
-
if (turn.pendingDecisions.length === 0)
|
|
528
|
-
return;
|
|
529
|
-
dropsFor(key).decisionEvicted += turn.pendingDecisions.length;
|
|
530
|
-
turn.pendingDecisions = [];
|
|
531
|
-
}
|
|
532
|
-
/**
|
|
533
|
-
* The decisions still describing something, dropping the ones that are not.
|
|
534
|
-
*
|
|
535
|
-
* The retention bound of {@link DIALOG_PENDING_MAX_MS}, applied on read and
|
|
536
|
-
* counted as `dialogTimedOut` so the release is visible. Kept here rather than
|
|
537
|
-
* in a timer for the reason the rest of this module has no timers: the state
|
|
538
|
-
* describes a live tmux session, and a reader is the only thing that ever needs
|
|
539
|
-
* the answer.
|
|
540
|
-
*/
|
|
541
|
-
function livePendingDecisions(key, turn, now) {
|
|
542
|
-
const live = turn.pendingDecisions.filter((decision) => (0, provisional_turn_1.isDecisionLive)(decision, now));
|
|
543
|
-
const dropped = turn.pendingDecisions.length - live.length;
|
|
544
|
-
if (dropped > 0) {
|
|
545
|
-
dropsFor(key).dialogTimedOut += dropped;
|
|
546
|
-
turn.pendingDecisions = live;
|
|
547
|
-
}
|
|
548
|
-
return live;
|
|
549
|
-
}
|
|
550
|
-
/**
|
|
551
|
-
* The approvals this instance is blocked on right now (Issue #1930).
|
|
552
|
-
*
|
|
553
|
-
* Published as `structuredEvents.pendingDecisions`; `#1932` is what teaches
|
|
554
|
-
* `commandmate respond` to name one of these ids.
|
|
555
|
-
*
|
|
556
|
-
* @param now - Epoch ms; defaults to now
|
|
557
|
-
*/
|
|
558
|
-
function getPendingDecisions(worktreeId, cliToolId, instanceId, now = Date.now()) {
|
|
559
|
-
const key = (0, auto_yes_state_1.buildCompositeKey)(worktreeId, cliToolId, instanceId);
|
|
560
|
-
const turn = effectiveTurn(key, now);
|
|
561
|
-
if (turn === null)
|
|
562
|
-
return [];
|
|
563
|
-
return livePendingDecisions(key, turn, now);
|
|
564
|
-
}
|
|
565
|
-
/**
|
|
566
|
-
* The turn record for one instance, fenced and aged, or null.
|
|
567
|
-
*
|
|
568
|
-
* The seam `current-output-builder` publishes from and the suites drive. The
|
|
569
|
-
* returned object is the live record; nothing outside this module writes to it.
|
|
570
|
-
*
|
|
571
|
-
* @param now - Epoch ms; defaults to now
|
|
572
|
-
*/
|
|
573
|
-
function getAgentTurn(worktreeId, cliToolId, instanceId, now = Date.now()) {
|
|
574
|
-
return effectiveTurn((0, auto_yes_state_1.buildCompositeKey)(worktreeId, cliToolId, instanceId), now);
|
|
575
|
-
}
|
|
576
|
-
/** {@link getAgentTurn} projected onto the four published fields. */
|
|
577
|
-
function getPublishedAgentTurn(worktreeId, cliToolId, instanceId, now = Date.now()) {
|
|
578
|
-
return (0, provisional_turn_1.derivePublishedTurn)(getAgentTurn(worktreeId, cliToolId, instanceId, now));
|
|
579
|
-
}
|
|
580
|
-
/**
|
|
581
|
-
* Close the open turn on evidence that did not come from the agent (Issue #1930).
|
|
582
|
-
*
|
|
583
|
-
* The one seam for the two {@link TurnCloseReason} values nothing in the event
|
|
584
|
-
* stream can produce:
|
|
585
|
-
*
|
|
586
|
-
* - `scraper_evidence` — see {@link observeScraperCompletionEvidence}, which is
|
|
587
|
-
* the caller in this tree.
|
|
588
|
-
* - `resync_idle` — a source whose `capabilities.resync` lets it be re-read
|
|
589
|
-
* answering "not busy" after a dropped transport. The reconnect loop that
|
|
590
|
-
* would call it lives in `sources/opencode/subscription`, which is #1931's
|
|
591
|
-
* file and deliberately untouched here; the value is in the vocabulary and
|
|
592
|
-
* the seam accepts it, so wiring it is a call site rather than a new state.
|
|
593
|
-
*
|
|
594
|
-
* A no-op when no turn is open, which is the ordinary case: neither of these is
|
|
595
|
-
* a statement about a session that already reported it finished.
|
|
596
|
-
*
|
|
597
|
-
* @param at - Epoch ms; defaults to now
|
|
598
|
-
* @returns Whether a turn was closed
|
|
599
|
-
*/
|
|
600
|
-
function closeAgentTurn(worktreeId, cliToolId, instanceId, closedBy, at = Date.now()) {
|
|
601
|
-
const key = (0, auto_yes_state_1.buildCompositeKey)(worktreeId, cliToolId, instanceId);
|
|
602
|
-
const turn = fencedTurn(key);
|
|
603
|
-
if (turn === null || turn.closedAt !== null)
|
|
604
|
-
return false;
|
|
605
|
-
turn.closedAt = at;
|
|
606
|
-
turn.closedBy = closedBy;
|
|
607
|
-
return true;
|
|
608
|
-
}
|
|
609
|
-
/**
|
|
610
|
-
* Feed one poll's reading of the pane into the turn's completion counter
|
|
611
|
-
* (Issue #1930).
|
|
612
|
-
*
|
|
613
|
-
* `completed` is the scraper's own verdict — `ready` with positive evidence —
|
|
614
|
-
* NOT the merged one, which would be circular: the merge is what the structured
|
|
615
|
-
* layer's `running` overrides, so reading it back would only ever confirm this
|
|
616
|
-
* layer's own answer.
|
|
617
|
-
*
|
|
618
|
-
* See {@link SCRAPER_COMPLETION_POLLS} for why three, and for why closing here
|
|
619
|
-
* does not complete a `commandmate wait`.
|
|
620
|
-
*
|
|
621
|
-
* @param at - Epoch ms; defaults to now
|
|
622
|
-
* @returns Whether this poll closed the turn
|
|
623
|
-
*/
|
|
624
|
-
function observeScraperCompletionEvidence(worktreeId, cliToolId, instanceId, completed, at = Date.now()) {
|
|
625
|
-
const key = (0, auto_yes_state_1.buildCompositeKey)(worktreeId, cliToolId, instanceId);
|
|
626
|
-
const turn = fencedTurn(key);
|
|
627
|
-
if (turn === null || turn.closedAt !== null)
|
|
628
|
-
return false;
|
|
629
|
-
if (!completed) {
|
|
630
|
-
turn.scraperCompletionPolls = 0;
|
|
631
|
-
return false;
|
|
632
|
-
}
|
|
633
|
-
turn.scraperCompletionPolls += 1;
|
|
634
|
-
if (turn.scraperCompletionPolls < provisional_turn_1.SCRAPER_COMPLETION_POLLS)
|
|
635
|
-
return false;
|
|
636
|
-
turn.closedAt = at;
|
|
637
|
-
turn.closedBy = 'scraper_evidence';
|
|
638
|
-
return true;
|
|
639
|
-
}
|
|
110
|
+
const lastAgentEvent = (0, global_state_1.getOrInitGlobal)('__agentEventLast', () => new Map());
|
|
640
111
|
/**
|
|
641
112
|
* Record that `instanceId` reported it stopped.
|
|
642
113
|
*
|
|
@@ -714,7 +185,7 @@ function isLateSessionStart(worktreeId, cliToolId, instanceId, record, options)
|
|
|
714
185
|
return false;
|
|
715
186
|
if (options.sessionStartMayArriveLate !== true)
|
|
716
187
|
return false;
|
|
717
|
-
const openTurn = getStructuredSessionState(worktreeId, cliToolId, instanceId, record.at);
|
|
188
|
+
const openTurn = (0, agent_event_structured_state_1.getStructuredSessionState)(worktreeId, cliToolId, instanceId, record.at);
|
|
718
189
|
if (openTurn === null || !OPEN_TURN_STATUSES.includes(openTurn.status))
|
|
719
190
|
return false;
|
|
720
191
|
const openSessionId = lastAgentEvent.get((0, auto_yes_state_1.buildCompositeKey)(worktreeId, cliToolId, instanceId))?.sessionId ?? null;
|
|
@@ -742,8 +213,8 @@ function recordAgentEvent(worktreeId, cliToolId, instanceId, record, options = {
|
|
|
742
213
|
// model would otherwise be the single case where the model is extracted and
|
|
743
214
|
// then dropped on the floor. copilot reports none today (#1783), which is
|
|
744
215
|
// exactly why this has to be decided here rather than left to be noticed.
|
|
745
|
-
latchAgentModel(key, record);
|
|
746
|
-
observeAgentModel(worktreeId, cliToolId, instanceId, 'hook', record.at);
|
|
216
|
+
(0, agent_event_model_1.latchAgentModel)(key, record);
|
|
217
|
+
(0, agent_event_model_1.observeAgentModel)(worktreeId, cliToolId, instanceId, 'hook', record.at);
|
|
747
218
|
return { recorded: false, skipped: 'late-session-start' };
|
|
748
219
|
}
|
|
749
220
|
lastAgentEvent.set(key, record);
|
|
@@ -753,567 +224,35 @@ function recordAgentEvent(worktreeId, cliToolId, instanceId, record, options = {
|
|
|
753
224
|
// latch so the comparison in `observeAgentModel` has nothing to compare
|
|
754
225
|
// against. `/clear` (session_end + session_start on the same process, same
|
|
755
226
|
// model) passes through here unchanged: same name, no edge either way.
|
|
756
|
-
modelBaseline.delete(key);
|
|
227
|
+
agent_event_model_1.modelBaseline.delete(key);
|
|
757
228
|
}
|
|
758
|
-
latchAgentModel(key, record);
|
|
759
|
-
observeAgentModel(worktreeId, cliToolId, instanceId, 'hook', record.at);
|
|
760
|
-
applyAskUserQuestionTransition(key, record);
|
|
229
|
+
(0, agent_event_model_1.latchAgentModel)(key, record);
|
|
230
|
+
(0, agent_event_model_1.observeAgentModel)(worktreeId, cliToolId, instanceId, 'hook', record.at);
|
|
231
|
+
(0, agent_event_ask_user_question_1.applyAskUserQuestionTransition)(key, record);
|
|
761
232
|
if (record.event === 'session_start') {
|
|
762
233
|
// The agent restarting inside a pane CommandMate never touched — `claude`
|
|
763
234
|
// relaunched by hand, or a `/clear` (which emits SessionEnd then
|
|
764
235
|
// SessionStart on a live session) — is a new generation just as much as a
|
|
765
236
|
// new tmux session is. Recorded from the event's own timestamp, so the
|
|
766
237
|
// event that opens a generation is never stale against it.
|
|
767
|
-
generationStartedAt.set(key, record.at);
|
|
238
|
+
agent_event_turn_1.generationStartedAt.set(key, record.at);
|
|
239
|
+
// Issue #3397: and Auto-Yes's Enter belonged to the previous process's
|
|
240
|
+
// screen. Only here — past `isLateSessionStart` above, and past the
|
|
241
|
+
// duplicate window the intake applies before calling this — so a late or
|
|
242
|
+
// repeated `SessionStart` never expires the record of the live process.
|
|
243
|
+
(0, auto_yes_enter_fallback_state_1.beginEnterFallbackSession)(worktreeId, cliToolId, instanceId);
|
|
768
244
|
// Issue #1930: and the turn the previous process was in ends with it. Done
|
|
769
245
|
// here rather than inside the transition below so the two paths into a new
|
|
770
246
|
// generation — this one and `beginAgentEventGeneration` — close the turn in
|
|
771
247
|
// exactly one place.
|
|
772
|
-
fenceTurnForNewGeneration(key, record.at);
|
|
248
|
+
(0, agent_event_turn_1.fenceTurnForNewGeneration)(key, record.at);
|
|
773
249
|
}
|
|
774
250
|
// Issue #1930: after the generation is settled, so a turn opened by this
|
|
775
251
|
// event belongs to the generation the event itself opened.
|
|
776
|
-
applyTurnTransition(key, record);
|
|
777
|
-
applyAwaitingInstructionTransition(key, record);
|
|
252
|
+
(0, agent_event_turn_1.applyTurnTransition)(key, record);
|
|
253
|
+
(0, agent_event_awaiting_instruction_1.applyAwaitingInstructionTransition)(key, record);
|
|
778
254
|
return { recorded: true };
|
|
779
255
|
}
|
|
780
|
-
/**
|
|
781
|
-
* End the open turn because the process that owned it has been replaced
|
|
782
|
-
* (Issue #1930).
|
|
783
|
-
*
|
|
784
|
-
* The turn is closed and re-stamped into the new generation rather than
|
|
785
|
-
* deleted, which is the difference between `capture --json` reporting
|
|
786
|
-
* `closedBy: 'generation'` and reporting nothing at all. Its approvals go: they
|
|
787
|
-
* were raised by a process that no longer exists, and answering one would
|
|
788
|
-
* deliver a verdict into a slot nobody is holding.
|
|
789
|
-
*/
|
|
790
|
-
function fenceTurnForNewGeneration(key, at) {
|
|
791
|
-
const turn = agentTurns.get(key);
|
|
792
|
-
if (!turn)
|
|
793
|
-
return;
|
|
794
|
-
evictDecisions(key, turn);
|
|
795
|
-
// A turn that had ALREADY ended is left where it is, which means the fence in
|
|
796
|
-
// `fencedTurn` hides it from here on. That asymmetry is deliberate: the
|
|
797
|
-
// previous process's `Stop` is not this process's, and re-stamping it into the
|
|
798
|
-
// new generation would publish `ready` — "the agent finished" — for a session
|
|
799
|
-
// nobody has typed into yet. Before #1930 the same protection came from
|
|
800
|
-
// comparing the event's timestamp against the generation.
|
|
801
|
-
if (turn.closedAt !== null)
|
|
802
|
-
return;
|
|
803
|
-
// An OPEN turn is closed and carried across, so `capture --json` can say
|
|
804
|
-
// `closedBy: 'generation'` rather than going quiet. `getStructuredSessionState`
|
|
805
|
-
// reads that reason as "nothing is known", so it publishes no verdict either
|
|
806
|
-
// way; what it buys is an operator being able to tell "the session was
|
|
807
|
-
// restarted under this turn" from "nothing was ever reported".
|
|
808
|
-
turn.closedAt = at;
|
|
809
|
-
turn.closedBy = 'generation';
|
|
810
|
-
turn.generationAt = at;
|
|
811
|
-
}
|
|
812
|
-
/**
|
|
813
|
-
* Issue #1783: latch, never clear. An event without a model is the ordinary
|
|
814
|
-
* case (Claude sends one on `SessionStart` and, since #2363, on a model
|
|
815
|
-
* switch), and reading it as "the model is now unknown" would blank the display
|
|
816
|
-
* on the very next event.
|
|
817
|
-
*/
|
|
818
|
-
function latchAgentModel(key, record) {
|
|
819
|
-
if (typeof record.model === 'string' && record.model !== '') {
|
|
820
|
-
lastAgentModel.set(key, record.model.slice(0, agent_event_types_1.MAX_EVENT_DETAIL_LENGTH));
|
|
821
|
-
// Issue #2361: stamped on every model-bearing report, not only on a change
|
|
822
|
-
// of value — a hook that names the same model again is the agent
|
|
823
|
-
// re-affirming it, and a frame has to be newer than THAT to overtake.
|
|
824
|
-
lastAgentModelAt.set(key, record.at);
|
|
825
|
-
}
|
|
826
|
-
}
|
|
827
|
-
/**
|
|
828
|
-
* The third state: "this turn is over and nobody has told me what to do next"
|
|
829
|
-
* (Issue #1786).
|
|
830
|
-
*
|
|
831
|
-
* | event | effect on awaiting_instruction |
|
|
832
|
-
* |-----------------------------------|--------------------------------|
|
|
833
|
-
* | `notification(idle_prompt)` | **set** |
|
|
834
|
-
* | `user_prompt_submit` | release |
|
|
835
|
-
* | `session_start` / `session_end` | release |
|
|
836
|
-
* | everything else | unchanged |
|
|
837
|
-
*
|
|
838
|
-
* It is a boolean beside the status rather than a fifth `SessionStatus`, because
|
|
839
|
-
* `idle_prompt` already maps to `ready` (`agentEventToSessionStatus`) and
|
|
840
|
-
* `ready` means "you can send a message" everywhere in this codebase — the
|
|
841
|
-
* sidebar, `deriveCliStatus`, `getNextAction`, `commandmate wait`. Widening that
|
|
842
|
-
* vocabulary to carry "and it is *asking* you to" would have every consumer of
|
|
843
|
-
* the four values re-decide what they mean; the boolean asks nothing of anyone
|
|
844
|
-
* who does not want it.
|
|
845
|
-
*
|
|
846
|
-
* `stop` deliberately does NOT set it. `Stop` fires when the turn ends, which is
|
|
847
|
-
* most of the time the agent has simply finished a step of its own plan;
|
|
848
|
-
* `Notification(idle_prompt)` is Claude's separate, later "waiting for your
|
|
849
|
-
* input" signal, and using the turn boundary instead would mark every
|
|
850
|
-
* intermediate stop as a request for instructions.
|
|
851
|
-
*
|
|
852
|
-
* `pre_tool_use` / `post_tool_use` / `notification(permission_prompt)` leave it
|
|
853
|
-
* alone, and that is not a gap: a tool call or a dialog can only happen inside a
|
|
854
|
-
* turn, and the turn's own `user_prompt_submit` has already released it. Adding
|
|
855
|
-
* them would only paper over a `UserPromptSubmit` that never arrived — and an
|
|
856
|
-
* operator whose `UserPromptSubmit` hook is missing has no `Notification` hook
|
|
857
|
-
* either, so there would be nothing to release.
|
|
858
|
-
*
|
|
859
|
-
* There is deliberately no age bound here, unlike every other state in this
|
|
860
|
-
* module. Those bound the damage of an event that may never arrive (a lost
|
|
861
|
-
* `Stop`, a dialog answered with no event to say so). This one is released by
|
|
862
|
-
* events that cannot be missed while the fact is still true: typing into the
|
|
863
|
-
* composer — from the app or straight into the tmux pane — raises
|
|
864
|
-
* `UserPromptSubmit`, and ending the session raises `SessionEnd`. An agent that
|
|
865
|
-
* has been idle for six hours genuinely is still awaiting instructions, and
|
|
866
|
-
* expiring the flag would erase the notification #1790 exists to send. The
|
|
867
|
-
* generation fence in {@link isAwaitingInstruction} still applies, so the flag
|
|
868
|
-
* never survives the process that reported it.
|
|
869
|
-
*/
|
|
870
|
-
function applyAwaitingInstructionTransition(key, record) {
|
|
871
|
-
switch (record.event) {
|
|
872
|
-
case 'notification':
|
|
873
|
-
if (record.detail === 'idle_prompt') {
|
|
874
|
-
awaitingInstruction.set(key, { at: record.at, message: record.message ?? null });
|
|
875
|
-
}
|
|
876
|
-
return;
|
|
877
|
-
case 'user_prompt_submit':
|
|
878
|
-
case 'session_start':
|
|
879
|
-
case 'session_end':
|
|
880
|
-
awaitingInstruction.delete(key);
|
|
881
|
-
return;
|
|
882
|
-
case 'stop':
|
|
883
|
-
case 'pre_tool_use':
|
|
884
|
-
case 'post_tool_use':
|
|
885
|
-
return;
|
|
886
|
-
default:
|
|
887
|
-
// exhaustive check: a new AgentEventType must decide its transition here
|
|
888
|
-
record.event;
|
|
889
|
-
return;
|
|
890
|
-
}
|
|
891
|
-
}
|
|
892
|
-
/**
|
|
893
|
-
* The agent's own "waiting for your input", or null (Issue #1786).
|
|
894
|
-
*
|
|
895
|
-
* Fenced by generation like the rest of this module: an `idle_prompt` from the
|
|
896
|
-
* Claude process that used to live in this pane is not this one's.
|
|
897
|
-
*/
|
|
898
|
-
function getAwaitingInstruction(worktreeId, cliToolId, instanceId) {
|
|
899
|
-
const key = (0, auto_yes_state_1.buildCompositeKey)(worktreeId, cliToolId, instanceId);
|
|
900
|
-
const record = awaitingInstruction.get(key);
|
|
901
|
-
if (!record)
|
|
902
|
-
return null;
|
|
903
|
-
const generation = generationStartedAt.get(key);
|
|
904
|
-
if (generation !== undefined && record.at < generation)
|
|
905
|
-
return null;
|
|
906
|
-
return record;
|
|
907
|
-
}
|
|
908
|
-
/** {@link getAwaitingInstruction} as the boolean the list API publishes. */
|
|
909
|
-
function isAwaitingInstruction(worktreeId, cliToolId, instanceId) {
|
|
910
|
-
return getAwaitingInstruction(worktreeId, cliToolId, instanceId) !== null;
|
|
911
|
-
}
|
|
912
|
-
/**
|
|
913
|
-
* The turn transition: what one delivery does to {@link TurnRecord}
|
|
914
|
-
* (Issue #1930, §4 D3 決定 2).
|
|
915
|
-
*
|
|
916
|
-
* | event | turn | dialog | display |
|
|
917
|
-
* |------------------------------------|-------------------------|----------|---------|
|
|
918
|
-
* | `user_prompt_submit` | **opens a new one** | release | yes |
|
|
919
|
-
* | `pre_tool_use` | continues, else opens | unchanged| yes |
|
|
920
|
-
* | `post_tool_use` | continues, else opens | release | yes |
|
|
921
|
-
* | `stop` (this session) | **closes** `stop` | release | yes |
|
|
922
|
-
* | `stop` (another session) | unchanged | unchanged| no |
|
|
923
|
-
* | `session_end` | **closes** `session_end`| release | no |
|
|
924
|
-
* | `session_start` | new generation (above) | evicted | no |
|
|
925
|
-
* | `notification(permission_prompt)` | unchanged | **open** | only to bootstrap |
|
|
926
|
-
* | `notification(permission_replied)` | unchanged | release | no |
|
|
927
|
-
* | `notification(idle_prompt)` | unchanged | release | yes |
|
|
928
|
-
* | `notification(anything else)` | unchanged | unchanged| no |
|
|
929
|
-
*
|
|
930
|
-
* Four rows are the whole point of the Issue, and each of them was a defect
|
|
931
|
-
* under "the newest event is the verdict":
|
|
932
|
-
*
|
|
933
|
-
* - **`user_prompt_submit` opens a new turn, the tool-call events continue the
|
|
934
|
-
* open one.** That is what makes {@link TurnRecord.turnId} an identity: #1926
|
|
935
|
-
* re-stamped it on every `pre_tool_use`, so a consumer reading a changed id
|
|
936
|
-
* as "a new turn began" false-positived several times inside one turn. `wait`
|
|
937
|
-
* reads it that way now.
|
|
938
|
-
* - **A `stop` naming another session changes nothing.** opencode publishes
|
|
939
|
-
* `session.idle` for every session its server holds, other processes'
|
|
940
|
-
* included (#1758 §5.6).
|
|
941
|
-
* - **`session_start` / `session_end` / an unknown notification /
|
|
942
|
-
* `permission_replied` do not become the displayed event.** An event carrying
|
|
943
|
-
* no verdict must not erase one. #1903 fixed the single measured instance
|
|
944
|
-
* (copilot's late `SessionStart`) by holding the delivery; the rule is
|
|
945
|
-
* general here, and the capability it reads is still the only reason a
|
|
946
|
-
* `session_start` is held rather than recorded.
|
|
947
|
-
* - **`notification(permission_prompt)` does not become the displayed event
|
|
948
|
-
* when a turn already exists.** The dialog is a fact about the *pane*, and
|
|
949
|
-
* it is published by the decision ledger below. Letting it overwrite the
|
|
950
|
-
* display is how the pre-#1898 `waiting` outlived the approval that caused
|
|
951
|
-
* it: released, the record still said `waiting`, because the only thing it
|
|
952
|
-
* could read was the event that opened the dialog.
|
|
953
|
-
*
|
|
954
|
-
* `idle_prompt` is the one row that publishes `ready` **without** closing the
|
|
955
|
-
* turn, and that is a measurement rather than an oversight: #1839 caught Claude
|
|
956
|
-
* emitting it 62 s into a turn that ran nothing, so it cannot be a turn
|
|
957
|
-
* boundary. `wait`'s gate stays armed through it; what changes is only what the
|
|
958
|
-
* pane displays.
|
|
959
|
-
*/
|
|
960
|
-
function applyTurnTransition(key, record) {
|
|
961
|
-
const generation = currentGeneration(key);
|
|
962
|
-
// An event stamped before the current generation was produced by a process
|
|
963
|
-
// that has been replaced. Refusing it here is what makes re-recording a stale
|
|
964
|
-
// `user_prompt_submit` at its original timestamp a no-op.
|
|
965
|
-
if (generation !== null && record.at < generation)
|
|
966
|
-
return;
|
|
967
|
-
switch (record.event) {
|
|
968
|
-
case 'user_prompt_submit':
|
|
969
|
-
openTurn(key, record, generation, { continueOpen: false });
|
|
970
|
-
releaseAllDecisions(key);
|
|
971
|
-
return;
|
|
972
|
-
case 'post_tool_use':
|
|
973
|
-
openTurn(key, record, generation, { continueOpen: true });
|
|
974
|
-
// The tool call the dialog was gating has finished, so somebody answered
|
|
975
|
-
// it (Issue #1726). This is the release #1725 could not have: it had no
|
|
976
|
-
// event meaning "the human answered", only `Stop` meaning "the turn
|
|
977
|
-
// ended", which can be minutes later.
|
|
978
|
-
releaseAllDecisions(key);
|
|
979
|
-
return;
|
|
980
|
-
case 'pre_tool_use':
|
|
981
|
-
// Deliberately leaves the dialog alone (Issue #1726). It is the
|
|
982
|
-
// `AskUserQuestion` invocation, and a picker being *about to be drawn* is
|
|
983
|
-
// not a fact this state can carry.
|
|
984
|
-
openTurn(key, record, generation, { continueOpen: true });
|
|
985
|
-
return;
|
|
986
|
-
case 'stop':
|
|
987
|
-
applyStopToTurn(key, record, generation);
|
|
988
|
-
return;
|
|
989
|
-
case 'session_end':
|
|
990
|
-
applySessionEndToTurn(key, record);
|
|
991
|
-
releaseAllDecisions(key);
|
|
992
|
-
return;
|
|
993
|
-
case 'session_start':
|
|
994
|
-
// The generation was opened by `recordAgentEvent` before this ran, and
|
|
995
|
-
// `fenceTurnForNewGeneration` closed the turn with it. Nothing further:
|
|
996
|
-
// the frame carries no verdict, so it is not the displayed event either.
|
|
997
|
-
return;
|
|
998
|
-
case 'notification':
|
|
999
|
-
applyNotificationToTurn(key, record, generation);
|
|
1000
|
-
return;
|
|
1001
|
-
default:
|
|
1002
|
-
// exhaustive check: a new AgentEventType must decide its transition here
|
|
1003
|
-
record.event;
|
|
1004
|
-
return;
|
|
1005
|
-
}
|
|
1006
|
-
}
|
|
1007
|
-
/** The three fields the turn keeps from a record. */
|
|
1008
|
-
function displayOf(record) {
|
|
1009
|
-
return { event: record.event, at: record.at, detail: record.detail };
|
|
1010
|
-
}
|
|
1011
|
-
/**
|
|
1012
|
-
* Open a turn for this event, or move the display onto an open one.
|
|
1013
|
-
*
|
|
1014
|
-
* @param continueOpen - Whether an already-open turn of the same session is
|
|
1015
|
-
* this event's turn. False for `user_prompt_submit`, which IS a new turn.
|
|
1016
|
-
*/
|
|
1017
|
-
function openTurn(key, record, generation, { continueOpen }) {
|
|
1018
|
-
const turn = fencedTurn(key);
|
|
1019
|
-
const openHere = turn !== null &&
|
|
1020
|
-
turn.closedAt === null &&
|
|
1021
|
-
turn.openedAt !== null &&
|
|
1022
|
-
(0, provisional_turn_1.closesTurn)(turn, record.sessionId);
|
|
1023
|
-
if (continueOpen && openHere) {
|
|
1024
|
-
turn.displayEvent = displayOf(record);
|
|
1025
|
-
// A hand-configured relay posts no session id, so a turn can learn one
|
|
1026
|
-
// partway through. Learning it is what lets a later `stop` be matched.
|
|
1027
|
-
turn.sessionId ??= record.sessionId;
|
|
1028
|
-
turn.scraperCompletionPolls = 0;
|
|
1029
|
-
return;
|
|
1030
|
-
}
|
|
1031
|
-
// A dialog-only record (openedAt null) is not a turn; its decisions belong to
|
|
1032
|
-
// the turn that is opening now, because that is when they were raised.
|
|
1033
|
-
const carried = turn !== null && turn.openedAt === null && turn.closedAt === null
|
|
1034
|
-
? turn.pendingDecisions
|
|
1035
|
-
: [];
|
|
1036
|
-
agentTurns.set(key, {
|
|
1037
|
-
turnId: nextTurnId(record.at),
|
|
1038
|
-
sessionId: record.sessionId,
|
|
1039
|
-
openedAt: record.at,
|
|
1040
|
-
closedAt: null,
|
|
1041
|
-
closedBy: null,
|
|
1042
|
-
generationAt: generation,
|
|
1043
|
-
displayEvent: displayOf(record),
|
|
1044
|
-
pendingDecisions: carried,
|
|
1045
|
-
scraperCompletionPolls: 0,
|
|
1046
|
-
});
|
|
1047
|
-
boundTurnMap();
|
|
1048
|
-
}
|
|
1049
|
-
/**
|
|
1050
|
-
* Apply a `stop` — the only event that is the agent saying its turn is over.
|
|
1051
|
-
*
|
|
1052
|
-
* A `stop` for another session leaves everything alone; see {@link closesTurn}.
|
|
1053
|
-
* A `stop` with no turn open still records one, because "this instance last
|
|
1054
|
-
* reported it stopped at T" is the fact `ready` is published from, and the
|
|
1055
|
-
* opening it never saw is published as null rather than guessed.
|
|
1056
|
-
*/
|
|
1057
|
-
function applyStopToTurn(key, record, generation) {
|
|
1058
|
-
const turn = fencedTurn(key);
|
|
1059
|
-
if (turn !== null && turn.closedAt === null && turn.openedAt !== null) {
|
|
1060
|
-
if (!(0, provisional_turn_1.closesTurn)(turn, record.sessionId))
|
|
1061
|
-
return;
|
|
1062
|
-
turn.closedAt = record.at;
|
|
1063
|
-
turn.closedBy = 'stop';
|
|
1064
|
-
turn.displayEvent = displayOf(record);
|
|
1065
|
-
releaseAllDecisions(key);
|
|
1066
|
-
return;
|
|
1067
|
-
}
|
|
1068
|
-
agentTurns.set(key, {
|
|
1069
|
-
turnId: nextTurnId(record.at),
|
|
1070
|
-
sessionId: record.sessionId,
|
|
1071
|
-
openedAt: null,
|
|
1072
|
-
closedAt: record.at,
|
|
1073
|
-
closedBy: 'stop',
|
|
1074
|
-
generationAt: generation,
|
|
1075
|
-
displayEvent: displayOf(record),
|
|
1076
|
-
pendingDecisions: [],
|
|
1077
|
-
scraperCompletionPolls: 0,
|
|
1078
|
-
});
|
|
1079
|
-
boundTurnMap();
|
|
1080
|
-
}
|
|
1081
|
-
/**
|
|
1082
|
-
* `session_end`: the agent session this instance was talking to is gone.
|
|
1083
|
-
*
|
|
1084
|
-
* **Overwrites an existing close reason**, which is the one place in this
|
|
1085
|
-
* module where a later event rewrites an earlier verdict, and #1723's contract
|
|
1086
|
-
* is why. `/clear` emits `SessionEnd(reason=clear)` on a session that is alive
|
|
1087
|
-
* and about to keep going, and the turn before it may perfectly well have ended
|
|
1088
|
-
* with a `Stop`. Leaving that `Stop` standing would keep publishing "the agent
|
|
1089
|
-
* finished" about a conversation that no longer exists — the integration pin is
|
|
1090
|
-
* `current-output-structured-status-1723`, which asserts the session goes back
|
|
1091
|
-
* to the scraper the moment `SessionEnd` lands.
|
|
1092
|
-
*
|
|
1093
|
-
* `closedBy: 'session_end'` publishes no verdict of its own (see
|
|
1094
|
-
* {@link getStructuredSessionState}), so this is a retirement rather than a
|
|
1095
|
-
* different answer.
|
|
1096
|
-
*/
|
|
1097
|
-
function applySessionEndToTurn(key, record) {
|
|
1098
|
-
const turn = fencedTurn(key);
|
|
1099
|
-
if (turn === null)
|
|
1100
|
-
return;
|
|
1101
|
-
if (!(0, provisional_turn_1.closesTurn)(turn, record.sessionId))
|
|
1102
|
-
return;
|
|
1103
|
-
turn.closedAt = record.at;
|
|
1104
|
-
turn.closedBy = 'session_end';
|
|
1105
|
-
}
|
|
1106
|
-
/**
|
|
1107
|
-
* The notification rows of the transition table.
|
|
1108
|
-
*
|
|
1109
|
-
* Matched on `notification_type` ({@link AgentEventRecord.detail}), never on the
|
|
1110
|
-
* human-facing `message` (D3): the observed messages are English prose the agent
|
|
1111
|
-
* is free to reword.
|
|
1112
|
-
*/
|
|
1113
|
-
function applyNotificationToTurn(key, record, generation) {
|
|
1114
|
-
if (record.detail === 'permission_prompt') {
|
|
1115
|
-
if (record.promptSettled === true) {
|
|
1116
|
-
// Issue #1898: adjudicated before it was recorded, and the verdict
|
|
1117
|
-
// reached the agent. There is no dialog and there never was one for a
|
|
1118
|
-
// human to answer — opening a record for it published `waiting` for the
|
|
1119
|
-
// whole of the tool call that followed (measured at eight seconds).
|
|
1120
|
-
releaseSettledDecision(key, record);
|
|
1121
|
-
return;
|
|
1122
|
-
}
|
|
1123
|
-
openDecision(key, generation, {
|
|
1124
|
-
source: 'notification',
|
|
1125
|
-
at: record.at,
|
|
1126
|
-
message: record.message ?? null,
|
|
1127
|
-
// Issue #2031. Was a hard `null`, which is what left an opencode approval
|
|
1128
|
-
// reaching the browser with no statement of what it was for: the tool
|
|
1129
|
-
// name is not in `permission.asked` and has to be carried on the record
|
|
1130
|
-
// by whoever correlated it. Absent still means "this source had nothing
|
|
1131
|
-
// to say", and `openDecision` leaves an earlier name in place for that.
|
|
1132
|
-
toolName: record.toolName ?? null,
|
|
1133
|
-
patterns: record.decisionPatterns ?? null,
|
|
1134
|
-
decisionId: record.decisionId ?? null,
|
|
1135
|
-
bootstrapDisplay: displayOf(record),
|
|
1136
|
-
});
|
|
1137
|
-
return;
|
|
1138
|
-
}
|
|
1139
|
-
if (record.detail === agent_event_types_1.PERMISSION_REPLIED_DETAIL) {
|
|
1140
|
-
// Issue #1898. The agent's own statement that the dialog is gone — whoever
|
|
1141
|
-
// answered it. Not a word this build reads a status from, so it decides
|
|
1142
|
-
// nothing; all it does is retire the record.
|
|
1143
|
-
if (record.promptSettled === true)
|
|
1144
|
-
releaseSettledDecision(key, record);
|
|
1145
|
-
return;
|
|
1146
|
-
}
|
|
1147
|
-
if (record.detail === 'idle_prompt') {
|
|
1148
|
-
// The agent reporting it is sitting at the composer waiting for input is the
|
|
1149
|
-
// agent saying nothing is in front of that composer.
|
|
1150
|
-
releaseAllDecisions(key);
|
|
1151
|
-
const turn = fencedTurn(key);
|
|
1152
|
-
if (turn !== null && turn.closedAt === null) {
|
|
1153
|
-
turn.displayEvent = displayOf(record);
|
|
1154
|
-
return;
|
|
1155
|
-
}
|
|
1156
|
-
if (turn !== null)
|
|
1157
|
-
return;
|
|
1158
|
-
// Nothing to attach it to — the first thing this instance ever said, or the
|
|
1159
|
-
// first since a generation. It still carries a verdict (`ready`), and #1723
|
|
1160
|
-
// publishes it, so a display-only record is opened to hold it. `openedAt`
|
|
1161
|
-
// stays null: an agent sitting at its composer is not in a turn, and a
|
|
1162
|
-
// record that claimed otherwise would gate `wait` on a turn nobody opened.
|
|
1163
|
-
agentTurns.set(key, {
|
|
1164
|
-
turnId: nextTurnId(record.at),
|
|
1165
|
-
sessionId: record.sessionId,
|
|
1166
|
-
openedAt: null,
|
|
1167
|
-
closedAt: null,
|
|
1168
|
-
closedBy: null,
|
|
1169
|
-
generationAt: generation,
|
|
1170
|
-
displayEvent: displayOf(record),
|
|
1171
|
-
pendingDecisions: [],
|
|
1172
|
-
scraperCompletionPolls: 0,
|
|
1173
|
-
});
|
|
1174
|
-
boundTurnMap();
|
|
1175
|
-
return;
|
|
1176
|
-
}
|
|
1177
|
-
// An unrecognised notification type is not evidence of anything, and guessing
|
|
1178
|
-
// would be worse than the scraper.
|
|
1179
|
-
}
|
|
1180
|
-
/**
|
|
1181
|
-
* Open, or refresh, a dialog record on this instance's turn.
|
|
1182
|
-
*
|
|
1183
|
-
* `bootstrapDisplay` is used only when there is no record at all to hang the
|
|
1184
|
-
* decision on. That record is deliberately **not** an open turn — its
|
|
1185
|
-
* `openedAt` is null — so releasing the decision hands the pane back to the
|
|
1186
|
-
* scraper rather than asserting `running` for a turn nobody ever saw open.
|
|
1187
|
-
*/
|
|
1188
|
-
function openDecision(key, generation, input) {
|
|
1189
|
-
let turn = fencedTurn(key);
|
|
1190
|
-
if (turn === null) {
|
|
1191
|
-
turn = {
|
|
1192
|
-
turnId: nextTurnId(input.at),
|
|
1193
|
-
sessionId: null,
|
|
1194
|
-
openedAt: null,
|
|
1195
|
-
closedAt: null,
|
|
1196
|
-
closedBy: null,
|
|
1197
|
-
generationAt: generation,
|
|
1198
|
-
displayEvent: input.bootstrapDisplay,
|
|
1199
|
-
pendingDecisions: [],
|
|
1200
|
-
scraperCompletionPolls: 0,
|
|
1201
|
-
};
|
|
1202
|
-
agentTurns.set(key, turn);
|
|
1203
|
-
boundTurnMap();
|
|
1204
|
-
}
|
|
1205
|
-
else if (turn.closedAt !== null) {
|
|
1206
|
-
// The dialog is being raised after the turn it would have belonged to
|
|
1207
|
-
// ended — a `PermissionRequest` racing a `Stop`, or a re-check landing
|
|
1208
|
-
// late. Re-open a dialog-only record rather than resurrecting the turn.
|
|
1209
|
-
turn = {
|
|
1210
|
-
turnId: nextTurnId(input.at),
|
|
1211
|
-
sessionId: null,
|
|
1212
|
-
openedAt: null,
|
|
1213
|
-
closedAt: null,
|
|
1214
|
-
closedBy: null,
|
|
1215
|
-
generationAt: generation,
|
|
1216
|
-
displayEvent: input.bootstrapDisplay,
|
|
1217
|
-
pendingDecisions: [],
|
|
1218
|
-
scraperCompletionPolls: 0,
|
|
1219
|
-
};
|
|
1220
|
-
agentTurns.set(key, turn);
|
|
1221
|
-
boundTurnMap();
|
|
1222
|
-
}
|
|
1223
|
-
// Issue #1930 / S1: an id that fails validation is DISCARDED, not truncated —
|
|
1224
|
-
// a truncated id compares equal to a different id sharing its prefix, and the
|
|
1225
|
-
// reply to one approval would then retire another's record.
|
|
1226
|
-
const decisionId = input.decisionId === null ? null : (0, provisional_turn_1.acceptExternalId)(input.decisionId);
|
|
1227
|
-
if (input.decisionId !== null && decisionId === null)
|
|
1228
|
-
dropsFor(key).idsDiscarded += 1;
|
|
1229
|
-
const confirmed = input.source === 'notification';
|
|
1230
|
-
// Which record, if any, this report is a second sighting of.
|
|
1231
|
-
//
|
|
1232
|
-
// - an id matches its own record;
|
|
1233
|
-
// - an id with no record of its own may still be *confirming* the anonymous
|
|
1234
|
-
// prediction that forecast it. That is the measured pair: a
|
|
1235
|
-
// `PermissionRequest` this server declined to decide fires ~6 s before the
|
|
1236
|
-
// `Notification(permission_prompt)` that proves the dialog, and the
|
|
1237
|
-
// request carries no id;
|
|
1238
|
-
// - an anonymous report merges only with another anonymous one. A source
|
|
1239
|
-
// that publishes no ids therefore keeps exactly one dialog record per
|
|
1240
|
-
// instance, which is what #1725 did and the honest limit for it — matching
|
|
1241
|
-
// an anonymous report against an *identified* record would let one
|
|
1242
|
-
// approval's forecast confirm a different approval's dialog.
|
|
1243
|
-
const existing = decisionId !== null
|
|
1244
|
-
? (turn.pendingDecisions.find((decision) => decision.decisionId === decisionId) ??
|
|
1245
|
-
turn.pendingDecisions.find((decision) => decision.decisionId === null && decision.source === 'permission-request'))
|
|
1246
|
-
: turn.pendingDecisions.find((decision) => decision.decisionId === null);
|
|
1247
|
-
if (existing) {
|
|
1248
|
-
// The same dialog, reported twice: `PermissionRequest` predicted it and the
|
|
1249
|
-
// `Notification` then proved it. Keep the earliest `at` — that is when the
|
|
1250
|
-
// human was first blocked, and it is what the scraper-release grace and the
|
|
1251
|
-
// age bound are measured from — and take the confirmation.
|
|
1252
|
-
existing.message = (0, provisional_turn_1.boundDecisionMessage)(input.message) ?? existing.message;
|
|
1253
|
-
existing.toolName = (0, provisional_turn_1.boundDecisionToolName)(input.toolName) ?? existing.toolName;
|
|
1254
|
-
// Issue #2031, same rule as the two lines above it: a second sighting that
|
|
1255
|
-
// knows the rules fills them in, one that does not leaves the earlier
|
|
1256
|
-
// answer alone. The measured pair is exactly this — the `PermissionRequest`
|
|
1257
|
-
// forecast carries no patterns, the `Notification` that confirms it does.
|
|
1258
|
-
existing.patterns = (0, provisional_turn_1.boundDecisionPatterns)(input.patterns) ?? existing.patterns;
|
|
1259
|
-
existing.decisionId ??= decisionId;
|
|
1260
|
-
if (confirmed) {
|
|
1261
|
-
existing.source = 'notification';
|
|
1262
|
-
existing.confirmedAt ??= input.at;
|
|
1263
|
-
}
|
|
1264
|
-
return;
|
|
1265
|
-
}
|
|
1266
|
-
if (turn.pendingDecisions.length >= provisional_turn_1.MAX_PENDING_DECISIONS_PER_TURN) {
|
|
1267
|
-
// S14(d). Counted rather than dropped in silence: an agent raising more
|
|
1268
|
-
// approvals than this in one turn is a fact an operator has to be able to
|
|
1269
|
-
// see, and the oldest is the one a human has been looking at.
|
|
1270
|
-
dropsFor(key).decisionOverflow += 1;
|
|
1271
|
-
return;
|
|
1272
|
-
}
|
|
1273
|
-
turn.pendingDecisions.push({
|
|
1274
|
-
decisionId,
|
|
1275
|
-
at: input.at,
|
|
1276
|
-
source: input.source,
|
|
1277
|
-
message: (0, provisional_turn_1.boundDecisionMessage)(input.message),
|
|
1278
|
-
toolName: (0, provisional_turn_1.boundDecisionToolName)(input.toolName),
|
|
1279
|
-
patterns: (0, provisional_turn_1.boundDecisionPatterns)(input.patterns),
|
|
1280
|
-
confirmedAt: confirmed ? input.at : null,
|
|
1281
|
-
scraperCorroborated: false,
|
|
1282
|
-
recorded: false,
|
|
1283
|
-
});
|
|
1284
|
-
}
|
|
1285
|
-
/** Retire every dialog this instance was holding. */
|
|
1286
|
-
function releaseAllDecisions(key) {
|
|
1287
|
-
const turn = fencedTurn(key);
|
|
1288
|
-
if (turn !== null)
|
|
1289
|
-
turn.pendingDecisions = [];
|
|
1290
|
-
}
|
|
1291
|
-
/**
|
|
1292
|
-
* Retire the record a verdict was delivered for (Issue #1898).
|
|
1293
|
-
*
|
|
1294
|
-
* Matched on {@link AgentEventRecord.decisionId} when both sides have one: an
|
|
1295
|
-
* agent that can run two approvals at once would otherwise have the reply to
|
|
1296
|
-
* the first retire the record for the second. When either side is anonymous the
|
|
1297
|
-
* release is unconditional — an unmatched id is a source that publishes none,
|
|
1298
|
-
* and refusing to act there would leave the pre-#1898 stall in place for it.
|
|
1299
|
-
*/
|
|
1300
|
-
function releaseSettledDecision(key, record) {
|
|
1301
|
-
const turn = fencedTurn(key);
|
|
1302
|
-
if (turn === null || turn.pendingDecisions.length === 0)
|
|
1303
|
-
return;
|
|
1304
|
-
const settledId = record.decisionId === null || record.decisionId === undefined
|
|
1305
|
-
? null
|
|
1306
|
-
: (0, provisional_turn_1.acceptExternalId)(record.decisionId);
|
|
1307
|
-
if (settledId === null) {
|
|
1308
|
-
turn.pendingDecisions = [];
|
|
1309
|
-
return;
|
|
1310
|
-
}
|
|
1311
|
-
turn.pendingDecisions = turn.pendingDecisions.filter((decision) => decision.decisionId !== null && decision.decisionId !== settledId);
|
|
1312
|
-
}
|
|
1313
|
-
/** Keep the turn map bounded the way every other map in this module is. */
|
|
1314
|
-
function boundTurnMap() {
|
|
1315
|
-
trimOldestEntries(agentTurns, MAX_RECENT_EVENT_KEYS);
|
|
1316
|
-
}
|
|
1317
256
|
/**
|
|
1318
257
|
* @returns The last structured event reported by this instance, or null when it
|
|
1319
258
|
* has reported none.
|
|
@@ -1321,277 +260,6 @@ function boundTurnMap() {
|
|
|
1321
260
|
function getLastAgentEvent(worktreeId, cliToolId, instanceId) {
|
|
1322
261
|
return lastAgentEvent.get((0, auto_yes_state_1.buildCompositeKey)(worktreeId, cliToolId, instanceId)) ?? null;
|
|
1323
262
|
}
|
|
1324
|
-
/**
|
|
1325
|
-
* The last model this instance reported running, or null (Issue #1783).
|
|
1326
|
-
*
|
|
1327
|
-
* "Last **non-null**", which is the only useful reading: three of the four tools
|
|
1328
|
-
* that publish a model publish it on some events and not others, and Claude
|
|
1329
|
-
* publishes it on two (`SessionStart`, and each switch since #2363). Reading
|
|
1330
|
-
* `getLastAgentEvent()?.model` would therefore answer null for almost every
|
|
1331
|
-
* moment of almost every session.
|
|
1332
|
-
*
|
|
1333
|
-
* Deliberately **not** bounded by {@link STRUCTURED_STATE_MAX_AGE_MS}, unlike
|
|
1334
|
-
* every other reader in this module. That bound exists because a *status* that
|
|
1335
|
-
* nothing has refreshed is a claim about right now that may have expired — a
|
|
1336
|
-
* lost `Stop` leaving the layer asserting `running` forever. A model is not that
|
|
1337
|
-
* kind of claim: an eight-hour turn is on the same model at the end as at the
|
|
1338
|
-
* start, and expiring it would blank the display on precisely the long-running
|
|
1339
|
-
* sessions this is most useful for. What *is* honoured is identity: a new
|
|
1340
|
-
* generation or a discarded session drops the value, because the process that
|
|
1341
|
-
* reported it is gone. See {@link beginAgentEventGeneration} /
|
|
1342
|
-
* {@link discardAgentEventState}.
|
|
1343
|
-
*
|
|
1344
|
-
* In-memory only — nothing is written to `session_states`. After a server
|
|
1345
|
-
* restart codex and antigravity repopulate on their next event; Claude stays
|
|
1346
|
-
* null until its next `SessionStart`, a gap Phase 2 (#1784) closes from the
|
|
1347
|
-
* terminal frame.
|
|
1348
|
-
*/
|
|
1349
|
-
function getLastKnownAgentModel(worktreeId, cliToolId, instanceId) {
|
|
1350
|
-
return lastAgentModel.get((0, auto_yes_state_1.buildCompositeKey)(worktreeId, cliToolId, instanceId)) ?? null;
|
|
1351
|
-
}
|
|
1352
|
-
/**
|
|
1353
|
-
* Latch the model the agent named for itself outside an agent event
|
|
1354
|
-
* (Issue #2964).
|
|
1355
|
-
*
|
|
1356
|
-
* The hook channel's latch — the one {@link recordAgentEvent} writes from
|
|
1357
|
-
* `record.model` — for a source whose model arrives on something that is not
|
|
1358
|
-
* one of the seven event words. OpenCode V2 is that source: its `session.*`
|
|
1359
|
-
* frames carry no model, and the model it actually answered with is read off
|
|
1360
|
-
* the assistant messages of `GET /api/session/{id}/message` after the turn.
|
|
1361
|
-
*
|
|
1362
|
-
* Latch, never clear ({@link latchAgentModel}'s rule), and judged for a change
|
|
1363
|
-
* exactly as a hook-borne model is, so #2357's broadcast and push fire for it.
|
|
1364
|
-
*
|
|
1365
|
-
* @param model - The model name, or null/empty to leave the latch alone
|
|
1366
|
-
*/
|
|
1367
|
-
function recordAgentReportedModel(worktreeId, cliToolId, instanceId, model, at = Date.now()) {
|
|
1368
|
-
if (typeof model !== 'string' || model === '')
|
|
1369
|
-
return;
|
|
1370
|
-
const key = (0, auto_yes_state_1.buildCompositeKey)(worktreeId, cliToolId, instanceId);
|
|
1371
|
-
lastAgentModel.set(key, model.slice(0, agent_event_types_1.MAX_EVENT_DETAIL_LENGTH));
|
|
1372
|
-
lastAgentModelAt.set(key, at);
|
|
1373
|
-
observeAgentModel(worktreeId, cliToolId, instanceId, 'hook', at);
|
|
1374
|
-
}
|
|
1375
|
-
/**
|
|
1376
|
-
* Latch what a terminal capture showed for this instance (Issue #1784).
|
|
1377
|
-
*
|
|
1378
|
-
* Called from the status-detection poll with the text that poll already
|
|
1379
|
-
* captured — no `capture-pane` is issued for this, so the feature costs nothing
|
|
1380
|
-
* in tmux round-trips.
|
|
1381
|
-
*
|
|
1382
|
-
* **Each half latches independently.** A Codex footer carries a model on every
|
|
1383
|
-
* frame but an effort only on some formats; a Claude banner carries both and
|
|
1384
|
-
* then scrolls away entirely. Writing `{model, effort}` wholesale would let the
|
|
1385
|
-
* frame that stopped showing one of them blank a value the other frame proved.
|
|
1386
|
-
* Nothing is ever written as null: absent means "no frame has ever shown this",
|
|
1387
|
-
* which is the honest state for gemini/copilot and for any session whose chrome
|
|
1388
|
-
* this module has no rule for.
|
|
1389
|
-
*
|
|
1390
|
-
* **The one exception is an effort the frame shows but cannot name** (Issue
|
|
1391
|
-
* #2835, `info.effortUnreadable`). A codex bar that draws `default` or a
|
|
1392
|
-
* model-defined value where `xhigh` used to be is not "not showing" the effort
|
|
1393
|
-
* — it is showing a different one. Keeping `xhigh` would publish a value the
|
|
1394
|
-
* session is no longer running at, so the effort half is dropped to null and
|
|
1395
|
-
* the model half latches as usual.
|
|
1396
|
-
*
|
|
1397
|
-
* @param info - {@link import('../../lib/detection/model-info-extractor').extractModelInfo}'s answer
|
|
1398
|
-
*/
|
|
1399
|
-
function recordCapturedModelInfo(worktreeId, cliToolId, instanceId, info, at = Date.now()) {
|
|
1400
|
-
if (!info.model && !info.effort)
|
|
1401
|
-
return;
|
|
1402
|
-
const key = (0, auto_yes_state_1.buildCompositeKey)(worktreeId, cliToolId, instanceId);
|
|
1403
|
-
const previous = capturedModelInfo.get(key);
|
|
1404
|
-
const model = info.model ? info.model.slice(0, agent_event_types_1.MAX_EVENT_DETAIL_LENGTH) : (previous?.model ?? null);
|
|
1405
|
-
capturedModelInfo.set(key, {
|
|
1406
|
-
model,
|
|
1407
|
-
effort: info.effortUnreadable ? null : (info.effort ?? previous?.effort ?? null),
|
|
1408
|
-
// Issue #2361: the change stamp moves only when the model half takes a
|
|
1409
|
-
// different value. A banner re-read on every poll keeps the stamp where the
|
|
1410
|
-
// first sighting put it, so it never out-dates a hook by mere repetition.
|
|
1411
|
-
modelChangedAt: model === null
|
|
1412
|
-
? null
|
|
1413
|
-
: model === (previous?.model ?? null)
|
|
1414
|
-
? (previous?.modelChangedAt ?? at)
|
|
1415
|
-
: at,
|
|
1416
|
-
});
|
|
1417
|
-
// Issue #2357: an effort-only frame leaves the model half where it was, so
|
|
1418
|
-
// there is nothing to judge; a frame that named a model is the screen's
|
|
1419
|
-
// report and is compared against what this instance was last seen on.
|
|
1420
|
-
if (info.model)
|
|
1421
|
-
observeAgentModel(worktreeId, cliToolId, instanceId, 'frame', at);
|
|
1422
|
-
}
|
|
1423
|
-
/**
|
|
1424
|
-
* The last model/effort a terminal capture showed, both halves possibly null.
|
|
1425
|
-
*
|
|
1426
|
-
* The raw scraped value, before precedence is applied — {@link
|
|
1427
|
-
* getResolvedAgentModelInfo} is what callers publishing to the UI or the API
|
|
1428
|
-
* want. Exported for the tests and for anything that needs to tell "the screen
|
|
1429
|
-
* said" apart from "the agent said".
|
|
1430
|
-
*/
|
|
1431
|
-
function getLastCapturedModelInfo(worktreeId, cliToolId, instanceId) {
|
|
1432
|
-
const record = capturedModelInfo.get((0, auto_yes_state_1.buildCompositeKey)(worktreeId, cliToolId, instanceId));
|
|
1433
|
-
return { model: record?.model ?? null, effort: record?.effort ?? null };
|
|
1434
|
-
}
|
|
1435
|
-
/**
|
|
1436
|
-
* The model and reasoning effort to publish for this instance (Issue #1784).
|
|
1437
|
-
*
|
|
1438
|
-
* The single answer both surfaces should read: it folds the hook channel
|
|
1439
|
-
* (#1783) together with the screen under the precedence documented on
|
|
1440
|
-
* {@link mergeModelInfo} — hooks win for the model, the screen is the only
|
|
1441
|
-
* source of effort for codex/claude, and antigravity's effort is derived from
|
|
1442
|
-
* the id it reports rather than from its (renderer-truncated) status bar —
|
|
1443
|
-
* with the one exception {@link resolveAgentModel} documents (Issue #2361): a
|
|
1444
|
-
* claude frame that started naming a different model after the hook last
|
|
1445
|
-
* spoke is the newer statement, and wins.
|
|
1446
|
-
*
|
|
1447
|
-
* Both halves may be null, and routinely are: no tool publishes an effort over
|
|
1448
|
-
* hooks, and most tools publish neither. Callers omit the key rather than
|
|
1449
|
-
* emitting null — see `CliToolSessionStatus`.
|
|
1450
|
-
*/
|
|
1451
|
-
function getResolvedAgentModelInfo(worktreeId, cliToolId, instanceId) {
|
|
1452
|
-
return resolveAgentModel((0, auto_yes_state_1.buildCompositeKey)(worktreeId, cliToolId, instanceId), cliToolId).info;
|
|
1453
|
-
}
|
|
1454
|
-
/**
|
|
1455
|
-
* Tools whose hook channel may name the model at session start and never again
|
|
1456
|
-
* (Issue #2361).
|
|
1457
|
-
*
|
|
1458
|
-
* For these, a frame that starts naming a DIFFERENT model after the hook last
|
|
1459
|
-
* spoke may be the only report of a mid-session switch there is, and it
|
|
1460
|
-
* overtakes the hook in {@link resolveAgentModel}. For every other tool the
|
|
1461
|
-
* rule stays #1784's — hooks win — because their hook channel re-reports the
|
|
1462
|
-
* model on later events (codex and antigravity on every one, opencode on every
|
|
1463
|
-
* `message.updated`), so a switch reaches the hook latch by itself and a frame
|
|
1464
|
-
* that disagrees with a hook that is still re-affirming its value is a
|
|
1465
|
-
* misread, not news.
|
|
1466
|
-
*
|
|
1467
|
-
* Measured on claude 2.1.263 for #2361: `SessionStart` alone carried `model`
|
|
1468
|
-
* among the events then registered; `/model`, `/fast` and `/effort` fired none
|
|
1469
|
-
* of them; the `SessionStart` that `/clear` emits carries no `model`.
|
|
1470
|
-
*
|
|
1471
|
-
* Issue #2363 registers `PostModelSwitch`, so on 2.1.263 and later the hook
|
|
1472
|
-
* DOES re-report every `/model` and `/fast` that changes the model, in the
|
|
1473
|
-
* `SessionStart` spelling (`claude-sonnet-5`, `claude-opus-5[1m]`). Claude
|
|
1474
|
-
* stays on this list all the same, as the **fallback** rather than the rule,
|
|
1475
|
-
* and the two cases the rule exists for were weighed against each other:
|
|
1476
|
-
*
|
|
1477
|
-
* - *The hook re-reports.* The switch lands here as a hook write, stamping
|
|
1478
|
-
* {@link lastAgentModelAt}; the pane re-reads the rewritten banner or the
|
|
1479
|
-
* confirmation line moments later and names the same model in its label
|
|
1480
|
-
* spelling, which {@link isSameAgentModelName} (cross-channel) folds into
|
|
1481
|
-
* the hook's exact id. The frame does not overtake, the hook's id is what
|
|
1482
|
-
* is published, and the edge fires once. Nothing in this rule had to change
|
|
1483
|
-
* for that — it was written to defer to a hook that re-affirms.
|
|
1484
|
-
* - *The hook does not arrive.* A claude older than the hook, a session
|
|
1485
|
-
* launched without injection (`CM_AGENT_HOOKS_INJECT=0`, a hand-written
|
|
1486
|
-
* `--settings`), a delivery lost to a wedged server or the 3 s
|
|
1487
|
-
* de-duplication window: the latch is stale, the frame is the only channel
|
|
1488
|
-
* that heard the switch, and this rule is what publishes it. Removing the
|
|
1489
|
-
* tool from this set would take that back to the #2361 symptom — a stale
|
|
1490
|
-
* id shown with no edge — on precisely the sessions that have no other way
|
|
1491
|
-
* of reporting.
|
|
1492
|
-
*
|
|
1493
|
-
* The price of keeping it is the case #2361 already carried: a frame that
|
|
1494
|
-
* starts showing a stale confirmation line AFTER the hook spoke (the banner
|
|
1495
|
-
* scrolls away, an older `Set model to …` row becomes the fallback the
|
|
1496
|
-
* extractor reads) overtakes a hook that was right. That was possible before
|
|
1497
|
-
* #2363 — with a staler hook value underneath — and is no wider now; a switch
|
|
1498
|
-
* whose confirmation line the extractor does read leaves the newer row on the
|
|
1499
|
-
* pane, so the stale row is reachable only after a `/fast` or an automatic
|
|
1500
|
-
* fallback, and only until the next `/model` line or hook.
|
|
1501
|
-
*/
|
|
1502
|
-
const FRAME_OVERTAKES_HOOK_MODEL_TOOLS = new Set(['claude']);
|
|
1503
|
-
/**
|
|
1504
|
-
* The merged model/effort for one instance, and which channel the model half
|
|
1505
|
-
* came from (Issue #2361).
|
|
1506
|
-
*
|
|
1507
|
-
* {@link mergeModelInfo}'s precedence — hooks win the model — with one
|
|
1508
|
-
* exception layered on top for {@link FRAME_OVERTAKES_HOOK_MODEL_TOOLS}: when
|
|
1509
|
-
* the frame's model half CHANGED after the hook last named a model
|
|
1510
|
-
* ({@link CapturedModelRecord.modelChangedAt} > {@link lastAgentModelAt}) and
|
|
1511
|
-
* the two do not name the same model ({@link isSameAgentModelName}, cross
|
|
1512
|
-
* channel), the frame's value is the newer statement and is published in the
|
|
1513
|
-
* hook's place. Every other case is unchanged:
|
|
1514
|
-
*
|
|
1515
|
-
* - the frame names the same model as the hook (the startup banner after
|
|
1516
|
-
* `SessionStart`, or `/model` back to the model the hook named): the hook's
|
|
1517
|
-
* exact id is published, as before;
|
|
1518
|
-
* - the frame's value predates the hook (a switch line still on screen when a
|
|
1519
|
-
* relaunched process reports its starting model): the hook wins;
|
|
1520
|
-
* - no hook has spoken: the frame is the only source, as before.
|
|
1521
|
-
*
|
|
1522
|
-
* `source` is null when neither channel has a model.
|
|
1523
|
-
*/
|
|
1524
|
-
function resolveAgentModel(key, cliToolId) {
|
|
1525
|
-
const hooksModel = lastAgentModel.get(key) ?? null;
|
|
1526
|
-
const captured = capturedModelInfo.get(key);
|
|
1527
|
-
const capturedInfo = { model: captured?.model ?? null, effort: captured?.effort ?? null };
|
|
1528
|
-
// Issue #2048: the third source. Null for every tool but opencode and for
|
|
1529
|
-
// every opencode session running on a model's default, so every other
|
|
1530
|
-
// surface's string is byte-identical to pre-#2048.
|
|
1531
|
-
const hooksEffort = reportedEffort.get(key) ?? null;
|
|
1532
|
-
const frameChangedAt = captured?.modelChangedAt ?? null;
|
|
1533
|
-
const frameOvertakes = hooksModel !== null &&
|
|
1534
|
-
capturedInfo.model !== null &&
|
|
1535
|
-
frameChangedAt !== null &&
|
|
1536
|
-
FRAME_OVERTAKES_HOOK_MODEL_TOOLS.has(cliToolId) &&
|
|
1537
|
-
frameChangedAt > (lastAgentModelAt.get(key) ?? 0) &&
|
|
1538
|
-
!isSameAgentModelName(hooksModel, capturedInfo.model, { crossSource: true });
|
|
1539
|
-
const info = (0, model_info_extractor_1.mergeModelInfo)(cliToolId, frameOvertakes ? null : hooksModel, capturedInfo, hooksEffort);
|
|
1540
|
-
if (info.model === null)
|
|
1541
|
-
return { info, source: null };
|
|
1542
|
-
return { info, source: hooksModel !== null && !frameOvertakes ? 'hook' : 'frame' };
|
|
1543
|
-
}
|
|
1544
|
-
/**
|
|
1545
|
-
* Latch the effort (opencode's *variant*) the agent named for itself (#2048).
|
|
1546
|
-
*
|
|
1547
|
-
* Latch, never clear — {@link latchAgentModel}'s rule, and for the same reason:
|
|
1548
|
-
* `session.updated` omits `Session.model.variant` entirely when the session is
|
|
1549
|
-
* on a model's default, and `message.updated` omits `info.variant` on the
|
|
1550
|
-
* assistant message a turn opens with. Reading either absence as "the variant
|
|
1551
|
-
* is now unknown" would blank the display between frames of the same turn.
|
|
1552
|
-
*
|
|
1553
|
-
* Called by the tool's own reader — `lib/hooks/sources/opencode/subscription` —
|
|
1554
|
-
* rather than from the generic event path, because the value lives on frames
|
|
1555
|
-
* that map to none of the seven event words.
|
|
1556
|
-
*
|
|
1557
|
-
* @param effort - The variant name, or null/empty to leave the latch alone
|
|
1558
|
-
*/
|
|
1559
|
-
function recordAgentReportedEffort(worktreeId, cliToolId, instanceId, effort) {
|
|
1560
|
-
if (typeof effort !== 'string' || effort === '')
|
|
1561
|
-
return;
|
|
1562
|
-
reportedEffort.set((0, auto_yes_state_1.buildCompositeKey)(worktreeId, cliToolId, instanceId), effort.slice(0, agent_event_types_1.MAX_EVENT_DETAIL_LENGTH));
|
|
1563
|
-
}
|
|
1564
|
-
/**
|
|
1565
|
-
* The effort the agent last reported for this instance, or null (#2048).
|
|
1566
|
-
*
|
|
1567
|
-
* The raw latched value, before precedence — {@link getResolvedAgentModelInfo}
|
|
1568
|
-
* is what callers publishing to the UI or the API want. Exported so a test can
|
|
1569
|
-
* tell "the agent said" apart from "the screen said", the same way
|
|
1570
|
-
* {@link getLastCapturedModelInfo} lets it.
|
|
1571
|
-
*/
|
|
1572
|
-
function getLastReportedAgentEffort(worktreeId, cliToolId, instanceId) {
|
|
1573
|
-
return reportedEffort.get((0, auto_yes_state_1.buildCompositeKey)(worktreeId, cliToolId, instanceId)) ?? null;
|
|
1574
|
-
}
|
|
1575
|
-
/**
|
|
1576
|
-
* The reasoning effort this instance is running at, or null (Issue #1784).
|
|
1577
|
-
*
|
|
1578
|
-
* Convenience reader over {@link getResolvedAgentModelInfo} for callers that
|
|
1579
|
-
* want only the effort — `capture --json` and `instances` (#1785) among them.
|
|
1580
|
-
*/
|
|
1581
|
-
function getLastKnownAgentEffort(worktreeId, cliToolId, instanceId) {
|
|
1582
|
-
return getResolvedAgentModelInfo(worktreeId, cliToolId, instanceId).effort;
|
|
1583
|
-
}
|
|
1584
|
-
/**
|
|
1585
|
-
* How long a structured verdict is trusted after the event that produced it
|
|
1586
|
-
* (Issue #1723; one expression since #1930).
|
|
1587
|
-
*
|
|
1588
|
-
* The turn model owns the number now — see `provisional-turn`'s
|
|
1589
|
-
* {@link TURN_STALE_AFTER_MS}, which documents why it is 30 minutes. This name
|
|
1590
|
-
* is kept because it is what `status-evidence` and the #1723/#1725/#1903 suites
|
|
1591
|
-
* read, and because "the age bound on a verdict" and "how long a turn may run
|
|
1592
|
-
* unheard-from" really are the same fact rather than two that happen to agree.
|
|
1593
|
-
*/
|
|
1594
|
-
exports.STRUCTURED_STATE_MAX_AGE_MS = provisional_turn_1.TURN_STALE_AFTER_MS;
|
|
1595
263
|
/**
|
|
1596
264
|
* Open a new generation for this instance, invalidating everything reported
|
|
1597
265
|
* before now (Issue #1723).
|
|
@@ -1611,43 +279,43 @@ exports.STRUCTURED_STATE_MAX_AGE_MS = provisional_turn_1.TURN_STALE_AFTER_MS;
|
|
|
1611
279
|
*/
|
|
1612
280
|
function beginAgentEventGeneration(worktreeId, cliToolId, instanceId, at = Date.now()) {
|
|
1613
281
|
const key = (0, auto_yes_state_1.buildCompositeKey)(worktreeId, cliToolId, instanceId);
|
|
1614
|
-
generationStartedAt.set(key, at);
|
|
282
|
+
agent_event_turn_1.generationStartedAt.set(key, at);
|
|
1615
283
|
// Issue #1930: the turn the previous process was in ends with that process,
|
|
1616
284
|
// and the approvals it was holding go with it — they were raised against a
|
|
1617
285
|
// slot nobody is holding open any more. The turn record is kept, closed as
|
|
1618
286
|
// `closedBy: 'generation'`, so `capture --json` can say why it ended instead
|
|
1619
287
|
// of going quiet. The dialogs #1725 kept in a map of their own live on that
|
|
1620
288
|
// record now, which is what makes this one call retire both.
|
|
1621
|
-
fenceTurnForNewGeneration(key, at);
|
|
289
|
+
(0, agent_event_turn_1.fenceTurnForNewGeneration)(key, at);
|
|
1622
290
|
// Same reasoning for the question that dialog was asking (Issue #1726).
|
|
1623
|
-
askUserQuestion.delete(key);
|
|
291
|
+
agent_event_ask_user_question_1.askUserQuestion.delete(key);
|
|
1624
292
|
// And for "waiting for your input" (Issue #1786): a new process has not asked
|
|
1625
293
|
// for anything yet.
|
|
1626
|
-
awaitingInstruction.delete(key);
|
|
294
|
+
agent_event_awaiting_instruction_1.awaitingInstruction.delete(key);
|
|
1627
295
|
// And for the model (Issue #1783): a new generation is a new agent process,
|
|
1628
296
|
// which may have been launched on a different model entirely. Latching across
|
|
1629
297
|
// one would show the *previous* process's model with nothing to correct it —
|
|
1630
298
|
// Claude only re-reports on `SessionStart`, which lands moments later anyway.
|
|
1631
299
|
// A `/clear` is deliberately not affected: it reaches `recordAgentEvent` as
|
|
1632
300
|
// `session_end` + `session_start`, never this function.
|
|
1633
|
-
lastAgentModel.delete(key);
|
|
1634
|
-
lastAgentModelAt.delete(key);
|
|
301
|
+
agent_event_model_1.lastAgentModel.delete(key);
|
|
302
|
+
agent_event_model_1.lastAgentModelAt.delete(key);
|
|
1635
303
|
// Issue #1784: same argument for what the screen showed. The latch exists to
|
|
1636
304
|
// survive the banner scrolling away *within* one process; carrying it across
|
|
1637
305
|
// a relaunch would show the old process's effort with no frame left that
|
|
1638
306
|
// could contradict it. The next poll re-reads a live footer immediately.
|
|
1639
|
-
capturedModelInfo.delete(key);
|
|
307
|
+
agent_event_model_1.capturedModelInfo.delete(key);
|
|
1640
308
|
// Issue #2048: and the variant the agent named, for exactly #1783's reason —
|
|
1641
309
|
// the new process may have been launched with a different one, or with none.
|
|
1642
|
-
reportedEffort.delete(key);
|
|
310
|
+
agent_event_model_1.reportedEffort.delete(key);
|
|
1643
311
|
// Issue #2357: and the model this instance was last seen on. The next value
|
|
1644
312
|
// is a new process's starting model (`null → value`), not a change from the
|
|
1645
313
|
// process that was replaced.
|
|
1646
|
-
modelBaseline.delete(key);
|
|
314
|
+
agent_event_model_1.modelBaseline.delete(key);
|
|
1647
315
|
// Issue #1899: the ids claimed for this key were issued by the process that
|
|
1648
316
|
// has just been replaced. Unlike the time-window keys, they never expire on
|
|
1649
317
|
// their own, so a generation is the only thing that retires them.
|
|
1650
|
-
recentEventIdentities.delete(key);
|
|
318
|
+
agent_event_dedup_1.recentEventIdentities.delete(key);
|
|
1651
319
|
}
|
|
1652
320
|
/**
|
|
1653
321
|
* @returns Epoch ms the current generation began, or null when no generation
|
|
@@ -1655,7 +323,7 @@ function beginAgentEventGeneration(worktreeId, cliToolId, instanceId, at = Date.
|
|
|
1655
323
|
* server process, whose events are then judged on age alone.
|
|
1656
324
|
*/
|
|
1657
325
|
function getAgentEventGenerationStartedAt(worktreeId, cliToolId, instanceId) {
|
|
1658
|
-
return generationStartedAt.get((0, auto_yes_state_1.buildCompositeKey)(worktreeId, cliToolId, instanceId)) ?? null;
|
|
326
|
+
return agent_event_turn_1.generationStartedAt.get((0, auto_yes_state_1.buildCompositeKey)(worktreeId, cliToolId, instanceId)) ?? null;
|
|
1659
327
|
}
|
|
1660
328
|
/**
|
|
1661
329
|
* Discard the structured state for one instance — the session it described is
|
|
@@ -1669,105 +337,24 @@ function getAgentEventGenerationStartedAt(worktreeId, cliToolId, instanceId) {
|
|
|
1669
337
|
function discardAgentEventState(worktreeId, cliToolId, instanceId) {
|
|
1670
338
|
const key = (0, auto_yes_state_1.buildCompositeKey)(worktreeId, cliToolId, instanceId);
|
|
1671
339
|
lastAgentEvent.delete(key);
|
|
1672
|
-
generationStartedAt.delete(key);
|
|
340
|
+
agent_event_turn_1.generationStartedAt.delete(key);
|
|
1673
341
|
// Issue #1930: the turn, and with it the dialogs it was holding. Not counted
|
|
1674
342
|
// as an eviction — the session is gone, so there is nobody left to tell.
|
|
1675
|
-
agentTurns.delete(key);
|
|
1676
|
-
dropCounts.delete(key);
|
|
1677
|
-
askUserQuestion.delete(key);
|
|
1678
|
-
awaitingInstruction.delete(key);
|
|
343
|
+
agent_event_turn_1.agentTurns.delete(key);
|
|
344
|
+
agent_event_dedup_1.dropCounts.delete(key);
|
|
345
|
+
agent_event_ask_user_question_1.askUserQuestion.delete(key);
|
|
346
|
+
agent_event_awaiting_instruction_1.awaitingInstruction.delete(key);
|
|
1679
347
|
// Issue #1783: the session that was on this model no longer exists.
|
|
1680
|
-
lastAgentModel.delete(key);
|
|
1681
|
-
lastAgentModelAt.delete(key);
|
|
348
|
+
agent_event_model_1.lastAgentModel.delete(key);
|
|
349
|
+
agent_event_model_1.lastAgentModelAt.delete(key);
|
|
1682
350
|
// Issue #1784: nor does the pane its footer was read from.
|
|
1683
|
-
capturedModelInfo.delete(key);
|
|
351
|
+
agent_event_model_1.capturedModelInfo.delete(key);
|
|
1684
352
|
// Issue #2048: nor the variant that session was running at.
|
|
1685
|
-
reportedEffort.delete(key);
|
|
353
|
+
agent_event_model_1.reportedEffort.delete(key);
|
|
1686
354
|
// Issue #2357: nor the model it was last seen on.
|
|
1687
|
-
modelBaseline.delete(key);
|
|
355
|
+
agent_event_model_1.modelBaseline.delete(key);
|
|
1688
356
|
// Issue #1899: nor do the frame ids that session issued.
|
|
1689
|
-
recentEventIdentities.delete(key);
|
|
1690
|
-
}
|
|
1691
|
-
/**
|
|
1692
|
-
* The status this instance's turn implies, or null when it implies nothing
|
|
1693
|
-
* (Issue #1723, re-derived from the turn model in #1930).
|
|
1694
|
-
*
|
|
1695
|
-
* Null is the answer for every session on a machine where hooks never fire,
|
|
1696
|
-
* which is what keeps the unconfigured environment on exactly the behaviour it
|
|
1697
|
-
* had before #1723. It is also the answer when:
|
|
1698
|
-
*
|
|
1699
|
-
* - the turn belongs to a previous generation, i.e. to an agent process that
|
|
1700
|
-
* used to live in this pane;
|
|
1701
|
-
* - nothing has been heard about it for {@link STRUCTURED_STATE_MAX_AGE_MS};
|
|
1702
|
-
* - the turn ended for a reason that says nothing about whether the pane is
|
|
1703
|
-
* free — `session_end` (a `/clear` mid-turn), `stale`, `generation`, or the
|
|
1704
|
-
* two the *screen* closed it on (`scraper_evidence`, `resync_idle`). Only
|
|
1705
|
-
* the agent's own `Stop` publishes `ready` over this channel; everything
|
|
1706
|
-
* else hands the pane back to the scraper, which is the layer those closures
|
|
1707
|
-
* came from in the first place;
|
|
1708
|
-
* - no decision stands and the displayed event carries no verdict.
|
|
1709
|
-
*
|
|
1710
|
-
* ## The derivation, in the order it is applied
|
|
1711
|
-
*
|
|
1712
|
-
* | condition | verdict |
|
|
1713
|
-
* |---------------------------------------------|-------------------------------|
|
|
1714
|
-
* | an unanswered decision is live | `waiting` |
|
|
1715
|
-
* | the turn was closed by `stop` | `ready` / `hook_stop` |
|
|
1716
|
-
* | the turn was closed by anything else | null |
|
|
1717
|
-
* | the displayed event carries a verdict | that verdict |
|
|
1718
|
-
* | a turn is open and it does not | `running` / `hook_prompt_submit` |
|
|
1719
|
-
* | otherwise | null |
|
|
1720
|
-
*
|
|
1721
|
-
* §4 D3's `running ⟺ open turn ∧ 未裁定なし ∧ 現世代` falls out of the first,
|
|
1722
|
-
* fourth and fifth rows together with the generation fence — with one measured
|
|
1723
|
-
* exception written into the table above it: `notification(idle_prompt)`
|
|
1724
|
-
* publishes `ready` **without** closing the turn (#1839 caught Claude emitting
|
|
1725
|
-
* it 62 s into a turn that ran nothing, so it cannot be a boundary). A reader
|
|
1726
|
-
* that needs the boundary rather than the display reads `closedAt`.
|
|
1727
|
-
*
|
|
1728
|
-
* Whether the tmux session is alive is NOT checked here — the caller
|
|
1729
|
-
* (`buildCurrentOutput`) has already answered that with the CLI tool's own
|
|
1730
|
-
* `isRunning()` and returned early, and asking twice would mean a second tmux
|
|
1731
|
-
* round-trip on the hot path for an answer it is holding.
|
|
1732
|
-
*
|
|
1733
|
-
* @param now - Epoch ms; defaults to now
|
|
1734
|
-
*/
|
|
1735
|
-
function getStructuredSessionState(worktreeId, cliToolId, instanceId, now = Date.now()) {
|
|
1736
|
-
const key = (0, auto_yes_state_1.buildCompositeKey)(worktreeId, cliToolId, instanceId);
|
|
1737
|
-
const turn = effectiveTurn(key, now);
|
|
1738
|
-
if (turn === null)
|
|
1739
|
-
return null;
|
|
1740
|
-
const display = turn.displayEvent;
|
|
1741
|
-
if (now - display.at >= exports.STRUCTURED_STATE_MAX_AGE_MS)
|
|
1742
|
-
return null;
|
|
1743
|
-
const seen = { event: display.event, at: display.at, detail: display.detail };
|
|
1744
|
-
const pending = livePendingDecisions(key, turn, now);
|
|
1745
|
-
if (pending.length > 0) {
|
|
1746
|
-
// The two kinds of evidence stay apart in the reason token, exactly as
|
|
1747
|
-
// `structuredWaitingReason` keeps them apart for the payload: a
|
|
1748
|
-
// `Notification` is proof a dialog exists, a `PermissionRequest` this
|
|
1749
|
-
// server declined to decide is the prediction that one is about to.
|
|
1750
|
-
const reason = pending[0].source === 'notification'
|
|
1751
|
-
? status_mapping_1.HOOK_STATUS_REASON.PERMISSION_PROMPT
|
|
1752
|
-
: status_mapping_1.HOOK_STATUS_REASON.PERMISSION_REQUEST;
|
|
1753
|
-
return { status: 'waiting', reason, ...seen };
|
|
1754
|
-
}
|
|
1755
|
-
if (turn.closedAt !== null) {
|
|
1756
|
-
if (turn.closedBy === 'stop') {
|
|
1757
|
-
return { status: 'ready', reason: status_mapping_1.HOOK_STATUS_REASON.STOP, ...seen };
|
|
1758
|
-
}
|
|
1759
|
-
return null;
|
|
1760
|
-
}
|
|
1761
|
-
const verdict = (0, status_mapping_1.agentEventToSessionStatus)(display.event, display.detail);
|
|
1762
|
-
// A `waiting` read off the displayed event is a dialog nothing is holding any
|
|
1763
|
-
// more — the ledger above is what answers that question now. The turn is
|
|
1764
|
-
// still open, so the agent is still working.
|
|
1765
|
-
if (verdict === null || verdict.status === 'waiting') {
|
|
1766
|
-
if (turn.openedAt === null)
|
|
1767
|
-
return null;
|
|
1768
|
-
return { status: 'running', reason: status_mapping_1.HOOK_STATUS_REASON.PROMPT_SUBMIT, ...seen };
|
|
1769
|
-
}
|
|
1770
|
-
return { ...verdict, ...seen };
|
|
357
|
+
agent_event_dedup_1.recentEventIdentities.delete(key);
|
|
1771
358
|
}
|
|
1772
359
|
/**
|
|
1773
360
|
* How long a `permission-request`-sourced record survives without
|
|
@@ -1793,7 +380,7 @@ exports.STRUCTURED_PROMPT_PROVISIONAL_MAX_AGE_MS = provisional_turn_1.DIALOG_PEN
|
|
|
1793
380
|
*/
|
|
1794
381
|
function reportPermissionRequestPending(worktreeId, cliToolId, instanceId, toolName, at = Date.now()) {
|
|
1795
382
|
const key = (0, auto_yes_state_1.buildCompositeKey)(worktreeId, cliToolId, instanceId);
|
|
1796
|
-
openDecision(key, currentGeneration(key), {
|
|
383
|
+
(0, agent_event_turn_1.openDecision)(key, (0, agent_event_turn_1.currentGeneration)(key), {
|
|
1797
384
|
source: 'permission-request',
|
|
1798
385
|
at,
|
|
1799
386
|
message: null,
|
|
@@ -1849,7 +436,7 @@ function reportPermissionRequestPending(worktreeId, cliToolId, instanceId, toolN
|
|
|
1849
436
|
*/
|
|
1850
437
|
function reportQuestionPending(worktreeId, cliToolId, instanceId, input, at = Date.now()) {
|
|
1851
438
|
const key = (0, auto_yes_state_1.buildCompositeKey)(worktreeId, cliToolId, instanceId);
|
|
1852
|
-
openDecision(key, currentGeneration(key), {
|
|
439
|
+
(0, agent_event_turn_1.openDecision)(key, (0, agent_event_turn_1.currentGeneration)(key), {
|
|
1853
440
|
source: 'notification',
|
|
1854
441
|
at,
|
|
1855
442
|
// The question TEXT is not the dialog's `message`: it is published as
|
|
@@ -1880,7 +467,7 @@ function reportQuestionPending(worktreeId, cliToolId, instanceId, input, at = Da
|
|
|
1880
467
|
* @param now - Epoch ms; defaults to now
|
|
1881
468
|
*/
|
|
1882
469
|
function getStructuredPromptWaiting(worktreeId, cliToolId, instanceId, now = Date.now()) {
|
|
1883
|
-
return getPendingDecisions(worktreeId, cliToolId, instanceId, now)[0] ?? null;
|
|
470
|
+
return (0, agent_event_turn_1.getPendingDecisions)(worktreeId, cliToolId, instanceId, now)[0] ?? null;
|
|
1884
471
|
}
|
|
1885
472
|
/**
|
|
1886
473
|
* Record that the scraper has seen a blocking frame while this dialog is open
|
|
@@ -1917,368 +504,33 @@ function markStructuredPromptRecorded(worktreeId, cliToolId, instanceId) {
|
|
|
1917
504
|
* for a frame the scraper has just said is clear.
|
|
1918
505
|
*/
|
|
1919
506
|
function clearStructuredPromptWaiting(worktreeId, cliToolId, instanceId) {
|
|
1920
|
-
releaseAllDecisions((0, auto_yes_state_1.buildCompositeKey)(worktreeId, cliToolId, instanceId));
|
|
1921
|
-
}
|
|
1922
|
-
/**
|
|
1923
|
-
* When the in-flight question is released (Issue #1726).
|
|
1924
|
-
*
|
|
1925
|
-
* | event | effect on the question |
|
|
1926
|
-
* |-----------------------------------|------------------------|
|
|
1927
|
-
* | `pre_tool_use(AskUserQuestion)` | unchanged (this IS it) |
|
|
1928
|
-
* | `pre_tool_use(any other tool)` | release |
|
|
1929
|
-
* | `post_tool_use` | release |
|
|
1930
|
-
* | `notification(permission_prompt)` | **unchanged** |
|
|
1931
|
-
* | `notification(idle_prompt)` | release |
|
|
1932
|
-
* | `notification(other)` | unchanged |
|
|
1933
|
-
* | `stop` | release |
|
|
1934
|
-
* | `user_prompt_submit` | release |
|
|
1935
|
-
* | `session_start` / `session_end` | release |
|
|
1936
|
-
*
|
|
1937
|
-
* `PostToolUse` is the precise release — "this tool call is over" — and `Stop`
|
|
1938
|
-
* is the backstop for a delivery that never arrives. Issue #1726's text proposed
|
|
1939
|
-
* `PostToolUse` and the #1721 spike recorded it as never observed, so this Issue
|
|
1940
|
-
* measured it directly on a live v2.1.223 session (2026-08-06):
|
|
1941
|
-
*
|
|
1942
|
-
* ```
|
|
1943
|
-
* 15:36:04.112 PreToolUse AskUserQuestion
|
|
1944
|
-
* 15:36:28.643 PostToolUse AskUserQuestion <- the human answered
|
|
1945
|
-
* 15:36:29.992 Stop
|
|
1946
|
-
* ```
|
|
1947
|
-
*
|
|
1948
|
-
* It fires, 1.3 s ahead of `Stop` here — and much further ahead whenever the
|
|
1949
|
-
* agent keeps working after the answer, which is the case that matters: `Stop`
|
|
1950
|
-
* alone would leave a finished question in place for the whole rest of the turn.
|
|
1951
|
-
*
|
|
1952
|
-
* A `PostToolUse` for any other tool releases as well: the agent could not have
|
|
1953
|
-
* finished another tool call while this question was still on screen.
|
|
1954
|
-
*
|
|
1955
|
-
* **`Notification(permission_prompt)` keeping the question is a live
|
|
1956
|
-
* measurement, not a guess.** The #1721 report says the picker emits no events
|
|
1957
|
-
* while it is displayed (§5.6), and a first cut of this module read that as "any
|
|
1958
|
-
* event means the picker is gone". Driving a real v2.1.223 session through the
|
|
1959
|
-
* server on 2026-08-06 disproved it:
|
|
1960
|
-
*
|
|
1961
|
-
* ```
|
|
1962
|
-
* 15:29:18.099 PreToolUse(AskUserQuestion)
|
|
1963
|
-
* 15:29:18.109 PermissionRequest(AskUserQuestion) -> no decision
|
|
1964
|
-
* 15:29:24.128 Notification(permission_prompt) <- picker still on screen
|
|
1965
|
-
* ```
|
|
1966
|
-
*
|
|
1967
|
-
* The notification lands ~6 s after the dialog is drawn (§5.5's timing exactly),
|
|
1968
|
-
* which is *inside* the window §5.6 was counting rather than outside it. Under
|
|
1969
|
-
* the first rule it deleted the question six seconds after it arrived, and the
|
|
1970
|
-
* options went back to being the screen's — which is the whole feature, silently
|
|
1971
|
-
* off, on every real session.
|
|
1972
|
-
*
|
|
1973
|
-
* `idle_prompt` still releases: the agent reporting it is sitting at the
|
|
1974
|
-
* composer is the agent saying no picker is in front of it. An unrecognised
|
|
1975
|
-
* notification type changes nothing, because nothing is known about it.
|
|
1976
|
-
*/
|
|
1977
|
-
function applyAskUserQuestionTransition(key, record) {
|
|
1978
|
-
switch (record.event) {
|
|
1979
|
-
case 'pre_tool_use':
|
|
1980
|
-
// A `PreToolUse` for anything else means the agent has moved on to another
|
|
1981
|
-
// tool, so whatever question was in flight has been answered. Only
|
|
1982
|
-
// reachable when the operator's own settings.json registers a wider
|
|
1983
|
-
// matcher than the injected `AskUserQuestion` one — the two files are
|
|
1984
|
-
// concatenated, not substituted (#1722).
|
|
1985
|
-
//
|
|
1986
|
-
// Issue #2100: the exemption is the whole QUESTION vocabulary, not
|
|
1987
|
-
// Claude's one spelling. opencode names its question tool `question`, and
|
|
1988
|
-
// it publishes a tool part for the same call: measured on 1.18.23 in an
|
|
1989
|
-
// isolated HOME, `question.asked` and
|
|
1990
|
-
// `message.part.updated(tool=question, status=running)` arrive **in the
|
|
1991
|
-
// same millisecond**, in that order (§27.3). Under the old test the
|
|
1992
|
-
// second frame read as "the agent moved on to another tool" and deleted
|
|
1993
|
-
// the episode 1 ms after the ingest recorded it, which is why
|
|
1994
|
-
// `pendingDecisions[].questionOptions` and `promptData.askUserQuestion`
|
|
1995
|
-
// were null for every opencode question. Claude is unaffected —
|
|
1996
|
-
// `AskUserQuestion` is the first member of the same list — and the cost
|
|
1997
|
-
// is that a hook tool literally named `question` no longer releases the
|
|
1998
|
-
// episode, which is the same name `pendingDecisionKind` already reads as
|
|
1999
|
-
// "this record is a question" for every tool.
|
|
2000
|
-
if (!pending_decision_kind_1.QUESTION_DECISION_TOOL_NAMES.includes(record.detail ?? '')) {
|
|
2001
|
-
askUserQuestion.delete(key);
|
|
2002
|
-
}
|
|
2003
|
-
return;
|
|
2004
|
-
case 'notification':
|
|
2005
|
-
if (record.detail === 'idle_prompt')
|
|
2006
|
-
askUserQuestion.delete(key);
|
|
2007
|
-
return;
|
|
2008
|
-
case 'post_tool_use':
|
|
2009
|
-
case 'stop':
|
|
2010
|
-
case 'user_prompt_submit':
|
|
2011
|
-
case 'session_start':
|
|
2012
|
-
case 'session_end':
|
|
2013
|
-
askUserQuestion.delete(key);
|
|
2014
|
-
return;
|
|
2015
|
-
default:
|
|
2016
|
-
// exhaustive check: a new AgentEventType must decide its transition here
|
|
2017
|
-
record.event;
|
|
2018
|
-
return;
|
|
2019
|
-
}
|
|
2020
|
-
}
|
|
2021
|
-
/**
|
|
2022
|
-
* Record the `AskUserQuestion` invocation reported for one instance.
|
|
2023
|
-
*
|
|
2024
|
-
* Idempotent by construction: the same call is reported twice on every session
|
|
2025
|
-
* — once by `PreToolUse` and once by the `PermissionRequest` that
|
|
2026
|
-
* `AskUserQuestion` also raises with a byte-identical `tool_input` — and the
|
|
2027
|
-
* second delivery simply overwrites the first with the same content.
|
|
2028
|
-
*
|
|
2029
|
-
* @param at - Epoch ms; defaults to now
|
|
2030
|
-
*/
|
|
2031
|
-
function recordAskUserQuestion(worktreeId, cliToolId, instanceId, spec, at = Date.now()) {
|
|
2032
|
-
askUserQuestion.set((0, auto_yes_state_1.buildCompositeKey)(worktreeId, cliToolId, instanceId), { at, spec });
|
|
2033
|
-
}
|
|
2034
|
-
/**
|
|
2035
|
-
* The `AskUserQuestion` call in flight for this instance, or null.
|
|
2036
|
-
*
|
|
2037
|
-
* Bounded exactly like {@link getStructuredSessionState}: a record from a
|
|
2038
|
-
* previous generation belongs to a Claude process that no longer exists, and one
|
|
2039
|
-
* older than {@link STRUCTURED_STATE_MAX_AGE_MS} has outlived the screen it
|
|
2040
|
-
* describes. There is no provisional bound — unlike a `PermissionRequest`, a
|
|
2041
|
-
* `PreToolUse(AskUserQuestion)` is not a prediction that a dialog *might*
|
|
2042
|
-
* appear: allowing the permission request does not dismiss the picker (§5.6), so
|
|
2043
|
-
* the picker is drawn unconditionally.
|
|
2044
|
-
*
|
|
2045
|
-
* @param now - Epoch ms; defaults to now
|
|
2046
|
-
*/
|
|
2047
|
-
function getAskUserQuestion(worktreeId, cliToolId, instanceId, now = Date.now()) {
|
|
2048
|
-
const key = (0, auto_yes_state_1.buildCompositeKey)(worktreeId, cliToolId, instanceId);
|
|
2049
|
-
const episode = askUserQuestion.get(key);
|
|
2050
|
-
if (!episode)
|
|
2051
|
-
return null;
|
|
2052
|
-
const generation = generationStartedAt.get(key);
|
|
2053
|
-
if (generation !== undefined && episode.at < generation)
|
|
2054
|
-
return null;
|
|
2055
|
-
if (now - episode.at >= exports.STRUCTURED_STATE_MAX_AGE_MS)
|
|
2056
|
-
return null;
|
|
2057
|
-
return episode;
|
|
2058
|
-
}
|
|
2059
|
-
/** Drop the in-flight question for one instance. */
|
|
2060
|
-
function clearAskUserQuestion(worktreeId, cliToolId, instanceId) {
|
|
2061
|
-
askUserQuestion.delete((0, auto_yes_state_1.buildCompositeKey)(worktreeId, cliToolId, instanceId));
|
|
2062
|
-
}
|
|
2063
|
-
/**
|
|
2064
|
-
* Whether this event is a second copy of one already handled, and should be
|
|
2065
|
-
* dropped.
|
|
2066
|
-
*
|
|
2067
|
-
* Only events that name a `sessionId` can be deduplicated, and calling this
|
|
2068
|
-
* *claims* the key: a first call answers false and marks it, so the caller must
|
|
2069
|
-
* ask once per request and act on the answer. Events with no session id are
|
|
2070
|
-
* never suppressed — a caller that omits it (the #1549 relay run without a hook
|
|
2071
|
-
* payload, a hand-rolled `curl`) has given us nothing to tell two deliveries of
|
|
2072
|
-
* one turn from two genuine turns, and inventing a match there would silently
|
|
2073
|
-
* drop real events.
|
|
2074
|
-
*
|
|
2075
|
-
* The subtype is part of the key (Issue #1726). It has to be, now that
|
|
2076
|
-
* `pre_tool_use` exists: that event's subtype is the tool name, several tool
|
|
2077
|
-
* calls a second is ordinary, and a key without it would read a `Bash` call and
|
|
2078
|
-
* the `AskUserQuestion` that follows it as one delivery and drop the second. The
|
|
2079
|
-
* same correction applies to two `Notification`s of different types inside the
|
|
2080
|
-
* window, which were previously collapsed as well.
|
|
2081
|
-
*
|
|
2082
|
-
* @param at - Epoch ms; defaults to now
|
|
2083
|
-
* @param detail - The event's subtype, when it has one
|
|
2084
|
-
*/
|
|
2085
|
-
function isDuplicateAgentEvent(worktreeId, cliToolId, instanceId, event, sessionId, at = Date.now(), detail = null) {
|
|
2086
|
-
if (!sessionId)
|
|
2087
|
-
return false;
|
|
2088
|
-
const key = [
|
|
2089
|
-
(0, auto_yes_state_1.buildCompositeKey)(worktreeId, cliToolId, instanceId),
|
|
2090
|
-
event,
|
|
2091
|
-
detail ?? '',
|
|
2092
|
-
sessionId,
|
|
2093
|
-
].join(' ');
|
|
2094
|
-
const seenAt = recentEventKeys.get(key);
|
|
2095
|
-
if (seenAt !== undefined && at - seenAt < exports.AGENT_EVENT_DEDUP_WINDOW_MS) {
|
|
2096
|
-
return true;
|
|
2097
|
-
}
|
|
2098
|
-
recentEventKeys.set(key, at);
|
|
2099
|
-
pruneRecentEventKeys(at);
|
|
2100
|
-
return false;
|
|
2101
|
-
}
|
|
2102
|
-
/** Drop keys past the window, then the oldest survivors if still over the cap. */
|
|
2103
|
-
function pruneRecentEventKeys(now) {
|
|
2104
|
-
for (const [key, seenAt] of recentEventKeys) {
|
|
2105
|
-
if (now - seenAt >= exports.AGENT_EVENT_DEDUP_WINDOW_MS) {
|
|
2106
|
-
recentEventKeys.delete(key);
|
|
2107
|
-
}
|
|
2108
|
-
}
|
|
2109
|
-
// Map iterates in insertion order, so the head is the oldest.
|
|
2110
|
-
while (recentEventKeys.size > MAX_RECENT_EVENT_KEYS) {
|
|
2111
|
-
const oldest = recentEventKeys.keys().next();
|
|
2112
|
-
if (oldest.done)
|
|
2113
|
-
break;
|
|
2114
|
-
recentEventKeys.delete(oldest.value);
|
|
2115
|
-
}
|
|
2116
|
-
}
|
|
2117
|
-
/** How many dedup keys are currently retained. Test seam for the bound above. */
|
|
2118
|
-
function getRecentEventKeyCount() {
|
|
2119
|
-
return recentEventKeys.size;
|
|
2120
|
-
}
|
|
2121
|
-
// =============================================================================
|
|
2122
|
-
// Identity de-duplication (Issue #1899)
|
|
2123
|
-
// =============================================================================
|
|
2124
|
-
/**
|
|
2125
|
-
* Event words whose repeat is a fact about the session, not a re-delivery
|
|
2126
|
-
* (Issue #1899; design §4 D3 decision 2).
|
|
2127
|
-
*
|
|
2128
|
-
* These are the two words that end something, and neither of them carries an
|
|
2129
|
-
* id on any source measured so far. `session.idle` — opencode's `stop` — is
|
|
2130
|
-
* `{ "sessionID": "ses_…" }` and nothing else, which is exactly why
|
|
2131
|
-
* `sources/opencode/turn-gate` exists. With no id, a generic deduper has only
|
|
2132
|
-
* the clock, and the clock cannot tell a second turn from a second delivery:
|
|
2133
|
-
* #1899 measured a real `stop` 2.5 s after the previous one being dropped,
|
|
2134
|
-
* which leaves the newest event at `user_prompt_submit`, the instance reading
|
|
2135
|
-
* `running`, and `commandmate wait` blocked until the 30-minute staleness
|
|
2136
|
-
* bound.
|
|
2137
|
-
*
|
|
2138
|
-
* **The exemption is conditional on the source declaring an identity**, and
|
|
2139
|
-
* that condition is the whole safety argument. A source that declares one is a
|
|
2140
|
-
* source with a real deduper elsewhere — on the SSE path, `TurnGate`, which
|
|
2141
|
-
* arms on `session.status(busy)` and completes on the first `session.idle`
|
|
2142
|
-
* after arming, so the abort double-idle (19 ms apart, §5.3.2) never reaches
|
|
2143
|
-
* the ingest at all. Push hooks have no such gate: #1722's concatenated
|
|
2144
|
-
* settings really do post two `Stop`s for one turn, and
|
|
2145
|
-
* {@link isDuplicateAgentEvent} — which is what every hook receiver still
|
|
2146
|
-
* calls — is left untouched for them.
|
|
2147
|
-
*/
|
|
2148
|
-
exports.LIFECYCLE_AGENT_EVENT_TYPES = ['stop', 'session_end'];
|
|
2149
|
-
/**
|
|
2150
|
-
* Whether this delivery is a second copy of one already handled (Issue #1899).
|
|
2151
|
-
*
|
|
2152
|
-
* The tool-agnostic replacement for calling {@link isDuplicateAgentEvent}
|
|
2153
|
-
* directly, and it branches on the source's declared capability rather than on
|
|
2154
|
-
* its name — flip opencode's `eventIdentity` to `null` and every case below
|
|
2155
|
-
* falls back to the 3-second window it used before this Issue.
|
|
2156
|
-
*
|
|
2157
|
-
* Three rules, in order:
|
|
2158
|
-
*
|
|
2159
|
-
* 1. **The frame has an id** — key on it, with no time bound at all. An id is
|
|
2160
|
-
* a claim about identity that a clock cannot improve on: two approvals
|
|
2161
|
-
* 1 s apart are two approvals, and the same approval replayed by
|
|
2162
|
-
* `resyncPending` four minutes later is still one approval. That second
|
|
2163
|
-
* half is what the ingest's window was reaching for ("a frame delivered
|
|
2164
|
-
* twice by a re-sync racing the live stream") and never actually covered,
|
|
2165
|
-
* since a re-sync is not obliged to land within three seconds.
|
|
2166
|
-
* 2. **No id, but the word ends something** — never suppressed. See
|
|
2167
|
-
* {@link LIFECYCLE_AGENT_EVENT_TYPES}.
|
|
2168
|
-
* 3. **Anything else** — the time window, unchanged. That is every push
|
|
2169
|
-
* source, and the identity-declaring source's `session.created` /
|
|
2170
|
-
* `session.error`, which publish no id either but are not turn boundaries.
|
|
2171
|
-
*
|
|
2172
|
-
* Calling this *claims* the key, exactly as {@link isDuplicateAgentEvent} does:
|
|
2173
|
-
* ask once per delivery and act on the answer.
|
|
2174
|
-
*/
|
|
2175
|
-
function classifyAgentEventDelivery(delivery) {
|
|
2176
|
-
const composite = (0, auto_yes_state_1.buildCompositeKey)(delivery.worktreeId, delivery.cliToolId, delivery.instanceId);
|
|
2177
|
-
if (delivery.identityKind !== null) {
|
|
2178
|
-
// Issue #1930 / S1: the id is the agent's, and it becomes a Map key. One
|
|
2179
|
-
// that fails validation is DISCARDED — the delivery then takes the
|
|
2180
|
-
// no-id path below, which is a path this function already has — rather than
|
|
2181
|
-
// truncated, because a truncated id compares equal to a different id that
|
|
2182
|
-
// shares its prefix and would drop a real event as a repeat.
|
|
2183
|
-
const identity = delivery.identity === null ? null : (0, provisional_turn_1.acceptExternalId)(delivery.identity);
|
|
2184
|
-
if (delivery.identity !== null && identity === null)
|
|
2185
|
-
dropsFor(composite).idsDiscarded += 1;
|
|
2186
|
-
if (identity !== null) {
|
|
2187
|
-
if (claimEventIdentity(delivery, identity)) {
|
|
2188
|
-
dropsFor(composite).dedupDropped.identity += 1;
|
|
2189
|
-
return { duplicate: true, by: 'identity' };
|
|
2190
|
-
}
|
|
2191
|
-
return { duplicate: false };
|
|
2192
|
-
}
|
|
2193
|
-
if (exports.LIFECYCLE_AGENT_EVENT_TYPES.includes(delivery.event)) {
|
|
2194
|
-
return { duplicate: false };
|
|
2195
|
-
}
|
|
2196
|
-
}
|
|
2197
|
-
const duplicate = isDuplicateAgentEvent(delivery.worktreeId, delivery.cliToolId, delivery.instanceId, delivery.event, delivery.sessionId, delivery.at, delivery.detail);
|
|
2198
|
-
if (!duplicate)
|
|
2199
|
-
return { duplicate: false };
|
|
2200
|
-
dropsFor(composite).dedupDropped.timeWindow += 1;
|
|
2201
|
-
return { duplicate: true, by: 'time-window' };
|
|
2202
|
-
}
|
|
2203
|
-
/**
|
|
2204
|
-
* Claim `(event, detail, identity)` for one instance, answering whether it was
|
|
2205
|
-
* already claimed.
|
|
2206
|
-
*
|
|
2207
|
-
* `event` and `detail` are in the key and are not decoration: opencode asks an
|
|
2208
|
-
* approval under `properties.id` and answers it under `properties.requestID`
|
|
2209
|
-
* with **the same `per_…` value** (#1898), so a key made of the identity alone
|
|
2210
|
-
* would read `permission.replied` as a repeat of `permission.asked` and drop
|
|
2211
|
-
* the one frame that releases the dialog. The same shape covers
|
|
2212
|
-
* `pre_tool_use` / `post_tool_use`, which share a `callID`.
|
|
2213
|
-
*
|
|
2214
|
-
* `sessionId` is deliberately *not* in the key. An id is unique within the
|
|
2215
|
-
* agent that issued it, and folding in a field that is null on some deliveries
|
|
2216
|
-
* would let the same frame through twice.
|
|
2217
|
-
*/
|
|
2218
|
-
function claimEventIdentity(delivery, identity) {
|
|
2219
|
-
const composite = (0, auto_yes_state_1.buildCompositeKey)(delivery.worktreeId, delivery.cliToolId, delivery.instanceId);
|
|
2220
|
-
let claimed = recentEventIdentities.get(composite);
|
|
2221
|
-
if (!claimed) {
|
|
2222
|
-
claimed = new Map();
|
|
2223
|
-
recentEventIdentities.set(composite, claimed);
|
|
2224
|
-
// Bound the instances as well as the entries per instance: the composite
|
|
2225
|
-
// key is (worktree, tool, instance) and a long-lived server accumulates
|
|
2226
|
-
// worktrees (DR4-009).
|
|
2227
|
-
trimOldestEntries(recentEventIdentities, MAX_RECENT_EVENT_KEYS);
|
|
2228
|
-
}
|
|
2229
|
-
const key = [delivery.event, delivery.detail ?? '', identity].join(' ');
|
|
2230
|
-
if (claimed.has(key))
|
|
2231
|
-
return true;
|
|
2232
|
-
claimed.set(key, delivery.at);
|
|
2233
|
-
trimOldestEntries(claimed, MAX_RECENT_EVENT_KEYS);
|
|
2234
|
-
return false;
|
|
2235
|
-
}
|
|
2236
|
-
/** Drop the oldest entries until the map fits. Maps iterate in insertion order. */
|
|
2237
|
-
function trimOldestEntries(entries, max) {
|
|
2238
|
-
while (entries.size > max) {
|
|
2239
|
-
const oldest = entries.keys().next();
|
|
2240
|
-
if (oldest.done)
|
|
2241
|
-
break;
|
|
2242
|
-
entries.delete(oldest.value);
|
|
2243
|
-
}
|
|
2244
|
-
}
|
|
2245
|
-
/**
|
|
2246
|
-
* How many identities are retained for one instance, or across all of them when
|
|
2247
|
-
* no instance is named. Test seam for the bound above.
|
|
2248
|
-
*/
|
|
2249
|
-
function getRecentEventIdentityCount(worktreeId, cliToolId, instanceId) {
|
|
2250
|
-
if (worktreeId === undefined || cliToolId === undefined) {
|
|
2251
|
-
let total = 0;
|
|
2252
|
-
for (const claimed of recentEventIdentities.values())
|
|
2253
|
-
total += claimed.size;
|
|
2254
|
-
return total;
|
|
2255
|
-
}
|
|
2256
|
-
return recentEventIdentities.get((0, auto_yes_state_1.buildCompositeKey)(worktreeId, cliToolId, instanceId))?.size ?? 0;
|
|
507
|
+
(0, agent_event_turn_1.releaseAllDecisions)((0, auto_yes_state_1.buildCompositeKey)(worktreeId, cliToolId, instanceId));
|
|
2257
508
|
}
|
|
2258
509
|
/** Drop every recorded event. Test seam. */
|
|
2259
510
|
function clearAgentStopEvents() {
|
|
2260
511
|
lastStopEventAt.clear();
|
|
2261
512
|
lastAgentEvent.clear();
|
|
2262
|
-
recentEventKeys.clear();
|
|
2263
|
-
recentEventIdentities.clear();
|
|
2264
|
-
generationStartedAt.clear();
|
|
513
|
+
agent_event_dedup_1.recentEventKeys.clear();
|
|
514
|
+
agent_event_dedup_1.recentEventIdentities.clear();
|
|
515
|
+
agent_event_turn_1.generationStartedAt.clear();
|
|
2265
516
|
// Issue #1930: the turns, the dialogs they hold, and the tally of what was
|
|
2266
517
|
// dropped from them.
|
|
2267
|
-
agentTurns.clear();
|
|
2268
|
-
|
|
2269
|
-
|
|
2270
|
-
|
|
518
|
+
agent_event_turn_1.agentTurns.clear();
|
|
519
|
+
agent_event_turn_1.reopenedTurns.clear();
|
|
520
|
+
agent_event_dedup_1.dropCounts.clear();
|
|
521
|
+
agent_event_ask_user_question_1.askUserQuestion.clear();
|
|
522
|
+
agent_event_awaiting_instruction_1.awaitingInstruction.clear();
|
|
2271
523
|
// Issue #1783. CI runs with `fileParallelism: false`, so every suite in the
|
|
2272
524
|
// repo shares this process — a model latched by one test would otherwise be
|
|
2273
525
|
// read by another, in file order, and only in CI.
|
|
2274
|
-
lastAgentModel.clear();
|
|
2275
|
-
lastAgentModelAt.clear();
|
|
526
|
+
agent_event_model_1.lastAgentModel.clear();
|
|
527
|
+
agent_event_model_1.lastAgentModelAt.clear();
|
|
2276
528
|
// Issue #1784: and the same for the scraped half.
|
|
2277
|
-
capturedModelInfo.clear();
|
|
529
|
+
agent_event_model_1.capturedModelInfo.clear();
|
|
2278
530
|
// Issue #2048: and for the variant the agent reported.
|
|
2279
|
-
reportedEffort.clear();
|
|
531
|
+
agent_event_model_1.reportedEffort.clear();
|
|
2280
532
|
// Issue #2357: and for the model each instance was last seen on. The
|
|
2281
533
|
// listener set is deliberately NOT cleared — a subscription belongs to the
|
|
2282
534
|
// process, not to a session, and a suite that armed one expects it to stay.
|
|
2283
|
-
modelBaseline.clear();
|
|
535
|
+
agent_event_model_1.modelBaseline.clear();
|
|
2284
536
|
}
|