commandmate 0.22.2 → 0.23.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 +108 -108
- 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/required-server-files.json +1 -1
- package/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
- package/.next/server/app/api/app/update/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/app/update-check/route.js +1 -1
- package/.next/server/app/api/app/update-check/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/assistant/conversation/route.js +1 -1
- package/.next/server/app/api/assistant/conversation/route.js.nft.json +1 -1
- package/.next/server/app/api/assistant/conversation/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/assistant/current-output/route.js +1 -1
- package/.next/server/app/api/assistant/current-output/route.js.nft.json +1 -1
- package/.next/server/app/api/assistant/current-output/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/assistant/messages/route.js +1 -1
- package/.next/server/app/api/assistant/messages/route.js.nft.json +1 -1
- package/.next/server/app/api/assistant/messages/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/assistant/session/route.js +1 -1
- package/.next/server/app/api/assistant/session/route.js.nft.json +1 -1
- package/.next/server/app/api/assistant/session/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/assistant/start/route.js +1 -1
- package/.next/server/app/api/assistant/start/route.js.nft.json +1 -1
- package/.next/server/app/api/assistant/start/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/assistant/terminal/route.js +3 -3
- package/.next/server/app/api/assistant/terminal/route.js.nft.json +1 -1
- package/.next/server/app/api/assistant/terminal/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/assistant/tools/route.js +1 -1
- package/.next/server/app/api/assistant/tools/route.js.nft.json +1 -1
- package/.next/server/app/api/assistant/tools/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/auth/login/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/auth/logout/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/auth/status/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/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.js +1 -1
- package/.next/server/app/api/external-apps/[id]/health/route.js.nft.json +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.js.nft.json +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.js.nft.json +1 -1
- package/.next/server/app/api/external-apps/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/fs/browse/route.js +1 -1
- package/.next/server/app/api/fs/browse/route.js.nft.json +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.js +1 -1
- package/.next/server/app/api/fs/recent-paths/route.js.nft.json +1 -1
- package/.next/server/app/api/fs/recent-paths/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/hooks/agent-event/route.js +1 -1
- package/.next/server/app/api/hooks/agent-event/route.js.nft.json +1 -1
- package/.next/server/app/api/hooks/agent-event/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/hooks/claude-done/route.js +1 -1
- package/.next/server/app/api/hooks/claude-done/route.js.nft.json +1 -1
- package/.next/server/app/api/hooks/claude-done/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/hooks/permission-request/route.js +1 -1
- package/.next/server/app/api/hooks/permission-request/route.js.nft.json +1 -1
- package/.next/server/app/api/hooks/permission-request/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/metrics/vibe/route.js +1 -1
- package/.next/server/app/api/metrics/vibe/route.js.nft.json +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/subscriptions/route.js +1 -1
- package/.next/server/app/api/push/subscriptions/route.js.nft.json +1 -1
- 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.js.nft.json +1 -1
- package/.next/server/app/api/push/vapid/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.js.nft.json +1 -1
- package/.next/server/app/api/repositories/[id]/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/repositories/[id]/todos/[todoId]/route.js +1 -1
- package/.next/server/app/api/repositories/[id]/todos/[todoId]/route.js.nft.json +1 -1
- package/.next/server/app/api/repositories/[id]/todos/[todoId]/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/repositories/[id]/todos/route.js +1 -1
- package/.next/server/app/api/repositories/[id]/todos/route.js.nft.json +1 -1
- package/.next/server/app/api/repositories/[id]/todos/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/repositories/clone/[jobId]/route.js +1 -1
- package/.next/server/app/api/repositories/clone/[jobId]/route.js.nft.json +1 -1
- package/.next/server/app/api/repositories/clone/[jobId]/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/repositories/clone/route.js +1 -1
- package/.next/server/app/api/repositories/clone/route.js.nft.json +1 -1
- package/.next/server/app/api/repositories/clone/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/repositories/excluded/route.js +1 -1
- package/.next/server/app/api/repositories/excluded/route.js.nft.json +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.js +1 -1
- package/.next/server/app/api/repositories/validate-path/route.js.nft.json +1 -1
- package/.next/server/app/api/repositories/validate-path/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.js.nft.json +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_client-reference-manifest.js +1 -1
- package/.next/server/app/api/skills/installations/route.js +1 -1
- package/.next/server/app/api/skills/installations/route.js.nft.json +1 -1
- package/.next/server/app/api/skills/installations/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/skills/operations/route.js +1 -1
- package/.next/server/app/api/skills/operations/route.js.nft.json +1 -1
- package/.next/server/app/api/skills/operations/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/skills/reindex/route.js +1 -1
- package/.next/server/app/api/skills/reindex/route.js.nft.json +1 -1
- package/.next/server/app/api/skills/reindex/route_client-reference-manifest.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.js +1 -4
- package/.next/server/app/api/tasks/[taskId]/cancel/route.js.nft.json +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.js +1 -4
- package/.next/server/app/api/tasks/[taskId]/route.js.nft.json +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 +1 -1
- package/.next/server/app/api/templates/[id]/route.js.nft.json +1 -1
- package/.next/server/app/api/templates/[id]/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/templates/route.js +1 -1
- package/.next/server/app/api/templates/route.js.nft.json +1 -1
- package/.next/server/app/api/templates/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/todos/route.js +1 -1
- package/.next/server/app/api/todos/route.js.nft.json +1 -1
- package/.next/server/app/api/todos/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/verification/runs/[runId]/route.js +1 -1
- package/.next/server/app/api/verification/runs/[runId]/route.js.nft.json +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.js +1 -1
- package/.next/server/app/api/verification/runs/route.js.nft.json +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]/cli-tool/route.js +1 -1
- package/.next/server/app/api/worktrees/[id]/cli-tool/route.js.nft.json +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 +2 -2
- 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]/execution-logs/[logId]/route.js +1 -1
- package/.next/server/app/api/worktrees/[id]/execution-logs/[logId]/route.js.nft.json +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.js.nft.json +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.js.nft.json +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.js.nft.json +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.js +1 -1
- package/.next/server/app/api/worktrees/[id]/git/branches/route.js.nft.json +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.js.nft.json +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.js.nft.json +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.js +1 -1
- package/.next/server/app/api/worktrees/[id]/git/diff/route.js.nft.json +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.js.nft.json +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.js +1 -1
- package/.next/server/app/api/worktrees/[id]/git/log/route.js.nft.json +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.js.nft.json +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.js.nft.json +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.js.nft.json +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.js.nft.json +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.js +1 -1
- package/.next/server/app/api/worktrees/[id]/git/show/[commitHash]/route.js.nft.json +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.js.nft.json +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.js +1 -1
- package/.next/server/app/api/worktrees/[id]/git/staged/route.js.nft.json +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.js +1 -1
- package/.next/server/app/api/worktrees/[id]/git/stash/[index]/route.js.nft.json +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.js.nft.json +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.js.nft.json +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.js.nft.json +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.js +1 -1
- package/.next/server/app/api/worktrees/[id]/git/stash/route.js.nft.json +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.js +1 -1
- package/.next/server/app/api/worktrees/[id]/git/status/route.js.nft.json +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.js.nft.json +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.js +1 -1
- package/.next/server/app/api/worktrees/[id]/git/working-diff/route.js.nft.json +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]/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.js +1 -1
- package/.next/server/app/api/worktrees/[id]/logs/[filename]/route.js.nft.json +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.js +1 -1
- package/.next/server/app/api/worktrees/[id]/logs/route.js.nft.json +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 +3 -3
- package/.next/server/app/api/worktrees/[id]/marp-render/route.js.nft.json +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.js.nft.json +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.js.nft.json +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.js +1 -1
- package/.next/server/app/api/worktrees/[id]/messages/route.js.nft.json +1 -1
- package/.next/server/app/api/worktrees/[id]/messages/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]/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 +1 -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 -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.js +1 -1
- package/.next/server/app/api/worktrees/[id]/search/route.js.nft.json +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 +1 -1
- 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.js +1 -1
- package/.next/server/app/api/worktrees/[id]/skills/[skillId]/install/route.js.nft.json +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.js.nft.json +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.js +1 -1
- package/.next/server/app/api/worktrees/[id]/skills/[skillId]/uninstall/route.js.nft.json +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.js +1 -1
- package/.next/server/app/api/worktrees/[id]/skills/[skillId]/uninstall-plan/route.js.nft.json +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.js +1 -1
- package/.next/server/app/api/worktrees/[id]/skills/[skillId]/update/route.js.nft.json +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.js.nft.json +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.js +1 -1
- package/.next/server/app/api/worktrees/[id]/skills/route.js.nft.json +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]/start-polling/route.js +1 -1
- package/.next/server/app/api/worktrees/[id]/start-polling/route.js.nft.json +1 -1
- package/.next/server/app/api/worktrees/[id]/start-polling/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/tasks/route.js +2 -2
- package/.next/server/app/api/worktrees/[id]/tasks/route.js.nft.json +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.js +1 -1
- package/.next/server/app/api/worktrees/[id]/timers/history/route.js.nft.json +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.js.nft.json +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.js.nft.json +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.js +1 -1
- package/.next/server/app/api/worktrees/[id]/tree/[...path]/route.js.nft.json +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.js +1 -1
- package/.next/server/app/api/worktrees/[id]/tree/route.js.nft.json +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.js +1 -1
- package/.next/server/app/api/worktrees/[id]/upload/[...path]/route.js.nft.json +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/route.js +1 -4
- package/.next/server/app/api/worktrees/[id]/verify/route.js.nft.json +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]/route.js +1 -1
- package/.next/server/app/api/worktrees/[id]/verify/runs/[runId]/route.js.nft.json +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.js +1 -1
- package/.next/server/app/api/worktrees/[id]/verify/runs/route.js.nft.json +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.js +1 -1
- package/.next/server/app/api/worktrees/[id]/viewed/route.js.nft.json +1 -1
- package/.next/server/app/api/worktrees/[id]/viewed/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/route.js +1 -1
- package/.next/server/app/api/worktrees/route.js.nft.json +1 -1
- package/.next/server/app/api/worktrees/route_client-reference-manifest.js +1 -1
- package/.next/server/app/chat/page_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.js +1 -1
- package/.next/server/app/proxy/[...path]/route.js.nft.json +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.js +1 -1
- package/.next/server/app/worktrees/[id]/files/[...path]/page.js.nft.json +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 +2 -2
- 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.js +1 -1
- package/.next/server/app/worktrees/[id]/terminal/page.js.nft.json +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/1029.js +1 -1
- package/.next/server/chunks/1068.js +1 -1
- 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/2546.js +1 -1
- package/.next/server/chunks/3241.js +1 -0
- package/.next/server/chunks/3417.js +1 -1
- package/.next/server/chunks/406.js +10 -6
- package/.next/server/chunks/4444.js +1 -1
- package/.next/server/chunks/5959.js +4 -5
- package/.next/server/chunks/5998.js +1 -12
- package/.next/server/chunks/6130.js +1 -1
- package/.next/server/chunks/6274.js +3 -3
- package/.next/server/chunks/6401.js +1 -1
- package/.next/server/chunks/6781.js +1 -0
- package/.next/server/chunks/7458.js +6 -0
- package/.next/server/chunks/{3753.js → 7970.js} +57 -32
- package/.next/server/chunks/8536.js +1 -1
- package/.next/server/chunks/8807.js +180 -127
- package/.next/server/chunks/9058.js +1 -1
- package/.next/server/chunks/9067.js +1 -1
- package/.next/server/chunks/9360.js +1 -1
- package/.next/server/chunks/9687.js +2 -2
- 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/server/src/middleware.js.map +1 -1
- package/.next/static/chunks/{6488-53a3e1d78e4fe8f7.js → 6488-95ba9d24a76f0cc5.js} +1 -1
- package/.next/static/chunks/app/worktrees/[id]/{page-6dc0b8c4e808ea3e.js → page-966a451ae7b0903a.js} +1 -1
- package/.next/trace +12 -12
- package/dist/server/src/config/log-config.js +10 -1
- package/dist/server/src/config/safe-directory.js +103 -0
- package/dist/server/src/config/system-directories.js +74 -9
- package/dist/server/src/lib/cli-tools/antigravity.js +19 -2
- package/dist/server/src/lib/cli-tools/codex.js +95 -1
- package/dist/server/src/lib/cli-tools/copilot.js +30 -1
- package/dist/server/src/lib/cli-tools/gemini.js +28 -2
- package/dist/server/src/lib/cli-tools/opencode.js +51 -2
- package/dist/server/src/lib/git/git-default-branch.js +59 -0
- package/dist/server/src/lib/hooks/agent-event-service.js +205 -0
- package/dist/server/src/lib/hooks/agent-event-types.js +7 -86
- package/dist/server/src/lib/hooks/hook-settings-generator.js +26 -11
- package/dist/server/src/lib/hooks/permission-decision-service.js +269 -0
- package/dist/server/src/lib/hooks/sources/abstain.js +78 -0
- package/dist/server/src/lib/hooks/sources/antigravity/hooks-config.js +372 -0
- package/dist/server/src/lib/hooks/sources/antigravity/source.js +225 -0
- package/dist/server/src/lib/hooks/sources/antigravity/tool-id.js +16 -0
- package/dist/server/src/lib/hooks/sources/claude/source.js +143 -0
- package/dist/server/src/lib/hooks/sources/claude/tool-id.js +18 -0
- package/dist/server/src/lib/hooks/sources/codex/hooks-config.js +465 -0
- package/dist/server/src/lib/hooks/sources/codex/source.js +138 -0
- package/dist/server/src/lib/hooks/sources/codex/tool-id.js +15 -0
- package/dist/server/src/lib/hooks/sources/copilot/hook-settings.js +473 -0
- package/dist/server/src/lib/hooks/sources/copilot/source.js +210 -0
- package/dist/server/src/lib/hooks/sources/copilot/tool-id.js +15 -0
- package/dist/server/src/lib/hooks/sources/define-source.js +169 -0
- package/dist/server/src/lib/hooks/sources/event-mapper.js +157 -0
- package/dist/server/src/lib/hooks/sources/gemini/event-vocabulary.js +102 -0
- package/dist/server/src/lib/hooks/sources/gemini/settings-generator.js +315 -0
- package/dist/server/src/lib/hooks/sources/gemini/shared-config-tree.js +103 -0
- package/dist/server/src/lib/hooks/sources/gemini/source.js +151 -0
- package/dist/server/src/lib/hooks/sources/gemini/tool-id.js +17 -0
- package/dist/server/src/lib/hooks/sources/hook-event-vocabulary.js +111 -0
- package/dist/server/src/lib/hooks/sources/index.js +62 -0
- package/dist/server/src/lib/hooks/sources/legacy-relay.js +84 -0
- package/dist/server/src/lib/hooks/sources/opencode/client.js +299 -0
- package/dist/server/src/lib/hooks/sources/opencode/ingest.js +261 -0
- package/dist/server/src/lib/hooks/sources/opencode/mappers.js +154 -0
- package/dist/server/src/lib/hooks/sources/opencode/payloads.js +189 -0
- package/dist/server/src/lib/hooks/sources/opencode/ports.js +295 -0
- package/dist/server/src/lib/hooks/sources/opencode/runtime.js +159 -0
- package/dist/server/src/lib/hooks/sources/opencode/source.js +250 -0
- package/dist/server/src/lib/hooks/sources/opencode/subscription.js +422 -0
- package/dist/server/src/lib/hooks/sources/opencode/tool-id.js +14 -0
- package/dist/server/src/lib/hooks/sources/opencode/turn-gate.js +193 -0
- package/dist/server/src/lib/hooks/sources/pending-decisions.js +127 -0
- package/dist/server/src/lib/hooks/sources/registry.js +111 -0
- package/dist/server/src/lib/hooks/sources/types.js +46 -0
- package/dist/server/src/lib/session/agent-session-lifecycle.js +82 -0
- package/dist/server/src/lib/session/claude-session.js +10 -3
- package/dist/server/src/lib/tasks/contract-message.js +175 -0
- package/dist/server/src/lib/verification/env-clean-gate.js +320 -0
- package/dist/server/src/lib/verification/env-snapshot.js +474 -0
- package/dist/server/src/lib/verification/gate-runner.js +802 -0
- package/dist/server/src/lib/verification/scope-gate.js +402 -0
- package/dist/server/src/lib/verification/verify-config.js +281 -0
- package/package.json +1 -1
- package/scripts/hooks/cmate-agent-event.sh +73 -3
- package/.next/server/chunks/8856.js +0 -4
- /package/.next/static/{XpKfn5x6H-mtTaXI9yNmb → wH6K6QNPHSdATFkVoIcy3}/_buildManifest.js +0 -0
- /package/.next/static/{XpKfn5x6H-mtTaXI9yNmb → wH6K6QNPHSdATFkVoIcy3}/_ssgManifest.js +0 -0
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* opencode as an {@link AgentEventSource} — the first pull implementation
|
|
4
|
+
* (Issue #1763, Epic #1720 Phase 4-5).
|
|
5
|
+
*
|
|
6
|
+
* Five tools push into `POST /api/hooks/agent-event`; this one is subscribed
|
|
7
|
+
* to. #1758 established that the difference is real and not cosmetic, and
|
|
8
|
+
* #1759 built the interface so it could be expressed without a special case.
|
|
9
|
+
* What is here is that expression: `definePullEventSource`, the measured
|
|
10
|
+
* values, and nothing shaped like `if (tool === 'opencode')` anywhere above it.
|
|
11
|
+
*
|
|
12
|
+
* ## Why each declared value is what it is
|
|
13
|
+
*
|
|
14
|
+
* - **`noDecision: 'blocks'`** — measured, and the single most consequential
|
|
15
|
+
* line in the file. An approval left unanswered for **10 minutes 19 seconds**
|
|
16
|
+
* was still pending, with no timeout event and no fall-through (#1758 §5.5.3).
|
|
17
|
+
* The TUI dialog and the REST request are not two stages, they are the same
|
|
18
|
+
* pending object seen twice, so "abstaining costs a dialog" — true on Claude,
|
|
19
|
+
* codex and copilot — is false here: abstaining costs the session. Callers
|
|
20
|
+
* read this through `describeAbstain()` and owe the operator a visible signal.
|
|
21
|
+
* - **`configScope: 'none'`** — nothing is written anywhere. No settings file,
|
|
22
|
+
* no hooks entry, no change to `~/.config/opencode/opencode.jsonc`. The whole
|
|
23
|
+
* integration is a `--port` argument and a subscription.
|
|
24
|
+
* - **`decisionTimeoutSeconds: null`** — the honest encoding of "waits forever".
|
|
25
|
+
* Not zero, which would read as "answer instantly or lose the chance", and not
|
|
26
|
+
* a large number, which would read as a deadline that exists.
|
|
27
|
+
* - **`supportedEvents`** — seven words, but two of them mean less than their
|
|
28
|
+
* name suggests. `session_end` fires only for an explicit
|
|
29
|
+
* `DELETE /session/:id`; the TUI's `/exit` emits nothing at all, so process
|
|
30
|
+
* death stays tmux's question (§5.6.4). And `notification` is a bundle of
|
|
31
|
+
* three unrelated events with no `idle_prompt` member — opencode never reports
|
|
32
|
+
* "the agent is sitting at the composer".
|
|
33
|
+
*
|
|
34
|
+
* ## What is deliberately not done
|
|
35
|
+
*
|
|
36
|
+
* `GET /session` is never called to enumerate "this instance's sessions". It
|
|
37
|
+
* returns sessions belonging to *other processes* that share the same `HOME`
|
|
38
|
+
* and project directory, because a session belongs to `opencode.db` rather than
|
|
39
|
+
* to a server (§5.6.3). Instance identity is (worktree, tool, instance) and the
|
|
40
|
+
* port, exactly as it is for every other tool.
|
|
41
|
+
*
|
|
42
|
+
* @module lib/hooks/sources/opencode/source
|
|
43
|
+
*/
|
|
44
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
45
|
+
exports.opencodeAgentEventSource = void 0;
|
|
46
|
+
exports.toOpencodePermissionReply = toOpencodePermissionReply;
|
|
47
|
+
exports.prepareOpencodeLaunch = prepareOpencodeLaunch;
|
|
48
|
+
const agent_event_types_1 = require("../../../../lib/hooks/agent-event-types");
|
|
49
|
+
const hook_settings_generator_1 = require("../../../../lib/hooks/hook-settings-generator");
|
|
50
|
+
const logger_1 = require("../../../../lib/logger");
|
|
51
|
+
const define_source_1 = require("../define-source");
|
|
52
|
+
const client_1 = require("./client");
|
|
53
|
+
const mappers_1 = require("./mappers");
|
|
54
|
+
const payloads_1 = require("./payloads");
|
|
55
|
+
const ports_1 = require("./ports");
|
|
56
|
+
const subscription_1 = require("./subscription");
|
|
57
|
+
const tool_id_1 = require("./tool-id");
|
|
58
|
+
const logger = (0, logger_1.createLogger)('lib/hooks/sources/opencode/source');
|
|
59
|
+
/**
|
|
60
|
+
* The `AGENT_EVENT_TYPES` members opencode can produce.
|
|
61
|
+
*
|
|
62
|
+
* All seven, but see the module comment for what two of them actually mean.
|
|
63
|
+
* Spelled from the shared constant so a new word added to the vocabulary is a
|
|
64
|
+
* compile-time decision here rather than a silent omission.
|
|
65
|
+
*/
|
|
66
|
+
const OPENCODE_SUPPORTED_EVENTS = agent_event_types_1.AGENT_EVENT_TYPES;
|
|
67
|
+
/**
|
|
68
|
+
* Map a verdict onto opencode's three-valued reply, or null when it has none.
|
|
69
|
+
*
|
|
70
|
+
* `abstain` is null and that is the point: there is no wire value for "no
|
|
71
|
+
* opinion", because there is no request to answer — the reply is a fresh POST
|
|
72
|
+
* that simply does not happen. What that costs is {@link
|
|
73
|
+
* AgentEventSource.noDecision}, not something this function can express.
|
|
74
|
+
*/
|
|
75
|
+
function toOpencodePermissionReply(verdict) {
|
|
76
|
+
switch (verdict.kind) {
|
|
77
|
+
case 'allowOnce':
|
|
78
|
+
return 'once';
|
|
79
|
+
case 'allowAlways':
|
|
80
|
+
return 'always';
|
|
81
|
+
case 'deny':
|
|
82
|
+
return 'reject';
|
|
83
|
+
case 'answer':
|
|
84
|
+
case 'abstain':
|
|
85
|
+
return null;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Send a verdict over its own connection (C2).
|
|
90
|
+
*
|
|
91
|
+
* The caller — `answerPendingDecision` — does not know this is an HTTP call
|
|
92
|
+
* rather than a response body, and must not.
|
|
93
|
+
*/
|
|
94
|
+
async function decideOpencode(target, decision, verdict) {
|
|
95
|
+
const instanceId = target.instanceId ?? target.cliToolId;
|
|
96
|
+
if (verdict.kind === 'abstain') {
|
|
97
|
+
// Nothing is sent, and on this tool that is an action rather than the
|
|
98
|
+
// absence of one: the agent waits with no timeout. Logged here as well as
|
|
99
|
+
// at the receiver, because this is the layer that knows the decision was
|
|
100
|
+
// addressed to a source whose silence blocks.
|
|
101
|
+
logger.warn('opencode-verdict-abstained', {
|
|
102
|
+
worktreeId: target.worktreeId,
|
|
103
|
+
instanceId,
|
|
104
|
+
decisionId: decision.id,
|
|
105
|
+
consequence: 'the agent waits indefinitely; nothing else will unblock it',
|
|
106
|
+
});
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
const port = (0, ports_1.getAssignedOpencodePort)(target);
|
|
110
|
+
if (port === null) {
|
|
111
|
+
logger.warn('opencode-verdict-undeliverable-no-port', {
|
|
112
|
+
worktreeId: target.worktreeId,
|
|
113
|
+
instanceId,
|
|
114
|
+
decisionId: decision.id,
|
|
115
|
+
});
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
if (decision.kind === 'question') {
|
|
119
|
+
if (verdict.kind !== 'answer') {
|
|
120
|
+
// An approval verdict cannot answer a question — and unlike Claude, where
|
|
121
|
+
// allowing the `AskUserQuestion` permission still leaves the picker up,
|
|
122
|
+
// there is nothing here that would make it harmless.
|
|
123
|
+
logger.info('opencode-question-not-answerable', {
|
|
124
|
+
worktreeId: target.worktreeId,
|
|
125
|
+
instanceId,
|
|
126
|
+
decisionId: decision.id,
|
|
127
|
+
verdict: verdict.kind,
|
|
128
|
+
});
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
const delivered = await (0, client_1.replyOpencodeQuestion)(port, decision.id, verdict.answers);
|
|
132
|
+
logger.info('opencode-question-replied', {
|
|
133
|
+
worktreeId: target.worktreeId,
|
|
134
|
+
instanceId,
|
|
135
|
+
decisionId: decision.id,
|
|
136
|
+
delivered,
|
|
137
|
+
});
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
const reply = toOpencodePermissionReply(verdict);
|
|
141
|
+
if (reply === null)
|
|
142
|
+
return;
|
|
143
|
+
const delivered = await (0, client_1.replyOpencodePermission)(port, decision.id, reply, verdict.kind === 'deny' ? verdict.message : undefined);
|
|
144
|
+
logger.info('opencode-permission-replied', {
|
|
145
|
+
worktreeId: target.worktreeId,
|
|
146
|
+
instanceId,
|
|
147
|
+
decisionId: decision.id,
|
|
148
|
+
reply,
|
|
149
|
+
delivered,
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
/** Re-read what is waiting on a human (C7). */
|
|
153
|
+
async function listOpencodePending(target) {
|
|
154
|
+
const port = (0, ports_1.getAssignedOpencodePort)(target);
|
|
155
|
+
if (port === null)
|
|
156
|
+
return [];
|
|
157
|
+
const askedAt = Date.now();
|
|
158
|
+
const [permissions, questions] = await Promise.all([
|
|
159
|
+
(0, client_1.fetchOpencodePendingPermissions)(port),
|
|
160
|
+
(0, client_1.fetchOpencodePendingQuestions)(port),
|
|
161
|
+
]);
|
|
162
|
+
const pending = [];
|
|
163
|
+
for (const entry of permissions) {
|
|
164
|
+
const decision = (0, payloads_1.toOpencodePendingPermission)(entry, askedAt);
|
|
165
|
+
if (decision)
|
|
166
|
+
pending.push(decision);
|
|
167
|
+
}
|
|
168
|
+
for (const entry of questions) {
|
|
169
|
+
const decision = (0, payloads_1.toOpencodePendingQuestion)(entry, askedAt);
|
|
170
|
+
if (decision)
|
|
171
|
+
pending.push(decision);
|
|
172
|
+
}
|
|
173
|
+
return pending;
|
|
174
|
+
}
|
|
175
|
+
/**
|
|
176
|
+
* Ask whether the conversation is working (C7).
|
|
177
|
+
*
|
|
178
|
+
* Note what `busy` includes: a session blocked on an approval reads `busy`
|
|
179
|
+
* (#1758 §5.3.1), because from the server's point of view the turn has not
|
|
180
|
+
* ended. This answers "is the turn over", never "is a human needed".
|
|
181
|
+
*/
|
|
182
|
+
async function probeOpencodeActivity(target) {
|
|
183
|
+
const port = (0, ports_1.getAssignedOpencodePort)(target);
|
|
184
|
+
if (port === null)
|
|
185
|
+
return null;
|
|
186
|
+
return (0, client_1.fetchOpencodeActivity)(port);
|
|
187
|
+
}
|
|
188
|
+
/**
|
|
189
|
+
* The command that starts opencode for one instance (S3 / S4 / S5).
|
|
190
|
+
*
|
|
191
|
+
* The whole of the launch change #1763 makes. #1758 §5.1.2 measured that the
|
|
192
|
+
* plain TUI serves the same API `opencode serve` does when given `--port`, so
|
|
193
|
+
* there is no second process to start, no pid to track and no orphan to reap —
|
|
194
|
+
* the server's lifetime is the pane's.
|
|
195
|
+
*
|
|
196
|
+
* Falls back to the bare executable — the pre-#1763 command, byte for byte —
|
|
197
|
+
* whenever there is no port to pass: `CM_AGENT_HOOKS_INJECT=0`, an exhausted
|
|
198
|
+
* range, or a caller that did not allocate one. A session that starts without
|
|
199
|
+
* structured events is the status quo; a session that fails to start is not.
|
|
200
|
+
*
|
|
201
|
+
* `--hostname` is passed explicitly even though `127.0.0.1` is the default,
|
|
202
|
+
* because the default is the security property: the server is unauthenticated,
|
|
203
|
+
* and `--mdns` (never passed) would move it to `0.0.0.0` (#1758 §5.8.3).
|
|
204
|
+
*/
|
|
205
|
+
function prepareOpencodeLaunch(target, executablePath) {
|
|
206
|
+
if (!(0, hook_settings_generator_1.isHookInjectionEnabled)()) {
|
|
207
|
+
return { command: executablePath, settingsPath: null };
|
|
208
|
+
}
|
|
209
|
+
const port = (0, ports_1.getAssignedOpencodePort)(target);
|
|
210
|
+
if (port === null) {
|
|
211
|
+
return { command: executablePath, settingsPath: null };
|
|
212
|
+
}
|
|
213
|
+
return {
|
|
214
|
+
command: `${(0, hook_settings_generator_1.shellQuote)(executablePath)} --port ${port} --hostname ${client_1.OPENCODE_SERVER_HOST}`,
|
|
215
|
+
// Nothing is written to disk. `configScope: 'none'` says the same thing to
|
|
216
|
+
// a caller that asks the capabilities instead of the plan.
|
|
217
|
+
settingsPath: null,
|
|
218
|
+
};
|
|
219
|
+
}
|
|
220
|
+
/**
|
|
221
|
+
* opencode as an event source.
|
|
222
|
+
*
|
|
223
|
+
* Registered in `../registry`. Nothing outside this directory imports it by
|
|
224
|
+
* name; callers ask the registry for the tool they are holding.
|
|
225
|
+
*/
|
|
226
|
+
exports.opencodeAgentEventSource = (0, define_source_1.definePullEventSource)({
|
|
227
|
+
cliToolId: tool_id_1.OPENCODE_CLI_TOOL_ID,
|
|
228
|
+
// #1758 §5.5.3. Not a default, not an assumption — a stopwatch.
|
|
229
|
+
noDecision: { kind: 'blocks' },
|
|
230
|
+
capabilities: {
|
|
231
|
+
supportedEvents: OPENCODE_SUPPORTED_EVENTS,
|
|
232
|
+
configScope: 'none',
|
|
233
|
+
decisionTimeoutSeconds: null,
|
|
234
|
+
},
|
|
235
|
+
// C4. Predicates, not a name table: see ./mappers.
|
|
236
|
+
mappers: mappers_1.OPENCODE_MAPPERS,
|
|
237
|
+
// The envelope names the event in `type`; `hook_event_name` does not exist.
|
|
238
|
+
nativeEventNameFields: ['type'],
|
|
239
|
+
// Flat lookup cannot reach `properties.sessionID`; the rules fill it in.
|
|
240
|
+
conversationIdFields: [],
|
|
241
|
+
parsePermissionRequest: payloads_1.parseOpencodePermissionRequest,
|
|
242
|
+
parseQuestion: payloads_1.parseOpencodeQuestion,
|
|
243
|
+
// Self-reference resolves at call time, which is after this const is bound.
|
|
244
|
+
subscribe: (target, onEvent) => (0, subscription_1.openOpencodeSubscription)(target, onEvent, (raw) => exports.opencodeAgentEventSource.normalizeEvent(raw)),
|
|
245
|
+
decide: decideOpencode,
|
|
246
|
+
listPending: listOpencodePending,
|
|
247
|
+
probeActivity: probeOpencodeActivity,
|
|
248
|
+
liveness: (target) => (0, subscription_1.getOpencodeLiveness)(target),
|
|
249
|
+
prepareLaunch: prepareOpencodeLaunch,
|
|
250
|
+
});
|
|
@@ -0,0 +1,422 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Holding the SSE connection to one opencode instance (Issue #1763, constraint
|
|
4
|
+
* C1).
|
|
5
|
+
*
|
|
6
|
+
* Every other tool CommandMate supports pushes: the agent runs a hook, the hook
|
|
7
|
+
* POSTs, and there is nothing to keep alive. opencode is subscribed to, so this
|
|
8
|
+
* module owns the states a push transport does not have — connected, lost,
|
|
9
|
+
* reconnecting — and the failure modes that come with them.
|
|
10
|
+
*
|
|
11
|
+
* ## What it has to get right
|
|
12
|
+
*
|
|
13
|
+
* - **Reconnect, and re-read what was missed.** Durable replay does not work on
|
|
14
|
+
* 1.18.3: `GET /api/session/:id/event?after=<seq>` returns zero bytes (#1758
|
|
15
|
+
* §5.2.2). So a reconnect cannot ask "what did I miss"; it re-reads *state*
|
|
16
|
+
* instead, from `GET /permission` and `GET /question`, which is what makes a
|
|
17
|
+
* dropped approval recoverable rather than lost forever. A missed
|
|
18
|
+
* `session.idle` is not recoverable and is not pretended to be — `wait` falls
|
|
19
|
+
* back to the scraper, which is the pre-#1763 behaviour.
|
|
20
|
+
* - **Not report a turn twice.** See `./turn-gate`.
|
|
21
|
+
* - **Not throw on an unknown frame.** `server.heartbeat` arrives every ten
|
|
22
|
+
* seconds and is not in the server's own OpenAPI `Event` union (#1758 D5), so
|
|
23
|
+
* a strict reader would fail six times a minute on a healthy connection. It is
|
|
24
|
+
* counted, not raised.
|
|
25
|
+
* - **Know when the connection is dead.** The heartbeat is the signal: 60 of
|
|
26
|
+
* them over ten minutes, gaps of 10.00-10.03 s (§5.7.1). Silence past
|
|
27
|
+
* {@link OPENCODE_HEARTBEAT_TIMEOUT_MS} means the stream is gone even if the
|
|
28
|
+
* socket has not noticed.
|
|
29
|
+
*
|
|
30
|
+
* ## Held on `globalThis`
|
|
31
|
+
*
|
|
32
|
+
* Not decoration (Issue #1736). Under `next dev` every route handler is bundled
|
|
33
|
+
* separately, so a module-scoped map of live subscriptions would be one map per
|
|
34
|
+
* bundle: the launcher would open a stream the receiver cannot see, closing a
|
|
35
|
+
* session would leave a stream running against a dead pane, and starting the
|
|
36
|
+
* same instance twice would open two streams that both deliver every event.
|
|
37
|
+
* None of that produces an error.
|
|
38
|
+
*
|
|
39
|
+
* @module lib/hooks/sources/opencode/subscription
|
|
40
|
+
*/
|
|
41
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
42
|
+
exports.OPENCODE_RECONNECT_BACKOFF_MS = exports.OPENCODE_HEARTBEAT_TIMEOUT_MS = void 0;
|
|
43
|
+
exports.isOpencodeSubscribed = isOpencodeSubscribed;
|
|
44
|
+
exports.getOpencodeLiveness = getOpencodeLiveness;
|
|
45
|
+
exports.openOpencodeSubscription = openOpencodeSubscription;
|
|
46
|
+
exports.closeOpencodeSubscription = closeOpencodeSubscription;
|
|
47
|
+
exports.resetOpencodeSubscriptions = resetOpencodeSubscriptions;
|
|
48
|
+
const logger_1 = require("../../../../lib/logger");
|
|
49
|
+
const auto_yes_state_1 = require("../../../../lib/auto-yes-state");
|
|
50
|
+
const event_mapper_1 = require("../event-mapper");
|
|
51
|
+
const client_1 = require("./client");
|
|
52
|
+
const mappers_1 = require("./mappers");
|
|
53
|
+
const payloads_1 = require("./payloads");
|
|
54
|
+
const ports_1 = require("./ports");
|
|
55
|
+
const turn_gate_1 = require("./turn-gate");
|
|
56
|
+
const logger = (0, logger_1.createLogger)('lib/hooks/sources/opencode/subscription');
|
|
57
|
+
/**
|
|
58
|
+
* How long a connection may be silent before it counts as dead.
|
|
59
|
+
*
|
|
60
|
+
* The heartbeat is a metronome at 10 s (#1758 §5.7.1), so three missed beats is
|
|
61
|
+
* unambiguous while leaving room for a stalled event loop. Shorter would churn
|
|
62
|
+
* connections on a busy server; longer would leave `liveness` claiming `live`
|
|
63
|
+
* for a stream that stopped.
|
|
64
|
+
*/
|
|
65
|
+
exports.OPENCODE_HEARTBEAT_TIMEOUT_MS = 30_000;
|
|
66
|
+
/** Reconnect backoff, in ms. The last value repeats. */
|
|
67
|
+
exports.OPENCODE_RECONNECT_BACKOFF_MS = [
|
|
68
|
+
1_000, 2_000, 4_000, 8_000, 15_000, 30_000,
|
|
69
|
+
];
|
|
70
|
+
/** Cap on remembered decision ids, so a long session cannot grow the set. */
|
|
71
|
+
const MAX_SEEN_DECISIONS = 256;
|
|
72
|
+
const subscriptions = (globalThis.__opencodeSubscriptions ??= new Map());
|
|
73
|
+
function keyOf(target) {
|
|
74
|
+
return (0, auto_yes_state_1.buildCompositeKey)(target.worktreeId, target.cliToolId, target.instanceId);
|
|
75
|
+
}
|
|
76
|
+
/** Whether a stream is currently held for this instance. */
|
|
77
|
+
function isOpencodeSubscribed(target) {
|
|
78
|
+
return subscriptions.has(keyOf(target));
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* How the connection to this instance is doing (C6).
|
|
82
|
+
*
|
|
83
|
+
* `unknown` for an instance with no subscription, which is the honest answer:
|
|
84
|
+
* a session started before this feature, or launched with structured events off,
|
|
85
|
+
* is indistinguishable from one whose stream has not been opened.
|
|
86
|
+
*/
|
|
87
|
+
function getOpencodeLiveness(target) {
|
|
88
|
+
return subscriptions.get(keyOf(target))?.liveness ?? { state: 'unknown' };
|
|
89
|
+
}
|
|
90
|
+
/** A subscription handle for an instance that has no server to subscribe to. */
|
|
91
|
+
function inertSubscription() {
|
|
92
|
+
return { close: async () => { }, liveness: { state: 'unknown' } };
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Open — or re-use — the event stream for one instance.
|
|
96
|
+
*
|
|
97
|
+
* Resolves as soon as the stream loop is running, not when the first frame
|
|
98
|
+
* arrives: the caller is a `startSession` that must not be held up by a server
|
|
99
|
+
* that is still booting, and the loop's own reconnect handles a port that is
|
|
100
|
+
* not listening yet.
|
|
101
|
+
*
|
|
102
|
+
* Never throws. A source that cannot reach its agent degrades to the scraper,
|
|
103
|
+
* and this call sits on the session-start path.
|
|
104
|
+
*
|
|
105
|
+
* @param target - The instance to watch
|
|
106
|
+
* @param onEvent - Receives normalised events, gated by {@link TurnGate}
|
|
107
|
+
* @param normalize - The source's own `normalizeEvent`, passed in rather than
|
|
108
|
+
* imported so this module does not depend on the module that depends on it
|
|
109
|
+
* @param port - Overrides the recorded assignment. For tests
|
|
110
|
+
*/
|
|
111
|
+
async function openOpencodeSubscription(target, onEvent, normalize, port) {
|
|
112
|
+
const key = keyOf(target);
|
|
113
|
+
const existing = subscriptions.get(key);
|
|
114
|
+
if (existing)
|
|
115
|
+
return handleFor(existing);
|
|
116
|
+
const resolvedPort = port ?? (0, ports_1.getAssignedOpencodePort)(target);
|
|
117
|
+
if (resolvedPort === null) {
|
|
118
|
+
// No port was assigned: structured events are off, allocation failed, or
|
|
119
|
+
// this pane predates the feature. All three mean "the scraper is in charge",
|
|
120
|
+
// which is exactly what an inert subscription expresses.
|
|
121
|
+
logger.info('opencode-subscription-skipped-no-port', {
|
|
122
|
+
worktreeId: target.worktreeId,
|
|
123
|
+
instanceId: target.instanceId ?? target.cliToolId,
|
|
124
|
+
});
|
|
125
|
+
return inertSubscription();
|
|
126
|
+
}
|
|
127
|
+
const state = {
|
|
128
|
+
key,
|
|
129
|
+
target,
|
|
130
|
+
port: resolvedPort,
|
|
131
|
+
onEvent,
|
|
132
|
+
normalize,
|
|
133
|
+
gate: (0, turn_gate_1.createTurnGate)(),
|
|
134
|
+
seenDecisions: new Set(),
|
|
135
|
+
streamController: new AbortController(),
|
|
136
|
+
liveness: { state: 'unknown' },
|
|
137
|
+
closed: false,
|
|
138
|
+
watchdog: null,
|
|
139
|
+
};
|
|
140
|
+
subscriptions.set(key, state);
|
|
141
|
+
logger.info('opencode-subscription-opened', {
|
|
142
|
+
worktreeId: target.worktreeId,
|
|
143
|
+
instanceId: target.instanceId ?? target.cliToolId,
|
|
144
|
+
port: resolvedPort,
|
|
145
|
+
});
|
|
146
|
+
// Deliberately not awaited: the loop runs for the life of the session.
|
|
147
|
+
void runStream(state);
|
|
148
|
+
return handleFor(state);
|
|
149
|
+
}
|
|
150
|
+
/** The handle the caller holds. `liveness` reads through to the live state. */
|
|
151
|
+
function handleFor(state) {
|
|
152
|
+
return {
|
|
153
|
+
close: async () => {
|
|
154
|
+
await closeOpencodeSubscription(state.target);
|
|
155
|
+
},
|
|
156
|
+
get liveness() {
|
|
157
|
+
return state.liveness;
|
|
158
|
+
},
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
/** Stop watching an instance. Idempotent. */
|
|
162
|
+
async function closeOpencodeSubscription(target) {
|
|
163
|
+
const key = keyOf(target);
|
|
164
|
+
const state = subscriptions.get(key);
|
|
165
|
+
if (!state)
|
|
166
|
+
return;
|
|
167
|
+
state.closed = true;
|
|
168
|
+
clearWatchdog(state);
|
|
169
|
+
state.streamController.abort();
|
|
170
|
+
subscriptions.delete(key);
|
|
171
|
+
logger.info('opencode-subscription-closed', {
|
|
172
|
+
worktreeId: target.worktreeId,
|
|
173
|
+
instanceId: target.instanceId ?? target.cliToolId,
|
|
174
|
+
port: state.port,
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
/** Close every subscription without waiting. Test seam. */
|
|
178
|
+
function resetOpencodeSubscriptions() {
|
|
179
|
+
for (const state of subscriptions.values()) {
|
|
180
|
+
state.closed = true;
|
|
181
|
+
clearWatchdog(state);
|
|
182
|
+
state.streamController.abort();
|
|
183
|
+
}
|
|
184
|
+
subscriptions.clear();
|
|
185
|
+
}
|
|
186
|
+
function clearWatchdog(state) {
|
|
187
|
+
if (state.watchdog !== null) {
|
|
188
|
+
clearTimeout(state.watchdog);
|
|
189
|
+
state.watchdog = null;
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
/**
|
|
193
|
+
* Restart the silence timer.
|
|
194
|
+
*
|
|
195
|
+
* `unref` so a held connection never keeps a CLI process alive; the server has
|
|
196
|
+
* its own reasons to stay up and does not need this timer to supply one.
|
|
197
|
+
*/
|
|
198
|
+
function armWatchdog(state) {
|
|
199
|
+
clearWatchdog(state);
|
|
200
|
+
const timer = setTimeout(() => {
|
|
201
|
+
if (state.closed)
|
|
202
|
+
return;
|
|
203
|
+
logger.warn('opencode-subscription-heartbeat-lost', {
|
|
204
|
+
worktreeId: state.target.worktreeId,
|
|
205
|
+
instanceId: state.target.instanceId ?? state.target.cliToolId,
|
|
206
|
+
port: state.port,
|
|
207
|
+
afterMs: exports.OPENCODE_HEARTBEAT_TIMEOUT_MS,
|
|
208
|
+
});
|
|
209
|
+
// Force the read to end; the loop treats it as a dropped connection.
|
|
210
|
+
state.streamController.abort();
|
|
211
|
+
}, exports.OPENCODE_HEARTBEAT_TIMEOUT_MS);
|
|
212
|
+
timer.unref?.();
|
|
213
|
+
state.watchdog = timer;
|
|
214
|
+
}
|
|
215
|
+
/** Sleep, unless the subscription is torn down first. */
|
|
216
|
+
function delay(ms, signal) {
|
|
217
|
+
return new Promise((resolve) => {
|
|
218
|
+
const timer = setTimeout(resolve, ms);
|
|
219
|
+
timer.unref?.();
|
|
220
|
+
signal.addEventListener('abort', () => {
|
|
221
|
+
clearTimeout(timer);
|
|
222
|
+
resolve();
|
|
223
|
+
}, { once: true });
|
|
224
|
+
});
|
|
225
|
+
}
|
|
226
|
+
function backoffFor(attempt) {
|
|
227
|
+
const index = Math.min(attempt, exports.OPENCODE_RECONNECT_BACKOFF_MS.length - 1);
|
|
228
|
+
return exports.OPENCODE_RECONNECT_BACKOFF_MS[index];
|
|
229
|
+
}
|
|
230
|
+
/**
|
|
231
|
+
* The connection loop.
|
|
232
|
+
*
|
|
233
|
+
* Runs until `close()`. Every ending — a clean EOF, an aborted read, a refused
|
|
234
|
+
* connection — is the same thing to this function: reconnect after a backoff,
|
|
235
|
+
* having first re-read the state that could not be replayed.
|
|
236
|
+
*/
|
|
237
|
+
async function runStream(state) {
|
|
238
|
+
let attempt = 0;
|
|
239
|
+
while (!state.closed) {
|
|
240
|
+
state.streamController = new AbortController();
|
|
241
|
+
// A reconnect is a new arming window: whatever was mid-turn when the
|
|
242
|
+
// connection dropped, its `session.status(busy)` was on the old stream, and
|
|
243
|
+
// an idle that arrives without one must not resolve a `wait`.
|
|
244
|
+
state.gate.reset();
|
|
245
|
+
let reason = 'stream-ended';
|
|
246
|
+
try {
|
|
247
|
+
await resyncPending(state);
|
|
248
|
+
armWatchdog(state);
|
|
249
|
+
for await (const frame of (0, client_1.readOpencodeEventStream)(state.port, state.streamController.signal)) {
|
|
250
|
+
attempt = 0;
|
|
251
|
+
armWatchdog(state);
|
|
252
|
+
handleFrame(state, frame);
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
catch (error) {
|
|
256
|
+
reason = error instanceof Error ? error.message : String(error);
|
|
257
|
+
}
|
|
258
|
+
finally {
|
|
259
|
+
clearWatchdog(state);
|
|
260
|
+
}
|
|
261
|
+
if (state.closed)
|
|
262
|
+
break;
|
|
263
|
+
state.liveness = { state: 'lost', since: Date.now(), reason };
|
|
264
|
+
logger.warn('opencode-subscription-disconnected', {
|
|
265
|
+
worktreeId: state.target.worktreeId,
|
|
266
|
+
instanceId: state.target.instanceId ?? state.target.cliToolId,
|
|
267
|
+
port: state.port,
|
|
268
|
+
reason,
|
|
269
|
+
attempt,
|
|
270
|
+
});
|
|
271
|
+
await delay(backoffFor(attempt), state.streamController.signal);
|
|
272
|
+
attempt += 1;
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
/**
|
|
276
|
+
* Re-read what is waiting on a human and announce anything not seen yet (C7).
|
|
277
|
+
*
|
|
278
|
+
* The only recovery opencode offers. An approval raised while CommandMate was
|
|
279
|
+
* disconnected is still pending on the server, so re-reading it restores both
|
|
280
|
+
* the "a human is blocked" state and Auto-Yes's chance to adjudicate it — which
|
|
281
|
+
* matters more here than anywhere else, because an unanswered opencode approval
|
|
282
|
+
* waits forever (#1758 §5.5.3).
|
|
283
|
+
*/
|
|
284
|
+
async function resyncPending(state) {
|
|
285
|
+
const now = Date.now();
|
|
286
|
+
const [permissions, questions] = await Promise.all([
|
|
287
|
+
(0, client_1.fetchOpencodePendingPermissions)(state.port),
|
|
288
|
+
(0, client_1.fetchOpencodePendingQuestions)(state.port),
|
|
289
|
+
]);
|
|
290
|
+
const replay = (entries, type) => {
|
|
291
|
+
for (const entry of entries) {
|
|
292
|
+
const id = (0, event_mapper_1.readStringField)(entry, 'id');
|
|
293
|
+
if (!id || state.seenDecisions.has(id))
|
|
294
|
+
continue;
|
|
295
|
+
// Rebuilt into the envelope the live path uses, so one mapper and one
|
|
296
|
+
// parser cover both arrival routes.
|
|
297
|
+
deliver(state, { id: `resync_${id}`, type, properties: entry }, now);
|
|
298
|
+
}
|
|
299
|
+
};
|
|
300
|
+
replay(permissions, 'permission.asked');
|
|
301
|
+
replay(questions, 'question.asked');
|
|
302
|
+
}
|
|
303
|
+
/** Process one frame off the stream. */
|
|
304
|
+
function handleFrame(state, frame) {
|
|
305
|
+
const type = (0, event_mapper_1.readStringField)(frame, 'type');
|
|
306
|
+
if (type === 'server.heartbeat' || type === 'server.connected') {
|
|
307
|
+
state.liveness = { state: 'live', lastHeartbeatAt: Date.now() };
|
|
308
|
+
// Still normalised below so the unknown-event tally counts it: the tally is
|
|
309
|
+
// the diagnostic for "this tool sends words we have none for", and a
|
|
310
|
+
// keepalive silently excluded from it would make the count look healthier
|
|
311
|
+
// than the vocabulary is.
|
|
312
|
+
}
|
|
313
|
+
deliver(state, frame, Date.now());
|
|
314
|
+
}
|
|
315
|
+
/**
|
|
316
|
+
* Normalise one frame and hand it on, if the gate allows.
|
|
317
|
+
*
|
|
318
|
+
* @param frame - `{ id, type, properties }`, live or rebuilt from a re-sync
|
|
319
|
+
* @param receivedAt - Epoch ms
|
|
320
|
+
*/
|
|
321
|
+
function deliver(state, frame, receivedAt) {
|
|
322
|
+
const type = (0, event_mapper_1.readStringField)(frame, 'type');
|
|
323
|
+
// Recorded before the mapping, and for every status: `permission.asked` names
|
|
324
|
+
// no tool, only a `callID`, and the frame that does name it is the `pending`
|
|
325
|
+
// one — which maps to nothing at all. Reading it only from mapped events
|
|
326
|
+
// would leave every approval anonymous (see ./payloads).
|
|
327
|
+
if (type === 'message.part.updated') {
|
|
328
|
+
const callId = (0, mappers_1.partCallId)(frame);
|
|
329
|
+
const toolName = (0, mappers_1.partToolName)(frame);
|
|
330
|
+
if (callId && toolName)
|
|
331
|
+
(0, payloads_1.rememberOpencodeToolCall)(callId, toolName);
|
|
332
|
+
}
|
|
333
|
+
const observation = state.gate.observe(type, frame);
|
|
334
|
+
if (observation.kind === 'failed') {
|
|
335
|
+
logger.warn('opencode-session-error', {
|
|
336
|
+
worktreeId: state.target.worktreeId,
|
|
337
|
+
instanceId: state.target.instanceId ?? state.target.cliToolId,
|
|
338
|
+
sessionId: observation.sessionId,
|
|
339
|
+
errorName: observation.errorName,
|
|
340
|
+
});
|
|
341
|
+
}
|
|
342
|
+
let normalized = null;
|
|
343
|
+
try {
|
|
344
|
+
normalized = state.normalize({ payload: frame, receivedAt });
|
|
345
|
+
}
|
|
346
|
+
catch (error) {
|
|
347
|
+
// The interface forbids this, and a source that broke the rule must still
|
|
348
|
+
// not take the connection down with it.
|
|
349
|
+
logger.error('opencode-normalize-threw', {
|
|
350
|
+
type,
|
|
351
|
+
error: error instanceof Error ? error.message : String(error),
|
|
352
|
+
});
|
|
353
|
+
return;
|
|
354
|
+
}
|
|
355
|
+
if (!normalized)
|
|
356
|
+
return;
|
|
357
|
+
const suppressedBecause = gateVerdict(normalized.event, observation);
|
|
358
|
+
if (suppressedBecause !== null) {
|
|
359
|
+
logger.info('opencode-repeat-suppressed', {
|
|
360
|
+
worktreeId: state.target.worktreeId,
|
|
361
|
+
instanceId: state.target.instanceId ?? state.target.cliToolId,
|
|
362
|
+
sessionId: normalized.conversationId,
|
|
363
|
+
event: normalized.event,
|
|
364
|
+
reason: suppressedBecause,
|
|
365
|
+
});
|
|
366
|
+
return;
|
|
367
|
+
}
|
|
368
|
+
rememberDecisionId(state, frame);
|
|
369
|
+
try {
|
|
370
|
+
state.onEvent(normalized);
|
|
371
|
+
}
|
|
372
|
+
catch (error) {
|
|
373
|
+
logger.error('opencode-event-handler-failed', {
|
|
374
|
+
type,
|
|
375
|
+
error: error instanceof Error ? error.message : String(error),
|
|
376
|
+
});
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
/**
|
|
380
|
+
* Whether the gate refuses to let this event through, and why.
|
|
381
|
+
*
|
|
382
|
+
* Both words opencode repeats are gated, and both would be wrong in the same
|
|
383
|
+
* direction — the status layer reads only the newest event, so a repeat of
|
|
384
|
+
* either overwrites the truth:
|
|
385
|
+
*
|
|
386
|
+
* - a second `stop` resolves a `wait` for work that is not over;
|
|
387
|
+
* - a trailing `user_prompt_submit` — which opencode emits *after*
|
|
388
|
+
* `session.idle` — leaves a finished turn reading `running` for the next
|
|
389
|
+
* thirty minutes.
|
|
390
|
+
*
|
|
391
|
+
* Everything else passes: repeats of `pre_tool_use` and `post_tool_use` are
|
|
392
|
+
* idempotent in the state machine they feed, and the receiver's own dedup
|
|
393
|
+
* window collapses them anyway.
|
|
394
|
+
*
|
|
395
|
+
* @returns The suppression reason, or null when the event may be delivered
|
|
396
|
+
*/
|
|
397
|
+
function gateVerdict(event, observation) {
|
|
398
|
+
if (event === 'stop' && observation.kind !== 'completed') {
|
|
399
|
+
return observation.kind === 'suppressed' ? observation.reason : 'not-a-turn-end';
|
|
400
|
+
}
|
|
401
|
+
if (event === 'user_prompt_submit' && observation.kind !== 'announced') {
|
|
402
|
+
return observation.kind === 'suppressed' ? observation.reason : 'not-a-new-prompt';
|
|
403
|
+
}
|
|
404
|
+
return null;
|
|
405
|
+
}
|
|
406
|
+
/** Note an approval / question id so a later re-sync does not repeat it. */
|
|
407
|
+
function rememberDecisionId(state, frame) {
|
|
408
|
+
const type = (0, event_mapper_1.readStringField)(frame, 'type');
|
|
409
|
+
if (type !== 'permission.asked' && type !== 'question.asked')
|
|
410
|
+
return;
|
|
411
|
+
const properties = (0, event_mapper_1.isPlainObject)(frame.properties) ? frame.properties : {};
|
|
412
|
+
const id = (0, event_mapper_1.readStringField)(properties, 'id');
|
|
413
|
+
if (!id)
|
|
414
|
+
return;
|
|
415
|
+
state.seenDecisions.add(id);
|
|
416
|
+
while (state.seenDecisions.size > MAX_SEEN_DECISIONS) {
|
|
417
|
+
const oldest = state.seenDecisions.values().next();
|
|
418
|
+
if (oldest.done)
|
|
419
|
+
break;
|
|
420
|
+
state.seenDecisions.delete(oldest.value);
|
|
421
|
+
}
|
|
422
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* opencode's {@link CLIToolType}, on its own so the id can be imported without
|
|
4
|
+
* pulling in the source (Issue #1763).
|
|
5
|
+
*
|
|
6
|
+
* Same shape as `../claude/tool-id`: the registry, the launcher and the tests
|
|
7
|
+
* all need the literal, and only the registry needs the implementation.
|
|
8
|
+
*
|
|
9
|
+
* @module lib/hooks/sources/opencode/tool-id
|
|
10
|
+
*/
|
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
exports.OPENCODE_CLI_TOOL_ID = void 0;
|
|
13
|
+
/** The tool id every opencode module keys off. */
|
|
14
|
+
exports.OPENCODE_CLI_TOOL_ID = 'opencode';
|