commandmate 0.42.2 → 0.43.0
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 +148 -148
- package/.next/app-path-routes-manifest.json +25 -25
- package/.next/build-manifest.json +2 -2
- package/.next/prerender-manifest.json +9 -9
- package/.next/required-server-files.json +1 -1
- package/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
- package/.next/server/app/api/agents/update/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/agents/versions/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/app/release-notes/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/app/update/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/app/update-check/route.js +1 -1
- package/.next/server/app/api/app/update-check/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/auth/login/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/auth/logout/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/auth/status/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/capabilities/route.js +1 -1
- package/.next/server/app/api/capabilities/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/daily-summary/route.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_client-reference-manifest.js +1 -1
- package/.next/server/app/api/external-apps/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/fs/browse/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/fs/recent-paths/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/hooks/agent-event/route.js +103 -1
- package/.next/server/app/api/hooks/agent-event/route.js.nft.json +1 -1
- package/.next/server/app/api/hooks/agent-event/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/hooks/claude-done/route.js +2 -2
- package/.next/server/app/api/hooks/claude-done/route.js.nft.json +1 -1
- package/.next/server/app/api/hooks/claude-done/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/hooks/permission-request/route.js +1 -1
- package/.next/server/app/api/hooks/permission-request/route.js.nft.json +1 -1
- package/.next/server/app/api/hooks/permission-request/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/metrics/vibe/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/ollama/models/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/push/escalation/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/push/subscriptions/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/push/vapid/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/relays/[relayId]/cancel/route.js +1 -1
- package/.next/server/app/api/relays/[relayId]/cancel/route.js.nft.json +1 -1
- package/.next/server/app/api/relays/[relayId]/cancel/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/relays/route.js +1 -1
- package/.next/server/app/api/relays/route.js.nft.json +1 -1
- package/.next/server/app/api/relays/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/remote/pair/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/repositories/[id]/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/repositories/clone/[jobId]/route.js +1 -1
- package/.next/server/app/api/repositories/clone/[jobId]/route.js.nft.json +1 -1
- package/.next/server/app/api/repositories/clone/[jobId]/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/repositories/clone/route.js +1 -1
- package/.next/server/app/api/repositories/clone/route.js.nft.json +1 -1
- package/.next/server/app/api/repositories/clone/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/repositories/excluded/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/repositories/restore/route.js +1 -1
- package/.next/server/app/api/repositories/restore/route.js.nft.json +1 -1
- package/.next/server/app/api/repositories/restore/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/repositories/route.js +1 -1
- package/.next/server/app/api/repositories/route.js.nft.json +1 -1
- package/.next/server/app/api/repositories/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/repositories/scan/route.js +1 -1
- package/.next/server/app/api/repositories/scan/route.js.nft.json +1 -1
- package/.next/server/app/api/repositories/scan/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/repositories/sync/route.js +1 -1
- package/.next/server/app/api/repositories/sync/route.js.nft.json +1 -1
- package/.next/server/app/api/repositories/sync/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/repositories/validate-path/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/settings/default-agents/route.js +1 -1
- package/.next/server/app/api/settings/default-agents/route.js.nft.json +1 -1
- package/.next/server/app/api/settings/default-agents/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/settings/default-surface-mode/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/sidebar/group-order/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/skills/[id]/route.js +1 -1
- package/.next/server/app/api/skills/[id]/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/skills/installations/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/skills/operations/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/skills/reindex/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/skills/route.js +1 -1
- package/.next/server/app/api/skills/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/slash-commands/route.js +1 -1
- package/.next/server/app/api/slash-commands/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/tasks/[taskId]/cancel/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/tasks/[taskId]/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/templates/[id]/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/templates/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/verification/runs/[runId]/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/verification/runs/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/auto-yes/route.js +1 -1
- package/.next/server/app/api/worktrees/[id]/auto-yes/route.js.nft.json +1 -1
- package/.next/server/app/api/worktrees/[id]/auto-yes/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/capture/route.js +1 -1
- package/.next/server/app/api/worktrees/[id]/capture/route.js.nft.json +1 -1
- package/.next/server/app/api/worktrees/[id]/capture/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/clear-composer/route.js +1 -1
- package/.next/server/app/api/worktrees/[id]/clear-composer/route.js.nft.json +1 -1
- package/.next/server/app/api/worktrees/[id]/clear-composer/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/cli-reference/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/cli-tool/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/cmate/schedules/route.js +3 -1
- 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.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 +3 -1
- 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_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/git/branch/create/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/git/branch/delete/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/git/branches/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/git/checkout/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/git/commit/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/git/diff/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/git/fetch/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/git/log/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/git/pull/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/git/push/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/git/reset/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/git/revert/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/git/show/[commitHash]/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/git/stage/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/git/staged/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/git/stash/[index]/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/git/stash/apply/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/git/stash/pop/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/git/stash/push/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/git/stash/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/git/status/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/git/unstage/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/git/working-diff/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/instances/notes/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/instances/opencode/route.js +1 -1
- package/.next/server/app/api/worktrees/[id]/instances/opencode/route.js.nft.json +1 -1
- package/.next/server/app/api/worktrees/[id]/instances/opencode/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/interrupt/route.js +1 -1
- package/.next/server/app/api/worktrees/[id]/interrupt/route.js.nft.json +1 -1
- package/.next/server/app/api/worktrees/[id]/interrupt/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/kill-session/route.js +1 -1
- package/.next/server/app/api/worktrees/[id]/kill-session/route.js.nft.json +1 -1
- package/.next/server/app/api/worktrees/[id]/kill-session/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/logs/[filename]/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/logs/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/marp-render/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/memos/[memoId]/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/memos/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/messages/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/opencode/diff/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/opencode/session/route.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 -103
- 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 +3 -1
- 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 +3 -1
- 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 -1
- 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 -103
- 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 +104 -2
- package/.next/server/app/api/worktrees/[id]/skills/[skillId]/git-workflow/route.js.nft.json +1 -1
- package/.next/server/app/api/worktrees/[id]/skills/[skillId]/git-workflow/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/skills/[skillId]/install/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/skills/[skillId]/plan/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/skills/[skillId]/uninstall/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/skills/[skillId]/uninstall-plan/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/skills/[skillId]/update/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/skills/[skillId]/update-plan/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/skills/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/slash-commands/route.js +1 -1
- package/.next/server/app/api/worktrees/[id]/slash-commands/route.js.nft.json +1 -1
- package/.next/server/app/api/worktrees/[id]/slash-commands/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/special-keys/route.js +1 -1
- package/.next/server/app/api/worktrees/[id]/special-keys/route.js.nft.json +1 -1
- package/.next/server/app/api/worktrees/[id]/special-keys/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/tasks/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/terminal/route.js +1 -1
- package/.next/server/app/api/worktrees/[id]/terminal/route.js.nft.json +1 -1
- package/.next/server/app/api/worktrees/[id]/terminal/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/timers/history/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/timers/route.js +1 -1
- package/.next/server/app/api/worktrees/[id]/timers/route.js.nft.json +1 -1
- package/.next/server/app/api/worktrees/[id]/timers/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/todos/[todoId]/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/todos/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/tree/[...path]/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/tree/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/upload/[...path]/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/verify/config/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/verify/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/verify/runs/[runId]/cancel/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/verify/runs/[runId]/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/verify/runs/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/viewed/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/apply-default-agents/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/route.js +1 -1
- package/.next/server/app/api/worktrees/route.js.nft.json +1 -1
- package/.next/server/app/api/worktrees/route_client-reference-manifest.js +1 -1
- package/.next/server/app/login/page_client-reference-manifest.js +1 -1
- package/.next/server/app/manifest.webmanifest/route_client-reference-manifest.js +1 -1
- package/.next/server/app/more/page_client-reference-manifest.js +1 -1
- package/.next/server/app/offline/page_client-reference-manifest.js +1 -1
- package/.next/server/app/page_client-reference-manifest.js +1 -1
- package/.next/server/app/proxy/[...path]/route_client-reference-manifest.js +1 -1
- package/.next/server/app/repositories/page_client-reference-manifest.js +1 -1
- package/.next/server/app/review/page_client-reference-manifest.js +1 -1
- package/.next/server/app/sessions/page_client-reference-manifest.js +1 -1
- package/.next/server/app/skills/[skillId]/page_client-reference-manifest.js +1 -1
- package/.next/server/app/skills/installed/page_client-reference-manifest.js +1 -1
- package/.next/server/app/skills/page_client-reference-manifest.js +1 -1
- package/.next/server/app/worktrees/[id]/files/[...path]/page_client-reference-manifest.js +1 -1
- package/.next/server/app/worktrees/[id]/page.js +9 -9
- package/.next/server/app/worktrees/[id]/page_client-reference-manifest.js +1 -1
- package/.next/server/app/worktrees/[id]/terminal/page_client-reference-manifest.js +1 -1
- package/.next/server/app-paths-manifest.json +25 -25
- package/.next/server/chunks/1068.js +1 -1
- package/.next/server/chunks/1210.js +1 -1
- package/.next/server/chunks/1501.js +3 -3
- package/.next/server/chunks/1990.js +1 -1
- package/.next/server/chunks/2119.js +1 -1
- package/.next/server/chunks/22.js +1 -1
- package/.next/server/chunks/2328.js +1 -1
- package/.next/server/chunks/2404.js +4 -3
- package/.next/server/chunks/2445.js +1 -1
- package/.next/server/chunks/252.js +1 -1
- package/.next/server/chunks/2816.js +1 -1
- package/.next/server/chunks/3543.js +1 -1
- package/.next/server/chunks/3730.js +1 -1
- package/.next/server/chunks/3945.js +1 -1
- package/.next/server/chunks/4207.js +3 -3
- package/.next/server/chunks/5064.js +1 -1
- package/.next/server/chunks/5571.js +1 -1
- package/.next/server/chunks/5742.js +1 -1
- package/.next/server/chunks/5775.js +1 -1
- package/.next/server/chunks/611.js +1 -1
- package/.next/server/chunks/6130.js +1 -1
- package/.next/server/chunks/6285.js +1 -1
- package/.next/server/chunks/6491.js +103 -0
- package/.next/server/chunks/6661.js +1 -1
- package/.next/server/chunks/6781.js +1 -1
- package/.next/server/chunks/6793.js +1 -1
- package/.next/server/chunks/7491.js +3 -0
- package/.next/server/chunks/7685.js +1 -1
- package/.next/server/chunks/7905.js +1 -1
- package/.next/server/chunks/8581.js +2 -2
- package/.next/server/chunks/8673.js +6 -6
- package/.next/server/chunks/8728.js +2 -2
- package/.next/server/chunks/8791.js +1 -1
- package/.next/server/chunks/8800.js +4 -3
- package/.next/server/chunks/8918.js +5 -5
- package/.next/server/chunks/9130.js +3 -3
- package/.next/server/chunks/{8052.js → 9353.js} +4 -2
- package/.next/server/chunks/9390.js +3 -0
- package/.next/server/chunks/9627.js +3 -3
- package/.next/server/chunks/967.js +1 -0
- package/.next/server/chunks/9731.js +3 -3
- package/.next/server/chunks/9882.js +3 -3
- package/.next/server/middleware-manifest.json +5 -5
- package/.next/server/pages/500.html +1 -1
- package/.next/server/server-reference-manifest.json +1 -1
- package/.next/static/chunks/{1642-fb2e176aaadbd137.js → 1642-767aed8e7c61ef7f.js} +1 -1
- package/.next/static/chunks/{2407-6b9e12c099fa598c.js → 2407-940df9b11fadafc6.js} +1 -1
- package/.next/static/chunks/{2619-04bc32f026a0d946.js → 2619-a305ad4810f1393a.js} +1 -1
- package/.next/static/chunks/5449-6cb80bab2bd06b79.js +1 -0
- package/.next/static/chunks/7705-1f58d4c03d991fcf.js +1 -0
- package/.next/static/chunks/8165-3525fb42f1b4976e.js +1 -0
- package/.next/static/chunks/app/{layout-5b95e1236dcd9ef6.js → layout-057c850cd0df978e.js} +1 -1
- package/.next/static/chunks/app/review/page-ff9efae3f86eb840.js +1 -0
- package/.next/static/chunks/app/skills/[skillId]/page-74aac15286504ef4.js +1 -0
- package/.next/static/chunks/app/skills/page-182e16168b2fc098.js +1 -0
- package/.next/static/chunks/app/worktrees/[id]/page-eab0bf7564d3f9e2.js +1 -0
- package/.next/static/chunks/app/worktrees/[id]/terminal/{page-373ea37b16d930c7.js → page-c601f3b3ece6557e.js} +1 -1
- package/.next/trace +13 -13
- package/README.md +3 -3
- package/dist/cli/commands/ask.d.ts.map +1 -1
- package/dist/cli/commands/ask.js +23 -149
- package/dist/cli/commands/reply.d.ts +40 -0
- package/dist/cli/commands/reply.d.ts.map +1 -0
- package/dist/cli/commands/reply.js +133 -0
- package/dist/cli/commands/wait.d.ts +5 -0
- package/dist/cli/commands/wait.d.ts.map +1 -1
- package/dist/cli/commands/wait.js +10 -1
- package/dist/cli/docs/agent-operations.d.ts +2 -2
- package/dist/cli/docs/agent-operations.d.ts.map +1 -1
- package/dist/cli/docs/agent-operations.js +13 -0
- package/dist/cli/program.d.ts.map +1 -1
- package/dist/cli/program.js +4 -0
- package/dist/cli/utils/reply-ledger.d.ts +95 -0
- package/dist/cli/utils/reply-ledger.d.ts.map +1 -0
- package/dist/cli/utils/reply-ledger.js +187 -0
- package/dist/lib/cli-tools/copilot-executable.d.ts +9 -0
- package/dist/lib/cli-tools/copilot-executable.d.ts.map +1 -1
- package/dist/lib/cli-tools/copilot-executable.js +21 -5
- package/dist/lib/cli-tools/opencode-executable.d.ts +103 -0
- package/dist/lib/cli-tools/opencode-executable.d.ts.map +1 -0
- package/dist/lib/cli-tools/opencode-executable.js +179 -0
- package/dist/lib/cli-tools/types.d.ts +1 -1
- package/dist/lib/cli-tools/types.d.ts.map +1 -1
- package/dist/lib/cli-tools/types.js +5 -1
- package/dist/lib/detection/tools/verified-against.d.ts +15 -0
- package/dist/lib/detection/tools/verified-against.d.ts.map +1 -1
- package/dist/lib/detection/tools/verified-against.js +17 -1
- package/dist/lib/detection/version-probes.d.ts.map +1 -1
- package/dist/lib/detection/version-probes.js +14 -1
- package/dist/lib/session/tmux-session-surface.d.ts.map +1 -1
- package/dist/lib/session/tmux-session-surface.js +1 -1
- package/dist/server/locales/en/worktree.json +44 -14
- package/dist/server/locales/ja/worktree.json +44 -14
- package/dist/server/server.js +23 -0
- package/dist/server/src/config/cli-tool-timing-config.js +19 -1
- package/dist/server/src/config/detection-evidence-config.js +3 -0
- package/dist/server/src/config/schedule-config.js +55 -1
- package/dist/server/src/lib/auto-yes-poller.js +47 -4
- package/dist/server/src/lib/cli-tools/agent-mode-spec.js +54 -2
- package/dist/server/src/lib/cli-tools/base.js +6 -2
- package/dist/server/src/lib/cli-tools/command-code.js +55 -1
- package/dist/server/src/lib/cli-tools/composer-spec.js +10 -0
- package/dist/server/src/lib/cli-tools/copilot-executable.js +21 -5
- package/dist/server/src/lib/cli-tools/graceful-exit.js +15 -1
- package/dist/server/src/lib/cli-tools/index.js +3 -1
- package/dist/server/src/lib/cli-tools/install-hints.js +9 -3
- package/dist/server/src/lib/cli-tools/liveness-spec.js +19 -0
- package/dist/server/src/lib/cli-tools/manager.js +2 -0
- package/dist/server/src/lib/cli-tools/opencode-executable.js +179 -0
- package/dist/server/src/lib/cli-tools/opencode-v2.js +369 -0
- package/dist/server/src/lib/cli-tools/opencode.js +29 -5
- package/dist/server/src/lib/cli-tools/types.js +5 -1
- package/dist/server/src/lib/cmate-cli-tool-parser.js +65 -7
- package/dist/server/src/lib/cmate-parser.js +12 -0
- package/dist/server/src/lib/cmate-validator.js +13 -1
- package/dist/server/src/lib/detection/cli-patterns.js +78 -2
- package/dist/server/src/lib/detection/model-info-extractor.js +79 -7
- package/dist/server/src/lib/detection/status-detector.js +10 -1
- package/dist/server/src/lib/detection/tools/codex/cli-patterns.js +2 -1
- package/dist/server/src/lib/detection/tools/opencode-v2/detect.js +253 -0
- package/dist/server/src/lib/detection/tools/opencode-v2/dialog-title.js +48 -0
- package/dist/server/src/lib/detection/tools/registry.js +2 -0
- package/dist/server/src/lib/detection/tools/run-detection.js +9 -0
- package/dist/server/src/lib/detection/tools/verified-against.js +17 -1
- package/dist/server/src/lib/detection/upstream-faults.js +8 -0
- package/dist/server/src/lib/detection/version-probes.js +14 -1
- package/dist/server/src/lib/hooks/agent-session-telemetry.js +36 -1
- package/dist/server/src/lib/hooks/sources/opencode/log-diagnosis.js +105 -0
- package/dist/server/src/lib/hooks/sources/opencode/source.js +7 -1
- package/dist/server/src/lib/hooks/sources/opencode-v2/client.js +422 -0
- package/dist/server/src/lib/hooks/sources/opencode-v2/decision-labels.js +24 -0
- package/dist/server/src/lib/hooks/sources/opencode-v2/history.js +421 -0
- package/dist/server/src/lib/hooks/sources/opencode-v2/ingest.js +271 -0
- package/dist/server/src/lib/hooks/sources/opencode-v2/mappers.js +165 -0
- package/dist/server/src/lib/hooks/sources/opencode-v2/payloads.js +351 -0
- package/dist/server/src/lib/hooks/sources/opencode-v2/ports.js +230 -0
- package/dist/server/src/lib/hooks/sources/opencode-v2/reattach.js +149 -0
- package/dist/server/src/lib/hooks/sources/opencode-v2/runtime.js +180 -0
- package/dist/server/src/lib/hooks/sources/opencode-v2/secrets.js +182 -0
- package/dist/server/src/lib/hooks/sources/opencode-v2/source.js +295 -0
- package/dist/server/src/lib/hooks/sources/opencode-v2/subscription.js +455 -0
- package/dist/server/src/lib/hooks/sources/opencode-v2/tool-id.js +18 -0
- package/dist/server/src/lib/hooks/sources/opencode-v2/usage.js +214 -0
- package/dist/server/src/lib/hooks/sources/registry.js +2 -0
- package/dist/server/src/lib/hooks/structured-decision-response.js +14 -5
- package/dist/server/src/lib/job-executor.js +2 -1
- package/dist/server/src/lib/polling/auto-yes-dialog-gate.js +15 -1
- package/dist/server/src/lib/polling/structured-history-gate.js +19 -5
- package/dist/server/src/lib/relay/relay-delivery.js +4 -1
- package/dist/server/src/lib/session/agent-event-state.js +24 -0
- package/dist/server/src/lib/session/claude-executor.js +184 -10
- package/dist/server/src/lib/session/current-output-builder.js +27 -3
- package/dist/server/src/lib/session/send-user-message.js +4 -0
- package/dist/server/src/lib/session/structured-prompt.js +54 -0
- package/dist/server/src/lib/session/tmux-session-surface.js +1 -1
- package/dist/server/src/lib/skills/compatibility-matrix.js +65 -0
- package/dist/server/src/lib/tmux/tmux.js +48 -2
- package/dist/server/src/lib/verification/env-clean-gate.js +87 -5
- package/dist/server/src/types/cli-tool-contracts.js +10 -2
- package/dist/types/cli-tool-contracts.d.ts +16 -5
- package/dist/types/cli-tool-contracts.d.ts.map +1 -1
- package/dist/types/cli-tool-contracts.js +10 -2
- package/package.json +2 -1
- package/release-notes/0.43.0.json +130 -0
- package/scripts/hooks/cmate-agent-event.sh +14 -0
- package/scripts/opencode-v2/launch.sh +226 -0
- package/.next/server/chunks/4443.js +0 -3
- package/.next/server/chunks/5164.js +0 -1
- package/.next/server/chunks/650.js +0 -1
- package/.next/static/chunks/5449-550bd273dccc3ae4.js +0 -1
- package/.next/static/chunks/7705-2a2936551b0cf9a3.js +0 -1
- package/.next/static/chunks/8165-934ee7ff3b4ddfd4.js +0 -1
- package/.next/static/chunks/app/review/page-30616daa1dc595c2.js +0 -1
- package/.next/static/chunks/app/skills/[skillId]/page-ab7e432b69fccb3b.js +0 -1
- package/.next/static/chunks/app/skills/page-56b4fa11223e0cbd.js +0 -1
- package/.next/static/chunks/app/worktrees/[id]/page-59f5337e031518c6.js +0 -1
- /package/.next/static/{MXZXkcFAnpELZnxxHB4iJ → BVkBYdceLRvMROqQIqeBx}/_buildManifest.js +0 -0
- /package/.next/static/{MXZXkcFAnpELZnxxHB4iJ → BVkBYdceLRvMROqQIqeBx}/_ssgManifest.js +0 -0
|
@@ -0,0 +1,455 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* The long-lived SSE subscription to one OpenCode V2 instance's own server
|
|
4
|
+
* (Issue #2934, decision D5).
|
|
5
|
+
*
|
|
6
|
+
* One instance = one `opencode2 serve` (started by `scripts/opencode-v2/launch.sh`
|
|
7
|
+
* in the instance's pane) = one subscription. Because the server is the
|
|
8
|
+
* instance's alone, routing is by construction: every frame this stream yields
|
|
9
|
+
* belongs to the instance it was opened for, whether or not the frame carries
|
|
10
|
+
* `location.directory` (several do not).
|
|
11
|
+
*
|
|
12
|
+
* The loop, per connection attempt:
|
|
13
|
+
* 1. read the password file — gone means the session was killed, so the
|
|
14
|
+
* subscription ends rather than retrying forever;
|
|
15
|
+
* 2. probe `GET /openapi.json` with it — `rejected` means the port now belongs
|
|
16
|
+
* to something that does not hold this password, which ends the
|
|
17
|
+
* subscription too (reading it would be reading someone else's events);
|
|
18
|
+
* 3. open `GET /api/event` and deliver the frames Phase 1 maps, while every
|
|
19
|
+
* frame and every `: heartbeat` refreshes the liveness and re-arms a
|
|
20
|
+
* watchdog that forces a reconnect on a silent socket.
|
|
21
|
+
* Any other failure waits out a backoff and tries again.
|
|
22
|
+
*
|
|
23
|
+
* Unlike v1's subscription this one keeps no turn gate. It does record the
|
|
24
|
+
* reply (Issue #2940): a frame that ends a turn also has `./history` read the
|
|
25
|
+
* finished turn off `GET /api/session/{id}/message`. And (Issue #2951) each
|
|
26
|
+
* connection first replays what the server is still waiting on
|
|
27
|
+
* ({@link resyncOpencodeV2Pending}), as v1's `resyncPending` does, so an
|
|
28
|
+
* approval raised while the stream was down is recorded and adjudicated.
|
|
29
|
+
* And (Issue #2981) `session.usage.updated` and the end of a turn have
|
|
30
|
+
* `./usage` refresh the session's spend and context, which the subscription
|
|
31
|
+
* drops again when it closes or ends — the place v1 drops them.
|
|
32
|
+
*
|
|
33
|
+
* @module lib/hooks/sources/opencode-v2/subscription
|
|
34
|
+
*/
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.OPENCODE_V2_MAX_RESYNCED_DECISIONS = exports.OPENCODE_V2_MAX_REJECTED_PROBES = exports.OPENCODE_V2_RECONNECT_BACKOFF_MS = exports.OPENCODE_V2_HEARTBEAT_TIMEOUT_MS = void 0;
|
|
37
|
+
exports.isOpencodeV2Subscribed = isOpencodeV2Subscribed;
|
|
38
|
+
exports.getOpencodeV2Liveness = getOpencodeV2Liveness;
|
|
39
|
+
exports.isOpencodeV2StructuredHistoryLive = isOpencodeV2StructuredHistoryLive;
|
|
40
|
+
exports.getOpencodeV2SubscribedPort = getOpencodeV2SubscribedPort;
|
|
41
|
+
exports.openOpencodeV2Subscription = openOpencodeV2Subscription;
|
|
42
|
+
exports.closeOpencodeV2Subscription = closeOpencodeV2Subscription;
|
|
43
|
+
exports.resetOpencodeV2Subscriptions = resetOpencodeV2Subscriptions;
|
|
44
|
+
exports.resyncOpencodeV2Pending = resyncOpencodeV2Pending;
|
|
45
|
+
exports.deliverOpencodeV2Frame = deliverOpencodeV2Frame;
|
|
46
|
+
exports.refreshOpencodeV2UsageOnFrame = refreshOpencodeV2UsageOnFrame;
|
|
47
|
+
exports.recordOpencodeV2TurnEnd = recordOpencodeV2TurnEnd;
|
|
48
|
+
const agent_session_telemetry_1 = require("../../../../lib/hooks/agent-session-telemetry");
|
|
49
|
+
const logger_1 = require("../../../../lib/logger");
|
|
50
|
+
const event_mapper_1 = require("../event-mapper");
|
|
51
|
+
const client_1 = require("./client");
|
|
52
|
+
const history_1 = require("./history");
|
|
53
|
+
const mappers_1 = require("./mappers");
|
|
54
|
+
const secrets_1 = require("./secrets");
|
|
55
|
+
const usage_1 = require("./usage");
|
|
56
|
+
const logger = (0, logger_1.createLogger)('lib/hooks/sources/opencode-v2/subscription');
|
|
57
|
+
/**
|
|
58
|
+
* A connection that has said nothing — not even a heartbeat — for this long is
|
|
59
|
+
* re-opened. 2.0.18 sends `: heartbeat` every 15 s (measured 2026-09-28), so
|
|
60
|
+
* this is four missed beats.
|
|
61
|
+
*/
|
|
62
|
+
exports.OPENCODE_V2_HEARTBEAT_TIMEOUT_MS = 60_000;
|
|
63
|
+
/** Waits between reconnect attempts; the last value repeats. */
|
|
64
|
+
exports.OPENCODE_V2_RECONNECT_BACKOFF_MS = [
|
|
65
|
+
500, 1_000, 2_000, 4_000, 8_000, 15_000, 30_000,
|
|
66
|
+
];
|
|
67
|
+
/** Consecutive `rejected` probes after which the port is taken to be someone else's. */
|
|
68
|
+
exports.OPENCODE_V2_MAX_REJECTED_PROBES = 3;
|
|
69
|
+
/**
|
|
70
|
+
* Cap on decisions replayed per list on one connection (Issue #2951). The same
|
|
71
|
+
* bound, for the same reason, as v1's `MAX_RESYNCED_DECISIONS`: the list comes
|
|
72
|
+
* off a server CommandMate does not police.
|
|
73
|
+
*/
|
|
74
|
+
exports.OPENCODE_V2_MAX_RESYNCED_DECISIONS = 50;
|
|
75
|
+
const subscriptions = (globalThis.__opencodeV2Subscriptions ??= new Map());
|
|
76
|
+
/** The last liveness of a subscription that ended on its own, for `liveness()`. */
|
|
77
|
+
const endedLiveness = (globalThis.__opencodeV2EndedLiveness ??= new Map());
|
|
78
|
+
/** Whether a subscription is open for the instance. */
|
|
79
|
+
function isOpencodeV2Subscribed(target) {
|
|
80
|
+
return subscriptions.has((0, secrets_1.opencodeV2KeyOf)(target));
|
|
81
|
+
}
|
|
82
|
+
/** The instance's subscription liveness. */
|
|
83
|
+
function getOpencodeV2Liveness(target) {
|
|
84
|
+
const key = (0, secrets_1.opencodeV2KeyOf)(target);
|
|
85
|
+
return subscriptions.get(key)?.liveness ?? endedLiveness.get(key) ?? { state: 'unknown' };
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Whether this instance's replies are being written from its server
|
|
89
|
+
* (Issue #2940).
|
|
90
|
+
*
|
|
91
|
+
* The screen scraper's stand-down test, read through
|
|
92
|
+
* `lib/polling/structured-history-gate`. `live` only, for the reason v1's
|
|
93
|
+
* `isOpencodeStructuredHistoryLive` gives: a `lost` stream delivers no end of
|
|
94
|
+
* turn, so it writes nothing, and standing the scraper down for it would leave
|
|
95
|
+
* the reply recorded by nobody.
|
|
96
|
+
*/
|
|
97
|
+
function isOpencodeV2StructuredHistoryLive(target) {
|
|
98
|
+
return getOpencodeV2Liveness(target).state === 'live';
|
|
99
|
+
}
|
|
100
|
+
/** The port an open subscription reads, or null. */
|
|
101
|
+
function getOpencodeV2SubscribedPort(target) {
|
|
102
|
+
return subscriptions.get((0, secrets_1.opencodeV2KeyOf)(target))?.port ?? null;
|
|
103
|
+
}
|
|
104
|
+
function handleFor(state) {
|
|
105
|
+
return {
|
|
106
|
+
close: async () => {
|
|
107
|
+
await closeOpencodeV2Subscription(state.target);
|
|
108
|
+
},
|
|
109
|
+
get liveness() {
|
|
110
|
+
return state.liveness;
|
|
111
|
+
},
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Open (or return the already-open) subscription for an instance.
|
|
116
|
+
*
|
|
117
|
+
* @param target - The instance
|
|
118
|
+
* @param port - Its server's port
|
|
119
|
+
* @param onEvent - Receives every mapped event
|
|
120
|
+
* @param normalize - The source's normalizer
|
|
121
|
+
*/
|
|
122
|
+
function openOpencodeV2Subscription(target, port, onEvent, normalize) {
|
|
123
|
+
const key = (0, secrets_1.opencodeV2KeyOf)(target);
|
|
124
|
+
const existing = subscriptions.get(key);
|
|
125
|
+
if (existing)
|
|
126
|
+
return handleFor(existing);
|
|
127
|
+
endedLiveness.delete(key);
|
|
128
|
+
const state = {
|
|
129
|
+
key,
|
|
130
|
+
target,
|
|
131
|
+
port,
|
|
132
|
+
onEvent,
|
|
133
|
+
normalize,
|
|
134
|
+
streamController: new AbortController(),
|
|
135
|
+
lifetimeController: new AbortController(),
|
|
136
|
+
liveness: { state: 'unknown' },
|
|
137
|
+
closed: false,
|
|
138
|
+
watchdog: null,
|
|
139
|
+
};
|
|
140
|
+
subscriptions.set(key, state);
|
|
141
|
+
logger.info('opencode-v2-subscription-opened', {
|
|
142
|
+
worktreeId: target.worktreeId,
|
|
143
|
+
instanceId: target.instanceId ?? target.cliToolId,
|
|
144
|
+
port,
|
|
145
|
+
});
|
|
146
|
+
void runStream(state);
|
|
147
|
+
return handleFor(state);
|
|
148
|
+
}
|
|
149
|
+
/** Close the instance's subscription. Safe to call when none is open. */
|
|
150
|
+
async function closeOpencodeV2Subscription(target) {
|
|
151
|
+
const key = (0, secrets_1.opencodeV2KeyOf)(target);
|
|
152
|
+
endedLiveness.delete(key);
|
|
153
|
+
const state = subscriptions.get(key);
|
|
154
|
+
if (!state)
|
|
155
|
+
return;
|
|
156
|
+
stop(state);
|
|
157
|
+
subscriptions.delete(key);
|
|
158
|
+
// Issue #2981, where v1's close does it (#2040): the records describe the
|
|
159
|
+
// conversation this process was having, and closing is the pane going away.
|
|
160
|
+
(0, agent_session_telemetry_1.forgetAgentSessionTelemetry)(target);
|
|
161
|
+
logger.info('opencode-v2-subscription-closed', {
|
|
162
|
+
worktreeId: target.worktreeId,
|
|
163
|
+
instanceId: target.instanceId ?? target.cliToolId,
|
|
164
|
+
port: state.port,
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
/** Close every subscription. For tests. */
|
|
168
|
+
function resetOpencodeV2Subscriptions() {
|
|
169
|
+
for (const state of subscriptions.values())
|
|
170
|
+
stop(state);
|
|
171
|
+
subscriptions.clear();
|
|
172
|
+
endedLiveness.clear();
|
|
173
|
+
}
|
|
174
|
+
function stop(state) {
|
|
175
|
+
state.closed = true;
|
|
176
|
+
clearWatchdog(state);
|
|
177
|
+
state.streamController.abort();
|
|
178
|
+
state.lifetimeController.abort();
|
|
179
|
+
}
|
|
180
|
+
/** End a subscription from inside its own loop, remembering why. */
|
|
181
|
+
function endSubscription(state, reason) {
|
|
182
|
+
stop(state);
|
|
183
|
+
state.liveness = { state: 'lost', since: Date.now(), reason };
|
|
184
|
+
if (subscriptions.get(state.key) === state)
|
|
185
|
+
subscriptions.delete(state.key);
|
|
186
|
+
endedLiveness.set(state.key, state.liveness);
|
|
187
|
+
// Issue #2981: ending on its own means the password file is gone or the port
|
|
188
|
+
// is someone else's — the server this pane had is not coming back either.
|
|
189
|
+
(0, agent_session_telemetry_1.forgetAgentSessionTelemetry)(state.target);
|
|
190
|
+
logger.warn('opencode-v2-subscription-ended', {
|
|
191
|
+
worktreeId: state.target.worktreeId,
|
|
192
|
+
instanceId: state.target.instanceId ?? state.target.cliToolId,
|
|
193
|
+
port: state.port,
|
|
194
|
+
reason,
|
|
195
|
+
});
|
|
196
|
+
}
|
|
197
|
+
function clearWatchdog(state) {
|
|
198
|
+
if (state.watchdog !== null) {
|
|
199
|
+
clearTimeout(state.watchdog);
|
|
200
|
+
state.watchdog = null;
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
function markAlive(state) {
|
|
204
|
+
state.liveness = { state: 'live', lastHeartbeatAt: Date.now() };
|
|
205
|
+
clearWatchdog(state);
|
|
206
|
+
const timer = setTimeout(() => {
|
|
207
|
+
if (state.closed)
|
|
208
|
+
return;
|
|
209
|
+
logger.warn('opencode-v2-subscription-heartbeat-lost', {
|
|
210
|
+
worktreeId: state.target.worktreeId,
|
|
211
|
+
instanceId: state.target.instanceId ?? state.target.cliToolId,
|
|
212
|
+
port: state.port,
|
|
213
|
+
afterMs: exports.OPENCODE_V2_HEARTBEAT_TIMEOUT_MS,
|
|
214
|
+
});
|
|
215
|
+
state.streamController.abort();
|
|
216
|
+
}, exports.OPENCODE_V2_HEARTBEAT_TIMEOUT_MS);
|
|
217
|
+
timer.unref?.();
|
|
218
|
+
state.watchdog = timer;
|
|
219
|
+
}
|
|
220
|
+
function waitUnlessAborted(ms, signal) {
|
|
221
|
+
return new Promise((resolve) => {
|
|
222
|
+
if (signal.aborted) {
|
|
223
|
+
resolve();
|
|
224
|
+
return;
|
|
225
|
+
}
|
|
226
|
+
const onAbort = () => {
|
|
227
|
+
clearTimeout(timer);
|
|
228
|
+
resolve();
|
|
229
|
+
};
|
|
230
|
+
signal.addEventListener('abort', onAbort, { once: true });
|
|
231
|
+
const timer = setTimeout(() => {
|
|
232
|
+
signal.removeEventListener('abort', onAbort);
|
|
233
|
+
resolve();
|
|
234
|
+
}, ms);
|
|
235
|
+
timer.unref?.();
|
|
236
|
+
});
|
|
237
|
+
}
|
|
238
|
+
function backoffFor(attempt) {
|
|
239
|
+
return exports.OPENCODE_V2_RECONNECT_BACKOFF_MS[Math.min(attempt, exports.OPENCODE_V2_RECONNECT_BACKOFF_MS.length - 1)];
|
|
240
|
+
}
|
|
241
|
+
async function runStream(state) {
|
|
242
|
+
let attempt = 0;
|
|
243
|
+
let rejected = 0;
|
|
244
|
+
while (!state.closed) {
|
|
245
|
+
state.streamController = new AbortController();
|
|
246
|
+
let reason = 'stream-ended';
|
|
247
|
+
try {
|
|
248
|
+
const password = (0, secrets_1.readOpencodeV2Password)(state.target);
|
|
249
|
+
if (password === null) {
|
|
250
|
+
endSubscription(state, 'password-file-missing');
|
|
251
|
+
return;
|
|
252
|
+
}
|
|
253
|
+
const probe = await (0, client_1.probeOpencodeV2Server)(state.port, password);
|
|
254
|
+
if (state.closed)
|
|
255
|
+
return;
|
|
256
|
+
if (probe.kind === 'rejected') {
|
|
257
|
+
rejected += 1;
|
|
258
|
+
reason = `server-rejected-${probe.status}`;
|
|
259
|
+
if (rejected >= exports.OPENCODE_V2_MAX_REJECTED_PROBES) {
|
|
260
|
+
endSubscription(state, 'port-identity-changed');
|
|
261
|
+
return;
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
else if (probe.kind === 'unreachable') {
|
|
265
|
+
reason = 'server-unreachable';
|
|
266
|
+
}
|
|
267
|
+
else {
|
|
268
|
+
rejected = 0;
|
|
269
|
+
const items = await (0, client_1.openOpencodeV2EventStream)(state.port, password, state.streamController.signal);
|
|
270
|
+
markAlive(state);
|
|
271
|
+
attempt = 0;
|
|
272
|
+
// Issue #2951: before the first live frame, so an approval raised while
|
|
273
|
+
// the stream was down reaches the same ingest (and Auto-Yes) as a live
|
|
274
|
+
// one. A frame for the same id on the new stream is then a duplicate by
|
|
275
|
+
// identity, not a second approval.
|
|
276
|
+
await resyncOpencodeV2Pending(state.target, state.port, password, (frame) => deliver(state, frame));
|
|
277
|
+
for await (const item of items) {
|
|
278
|
+
if (state.closed)
|
|
279
|
+
break;
|
|
280
|
+
markAlive(state);
|
|
281
|
+
if (item.kind === 'frame')
|
|
282
|
+
deliver(state, item.frame);
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
catch (error) {
|
|
287
|
+
reason = error instanceof Error ? error.message : String(error);
|
|
288
|
+
}
|
|
289
|
+
finally {
|
|
290
|
+
clearWatchdog(state);
|
|
291
|
+
}
|
|
292
|
+
if (state.closed)
|
|
293
|
+
return;
|
|
294
|
+
state.liveness = { state: 'lost', since: Date.now(), reason };
|
|
295
|
+
logger.info('opencode-v2-subscription-disconnected', {
|
|
296
|
+
worktreeId: state.target.worktreeId,
|
|
297
|
+
instanceId: state.target.instanceId ?? state.target.cliToolId,
|
|
298
|
+
port: state.port,
|
|
299
|
+
reason,
|
|
300
|
+
attempt,
|
|
301
|
+
});
|
|
302
|
+
await waitUnlessAborted(backoffFor(attempt), state.lifetimeController.signal);
|
|
303
|
+
attempt += 1;
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
/**
|
|
307
|
+
* Replay what the server is still waiting on as the frames that announced it
|
|
308
|
+
* (Issue #2951).
|
|
309
|
+
*
|
|
310
|
+
* `GET /api/permission/request` entries become `permission.asked` frames and
|
|
311
|
+
* `GET /api/form` entries `form.created` frames (the form nested as
|
|
312
|
+
* `data.form`, as 2.0.18 sends it), so one mapper and one parser cover both
|
|
313
|
+
* arrival routes. Never throws; an unreachable server replays nothing.
|
|
314
|
+
*
|
|
315
|
+
* Exported for the tests.
|
|
316
|
+
*
|
|
317
|
+
* @returns How many frames were replayed
|
|
318
|
+
*/
|
|
319
|
+
async function resyncOpencodeV2Pending(target, port, password, replay) {
|
|
320
|
+
let replayed = 0;
|
|
321
|
+
try {
|
|
322
|
+
const [permissions, forms] = await Promise.all([
|
|
323
|
+
(0, client_1.fetchOpencodeV2PendingPermissions)(port, password),
|
|
324
|
+
(0, client_1.fetchOpencodeV2PendingForms)(port, password),
|
|
325
|
+
]);
|
|
326
|
+
const each = (entries, type, wrap) => {
|
|
327
|
+
const list = (entries ?? []).filter(event_mapper_1.isPlainObject);
|
|
328
|
+
const kept = list.slice(0, exports.OPENCODE_V2_MAX_RESYNCED_DECISIONS);
|
|
329
|
+
if (list.length > kept.length) {
|
|
330
|
+
logger.warn('opencode-v2-resync-truncated', {
|
|
331
|
+
worktreeId: target.worktreeId,
|
|
332
|
+
instanceId: target.instanceId ?? target.cliToolId,
|
|
333
|
+
type,
|
|
334
|
+
examined: kept.length,
|
|
335
|
+
skipped: list.length - kept.length,
|
|
336
|
+
limit: exports.OPENCODE_V2_MAX_RESYNCED_DECISIONS,
|
|
337
|
+
});
|
|
338
|
+
}
|
|
339
|
+
for (const entry of kept) {
|
|
340
|
+
const id = typeof entry.id === 'string' && entry.id !== '' ? entry.id : null;
|
|
341
|
+
if (id === null)
|
|
342
|
+
continue;
|
|
343
|
+
replay({ id: `resync_${id}`, type, data: wrap(entry) });
|
|
344
|
+
replayed += 1;
|
|
345
|
+
}
|
|
346
|
+
};
|
|
347
|
+
each(permissions, 'permission.asked', (entry) => entry);
|
|
348
|
+
each(forms, 'form.created', (entry) => ({ form: entry }));
|
|
349
|
+
}
|
|
350
|
+
catch (error) {
|
|
351
|
+
logger.warn('opencode-v2-resync-failed', {
|
|
352
|
+
worktreeId: target.worktreeId,
|
|
353
|
+
instanceId: target.instanceId ?? target.cliToolId,
|
|
354
|
+
error: error instanceof Error ? error.message : String(error),
|
|
355
|
+
});
|
|
356
|
+
}
|
|
357
|
+
if (replayed > 0) {
|
|
358
|
+
logger.info('opencode-v2-resync', {
|
|
359
|
+
worktreeId: target.worktreeId,
|
|
360
|
+
instanceId: target.instanceId ?? target.cliToolId,
|
|
361
|
+
replayed,
|
|
362
|
+
});
|
|
363
|
+
}
|
|
364
|
+
return replayed;
|
|
365
|
+
}
|
|
366
|
+
/**
|
|
367
|
+
* Normalize one frame and hand it on, if Phase 1 reads its type.
|
|
368
|
+
*
|
|
369
|
+
* Exported for the mapping tests, which drive frames through exactly the path
|
|
370
|
+
* the stream does.
|
|
371
|
+
*/
|
|
372
|
+
function deliverOpencodeV2Frame(frame, normalize, onEvent, receivedAt = Date.now()) {
|
|
373
|
+
if (!(0, mappers_1.isHandledOpencodeV2EventType)((0, mappers_1.frameType)(frame)))
|
|
374
|
+
return false;
|
|
375
|
+
const event = normalize({ payload: frame, receivedAt });
|
|
376
|
+
if (!event)
|
|
377
|
+
return false;
|
|
378
|
+
onEvent(event);
|
|
379
|
+
return true;
|
|
380
|
+
}
|
|
381
|
+
function deliver(state, frame) {
|
|
382
|
+
try {
|
|
383
|
+
deliverOpencodeV2Frame(frame, state.normalize, state.onEvent);
|
|
384
|
+
}
|
|
385
|
+
catch (error) {
|
|
386
|
+
logger.warn('opencode-v2-frame-delivery-failed', {
|
|
387
|
+
worktreeId: state.target.worktreeId,
|
|
388
|
+
instanceId: state.target.instanceId ?? state.target.cliToolId,
|
|
389
|
+
type: (0, mappers_1.frameType)(frame),
|
|
390
|
+
error: error instanceof Error ? error.message : String(error),
|
|
391
|
+
});
|
|
392
|
+
}
|
|
393
|
+
void recordOpencodeV2TurnEnd(state.target, state.port, frame);
|
|
394
|
+
void refreshOpencodeV2UsageOnFrame(state.target, state.port, frame, () => isCurrentSubscription(state));
|
|
395
|
+
}
|
|
396
|
+
function isCurrentSubscription(state) {
|
|
397
|
+
return !state.closed && subscriptions.get(state.key) === state;
|
|
398
|
+
}
|
|
399
|
+
/**
|
|
400
|
+
* Have `./usage` re-read the session's spend and context when this frame says
|
|
401
|
+
* they moved (Issue #2981): `session.usage.updated`, or the end of a turn.
|
|
402
|
+
*
|
|
403
|
+
* Not awaited, for the reason {@link recordOpencodeV2TurnEnd} gives. Exported
|
|
404
|
+
* for the tests.
|
|
405
|
+
*
|
|
406
|
+
* @returns The refresh, or null when the frame triggers none
|
|
407
|
+
*/
|
|
408
|
+
function refreshOpencodeV2UsageOnFrame(target, port, frame, isCurrent = () => true) {
|
|
409
|
+
try {
|
|
410
|
+
if (!(0, usage_1.isOpencodeV2UsageTriggerType)((0, mappers_1.frameType)(frame)))
|
|
411
|
+
return null;
|
|
412
|
+
const sessionId = (0, mappers_1.frameSessionId)(frame);
|
|
413
|
+
if (sessionId === null)
|
|
414
|
+
return null;
|
|
415
|
+
return (0, usage_1.scheduleOpencodeV2SessionUsageRefresh)(target, port, sessionId, isCurrent);
|
|
416
|
+
}
|
|
417
|
+
catch (error) {
|
|
418
|
+
logger.warn('opencode-v2-usage-trigger-failed', {
|
|
419
|
+
worktreeId: target.worktreeId,
|
|
420
|
+
instanceId: target.instanceId ?? target.cliToolId,
|
|
421
|
+
error: error instanceof Error ? error.message : String(error),
|
|
422
|
+
});
|
|
423
|
+
return null;
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
/**
|
|
427
|
+
* Have `./history` record the reply of a turn this frame ends (Issue #2940).
|
|
428
|
+
*
|
|
429
|
+
* Not awaited: the read loop delivers one frame at a time and a fetch plus a
|
|
430
|
+
* database write must not hold the stream (or the `stop` just delivered).
|
|
431
|
+
* `syncOpencodeV2History` catches its own failures; the guard here is for a
|
|
432
|
+
* frame that cannot even be read.
|
|
433
|
+
*
|
|
434
|
+
* Exported for the tests, which drive frames through the path the stream does.
|
|
435
|
+
*
|
|
436
|
+
* @returns The sync, or null when the frame ends no turn
|
|
437
|
+
*/
|
|
438
|
+
function recordOpencodeV2TurnEnd(target, port, frame) {
|
|
439
|
+
try {
|
|
440
|
+
if (!(0, history_1.isOpencodeV2TurnEndEventType)((0, mappers_1.frameType)(frame)))
|
|
441
|
+
return null;
|
|
442
|
+
const sessionId = (0, mappers_1.frameSessionId)(frame);
|
|
443
|
+
if (sessionId === null)
|
|
444
|
+
return null;
|
|
445
|
+
return (0, history_1.syncOpencodeV2History)(target, port, sessionId);
|
|
446
|
+
}
|
|
447
|
+
catch (error) {
|
|
448
|
+
logger.warn('opencode-v2-history-trigger-failed', {
|
|
449
|
+
worktreeId: target.worktreeId,
|
|
450
|
+
instanceId: target.instanceId ?? target.cliToolId,
|
|
451
|
+
error: error instanceof Error ? error.message : String(error),
|
|
452
|
+
});
|
|
453
|
+
return null;
|
|
454
|
+
}
|
|
455
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* OpenCode V2's {@link CLIToolType}, on its own so the id can be imported
|
|
4
|
+
* without pulling in the source (Issue #2934).
|
|
5
|
+
*
|
|
6
|
+
* The same one-line module `../opencode/tool-id` is. OpenCode V2 (`opencode2`)
|
|
7
|
+
* is a separate tool id rather than a version branch of `opencode` (Epic #2370,
|
|
8
|
+
* decision 1): its server, its event vocabulary and its screen all differ, and
|
|
9
|
+
* v1 keeps every behaviour it had.
|
|
10
|
+
*
|
|
11
|
+
* @module lib/hooks/sources/opencode-v2/tool-id
|
|
12
|
+
*/
|
|
13
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
14
|
+
exports.OPENCODE_V2_COMMAND = exports.OPENCODE_V2_CLI_TOOL_ID = void 0;
|
|
15
|
+
/** The tool id every OpenCode V2 module keys off. */
|
|
16
|
+
exports.OPENCODE_V2_CLI_TOOL_ID = 'opencode-v2';
|
|
17
|
+
/** The executable OpenCode V2 installs (npm `@opencode/cli`). */
|
|
18
|
+
exports.OPENCODE_V2_COMMAND = 'opencode2';
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* What an OpenCode V2 session has spent, and how full its context is
|
|
4
|
+
* (Issue #2981, Epic #2370).
|
|
5
|
+
*
|
|
6
|
+
* v1 publishes both through `lib/hooks/agent-session-telemetry`: the session
|
|
7
|
+
* record off `session.updated`, and the context occupancy from two further
|
|
8
|
+
* requests to v1's server. This module fills the same two records for
|
|
9
|
+
* `opencode-v2`, so every reader (`current-output`, `capture --json`, the pane
|
|
10
|
+
* header, the cost sampler) shows v2 without knowing it is v2.
|
|
11
|
+
*
|
|
12
|
+
* ## What was measured (2.0.18, `tests/fixtures/opencode-v2-usage-2981`)
|
|
13
|
+
*
|
|
14
|
+
* | quantity | where | after turn 1 | after turn 2 |
|
|
15
|
+
* |---|---|---|---|
|
|
16
|
+
* | session, cumulative | `session.usage.updated` `data.tokens` = `GET /api/session/{id}` `tokens` | 2857 / 12 / 64 / 3840 | 5925 / 34 / 128 / 13312 |
|
|
17
|
+
* | context, last turn | newest assistant message's `tokens` (= its `session.step.ended`) | 6,208 | 6,341 |
|
|
18
|
+
* | TUI | footer / sidebar | `6.2K (1%)` / `6,208 tokens` | `6.3K (1%)` / `6,341 tokens` |
|
|
19
|
+
*
|
|
20
|
+
* (input / output / reasoning / cache.read.) The session total also counts the
|
|
21
|
+
* title-generation call (the first `session.usage.updated` of a session, 565
|
|
22
|
+
* tokens, belongs to no assistant message), so the two are different quantities
|
|
23
|
+
* and v1's rule holds unchanged: the record is the cumulative figure, the
|
|
24
|
+
* context is the newest assistant message's sum, and the percentage is
|
|
25
|
+
* `round(tokens / limit.context * 100)` with `limit.context` from
|
|
26
|
+
* `GET /api/model` (1,000,000 for the measured model).
|
|
27
|
+
*
|
|
28
|
+
* ## Pull on the frame, not the frame
|
|
29
|
+
*
|
|
30
|
+
* `session.usage.updated` carries `sessionID`, `cost` and `tokens` and nothing
|
|
31
|
+
* else — no title, agent, model or `parentID`. So the frame is a trigger, as
|
|
32
|
+
* the end of a turn is: both make this module read `GET /api/session/{id}`
|
|
33
|
+
* (the record, and `parentID` so a sub-agent's session is refused exactly as
|
|
34
|
+
* v1 refuses it), the newest messages (the context) and `GET /api/model` (the
|
|
35
|
+
* limit), with the instance's own port and password. Refreshes are coalesced
|
|
36
|
+
* per instance: one in flight, at most one queued behind it.
|
|
37
|
+
*
|
|
38
|
+
* ## Lifetime
|
|
39
|
+
*
|
|
40
|
+
* The subscription drops both records when it closes or ends
|
|
41
|
+
* (`forgetAgentSessionTelemetry`, the call v1 makes in the same place), and a
|
|
42
|
+
* refresh that finishes after that writes nothing ({@link refreshOpencodeV2SessionUsage}'s
|
|
43
|
+
* `isCurrent`).
|
|
44
|
+
*
|
|
45
|
+
* @module lib/hooks/sources/opencode-v2/usage
|
|
46
|
+
*/
|
|
47
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
48
|
+
exports.OPENCODE_V2_USAGE_EVENT_TYPE = void 0;
|
|
49
|
+
exports.isOpencodeV2UsageTriggerType = isOpencodeV2UsageTriggerType;
|
|
50
|
+
exports.readOpencodeV2ContextTokens = readOpencodeV2ContextTokens;
|
|
51
|
+
exports.readOpencodeV2ModelContextLimit = readOpencodeV2ModelContextLimit;
|
|
52
|
+
exports.refreshOpencodeV2SessionUsage = refreshOpencodeV2SessionUsage;
|
|
53
|
+
exports.resetOpencodeV2UsageRefreshes = resetOpencodeV2UsageRefreshes;
|
|
54
|
+
exports.scheduleOpencodeV2SessionUsageRefresh = scheduleOpencodeV2SessionUsageRefresh;
|
|
55
|
+
const agent_session_telemetry_1 = require("../../../../lib/hooks/agent-session-telemetry");
|
|
56
|
+
const logger_1 = require("../../../../lib/logger");
|
|
57
|
+
const event_mapper_1 = require("../event-mapper");
|
|
58
|
+
const client_1 = require("./client");
|
|
59
|
+
const history_1 = require("./history");
|
|
60
|
+
const secrets_1 = require("./secrets");
|
|
61
|
+
const logger = (0, logger_1.createLogger)('lib/hooks/sources/opencode-v2/usage');
|
|
62
|
+
/** The frame that says a session's running total moved. */
|
|
63
|
+
exports.OPENCODE_V2_USAGE_EVENT_TYPE = 'session.usage.updated';
|
|
64
|
+
/** Whether a frame `type` should refresh the usage records. */
|
|
65
|
+
function isOpencodeV2UsageTriggerType(type) {
|
|
66
|
+
return type === exports.OPENCODE_V2_USAGE_EVENT_TYPE || (0, history_1.isOpencodeV2TurnEndEventType)(type);
|
|
67
|
+
}
|
|
68
|
+
function readCount(source, key) {
|
|
69
|
+
const value = source[key];
|
|
70
|
+
return typeof value === 'number' && Number.isFinite(value) && value > 0 ? value : 0;
|
|
71
|
+
}
|
|
72
|
+
function createdAtOf(message) {
|
|
73
|
+
const time = (0, event_mapper_1.isPlainObject)(message.time) ? message.time : null;
|
|
74
|
+
const created = time?.created;
|
|
75
|
+
return typeof created === 'number' && Number.isFinite(created) ? created : 0;
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* The newest assistant message's token footprint, or null.
|
|
79
|
+
*
|
|
80
|
+
* v1's `fetchOpencodeContextTokens` rule on v2's message shape: the newest
|
|
81
|
+
* `assistant` message (by `time.created`, the id breaking ties) that produced
|
|
82
|
+
* output, `input + output + reasoning + cache.read + cache.write`. A message
|
|
83
|
+
* with no output yet is a step still streaming, and would read as a context
|
|
84
|
+
* that shrank. Order-agnostic, pure.
|
|
85
|
+
*/
|
|
86
|
+
function readOpencodeV2ContextTokens(entries) {
|
|
87
|
+
let newest = null;
|
|
88
|
+
for (const entry of entries) {
|
|
89
|
+
if (!(0, event_mapper_1.isPlainObject)(entry) || (0, event_mapper_1.readStringField)(entry, 'type') !== 'assistant')
|
|
90
|
+
continue;
|
|
91
|
+
const tokens = (0, event_mapper_1.isPlainObject)(entry.tokens) ? entry.tokens : null;
|
|
92
|
+
if (!tokens)
|
|
93
|
+
continue;
|
|
94
|
+
const output = readCount(tokens, 'output');
|
|
95
|
+
if (output <= 0)
|
|
96
|
+
continue;
|
|
97
|
+
const cache = (0, event_mapper_1.isPlainObject)(tokens.cache) ? tokens.cache : {};
|
|
98
|
+
const total = readCount(tokens, 'input') +
|
|
99
|
+
output +
|
|
100
|
+
readCount(tokens, 'reasoning') +
|
|
101
|
+
readCount(cache, 'read') +
|
|
102
|
+
readCount(cache, 'write');
|
|
103
|
+
const at = createdAtOf(entry);
|
|
104
|
+
const id = (0, event_mapper_1.readStringField)(entry, 'id') ?? '';
|
|
105
|
+
if (newest === null || at > newest.at || (at === newest.at && id > newest.id)) {
|
|
106
|
+
newest = { at, id, total };
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
return newest?.total ?? null;
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* `limit.context` of one model out of `GET /api/model`'s `data`, or null.
|
|
113
|
+
*
|
|
114
|
+
* `context` only, and a non-positive value is "no percentage" — v1's
|
|
115
|
+
* `readOpencodeModelContextLimit` rule. v2 lists models flat, each naming its
|
|
116
|
+
* `providerID` and `id`.
|
|
117
|
+
*/
|
|
118
|
+
function readOpencodeV2ModelContextLimit(models, providerId, modelId) {
|
|
119
|
+
for (const model of models) {
|
|
120
|
+
if (!(0, event_mapper_1.isPlainObject)(model))
|
|
121
|
+
continue;
|
|
122
|
+
if (model.providerID !== providerId || model.id !== modelId)
|
|
123
|
+
continue;
|
|
124
|
+
const limit = (0, event_mapper_1.isPlainObject)(model.limit) ? model.limit : null;
|
|
125
|
+
const context = limit?.context;
|
|
126
|
+
if (typeof context !== 'number' || !Number.isFinite(context) || context <= 0)
|
|
127
|
+
return null;
|
|
128
|
+
return context;
|
|
129
|
+
}
|
|
130
|
+
return null;
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* Read the session, its context and its model's limit, and store both records.
|
|
134
|
+
*
|
|
135
|
+
* Never throws. Answers the record it stored, or null when it stored nothing:
|
|
136
|
+
* no password (the session was killed), a session the server did not answer, a
|
|
137
|
+
* sub-agent's session, or `isCurrent()` false by the time the answers came in.
|
|
138
|
+
*
|
|
139
|
+
* @param isCurrent - Whether the subscription that asked is still the
|
|
140
|
+
* instance's; checked after the requests, right before writing
|
|
141
|
+
*/
|
|
142
|
+
async function refreshOpencodeV2SessionUsage(target, port, sessionId, isCurrent = () => true) {
|
|
143
|
+
try {
|
|
144
|
+
const password = (0, secrets_1.readOpencodeV2Password)(target);
|
|
145
|
+
if (password === null)
|
|
146
|
+
return null;
|
|
147
|
+
const info = await (0, client_1.fetchOpencodeV2Session)(port, password, sessionId);
|
|
148
|
+
const record = (0, agent_session_telemetry_1.readOpencodeSessionInfo)(info, Date.now());
|
|
149
|
+
if (!record || !record.id)
|
|
150
|
+
return null;
|
|
151
|
+
const provider = record.provider;
|
|
152
|
+
const model = record.model;
|
|
153
|
+
const [messages, models] = await Promise.all([
|
|
154
|
+
(0, client_1.fetchOpencodeV2RecentMessages)(port, password, record.id),
|
|
155
|
+
provider && model ? (0, client_1.fetchOpencodeV2Models)(port, password) : Promise.resolve(null),
|
|
156
|
+
]);
|
|
157
|
+
const tokens = messages ? readOpencodeV2ContextTokens(messages) : null;
|
|
158
|
+
const limit = models && provider && model ? readOpencodeV2ModelContextLimit(models, provider, model) : null;
|
|
159
|
+
if (!isCurrent())
|
|
160
|
+
return null;
|
|
161
|
+
(0, agent_session_telemetry_1.recordAgentSessionTelemetry)(target, record);
|
|
162
|
+
(0, agent_session_telemetry_1.recordAgentSessionContextUsage)(target, {
|
|
163
|
+
tokens,
|
|
164
|
+
limit,
|
|
165
|
+
percent: (0, agent_session_telemetry_1.agentSessionContextPercent)(tokens, limit),
|
|
166
|
+
sessionAt: record.at,
|
|
167
|
+
at: Date.now(),
|
|
168
|
+
});
|
|
169
|
+
return record;
|
|
170
|
+
}
|
|
171
|
+
catch (error) {
|
|
172
|
+
logger.warn('opencode-v2-usage-refresh-failed', {
|
|
173
|
+
worktreeId: target.worktreeId,
|
|
174
|
+
instanceId: target.instanceId ?? target.cliToolId,
|
|
175
|
+
port,
|
|
176
|
+
error: error instanceof Error ? error.message : String(error),
|
|
177
|
+
});
|
|
178
|
+
return null;
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
/** One refresh in flight per instance, on `globalThis` for #1736's reason. */
|
|
182
|
+
const slots = (globalThis.__opencodeV2UsageRefreshes ??= new Map());
|
|
183
|
+
/** Forget in-flight markers. Test seam. */
|
|
184
|
+
function resetOpencodeV2UsageRefreshes() {
|
|
185
|
+
slots.clear();
|
|
186
|
+
}
|
|
187
|
+
/**
|
|
188
|
+
* Refresh the instance's usage, coalescing a burst of frames into at most one
|
|
189
|
+
* request set in flight and one queued behind it (the newest wins the queue).
|
|
190
|
+
*
|
|
191
|
+
* @returns A promise that settles when the instance has no refresh left to run
|
|
192
|
+
*/
|
|
193
|
+
function scheduleOpencodeV2SessionUsageRefresh(target, port, sessionId, isCurrent = () => true) {
|
|
194
|
+
const key = (0, secrets_1.opencodeV2KeyOf)(target);
|
|
195
|
+
const existing = slots.get(key);
|
|
196
|
+
if (existing) {
|
|
197
|
+
existing.queued = { port, sessionId, isCurrent };
|
|
198
|
+
return existing.running;
|
|
199
|
+
}
|
|
200
|
+
const slot = { running: Promise.resolve(), queued: null };
|
|
201
|
+
slot.running = (async () => {
|
|
202
|
+
let next = { port, sessionId, isCurrent };
|
|
203
|
+
while (next !== null) {
|
|
204
|
+
const current = next;
|
|
205
|
+
slot.queued = null;
|
|
206
|
+
await refreshOpencodeV2SessionUsage(target, current.port, current.sessionId, current.isCurrent);
|
|
207
|
+
next = slot.queued;
|
|
208
|
+
}
|
|
209
|
+
if (slots.get(key) === slot)
|
|
210
|
+
slots.delete(key);
|
|
211
|
+
})();
|
|
212
|
+
slots.set(key, slot);
|
|
213
|
+
return slot.running;
|
|
214
|
+
}
|