@cluesmith/codev 3.2.4 → 3.3.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/dashboard-dist/assets/index-BZRTEMtH.css +32 -0
- package/dashboard-dist/assets/{index-BZ1x7x1X.js → index-JCbkpAcV.js} +33 -33
- package/dashboard-dist/assets/index-JCbkpAcV.js.map +1 -0
- package/dashboard-dist/index.html +2 -2
- package/dist/agent-farm/cli.d.ts.map +1 -1
- package/dist/agent-farm/cli.js +142 -0
- package/dist/agent-farm/cli.js.map +1 -1
- package/dist/agent-farm/commands/cleanup.d.ts.map +1 -1
- package/dist/agent-farm/commands/cleanup.js +16 -0
- package/dist/agent-farm/commands/cleanup.js.map +1 -1
- package/dist/agent-farm/commands/inbox.d.ts +42 -0
- package/dist/agent-farm/commands/inbox.d.ts.map +1 -0
- package/dist/agent-farm/commands/inbox.js +129 -0
- package/dist/agent-farm/commands/inbox.js.map +1 -0
- package/dist/agent-farm/commands/interrupt.d.ts +20 -0
- package/dist/agent-farm/commands/interrupt.d.ts.map +1 -0
- package/dist/agent-farm/commands/interrupt.js +65 -0
- package/dist/agent-farm/commands/interrupt.js.map +1 -0
- package/dist/agent-farm/commands/reset/constants.d.ts +52 -0
- package/dist/agent-farm/commands/reset/constants.d.ts.map +1 -0
- package/dist/agent-farm/commands/reset/constants.js +52 -0
- package/dist/agent-farm/commands/reset/constants.js.map +1 -0
- package/dist/agent-farm/commands/reset/context.d.ts +230 -0
- package/dist/agent-farm/commands/reset/context.d.ts.map +1 -0
- package/dist/agent-farm/commands/reset/context.js +428 -0
- package/dist/agent-farm/commands/reset/context.js.map +1 -0
- package/dist/agent-farm/commands/reset/index.d.ts +192 -0
- package/dist/agent-farm/commands/reset/index.d.ts.map +1 -0
- package/dist/agent-farm/commands/reset/index.js +488 -0
- package/dist/agent-farm/commands/reset/index.js.map +1 -0
- package/dist/agent-farm/commands/reset/receipt.d.ts +97 -0
- package/dist/agent-farm/commands/reset/receipt.d.ts.map +1 -0
- package/dist/agent-farm/commands/reset/receipt.js +149 -0
- package/dist/agent-farm/commands/reset/receipt.js.map +1 -0
- package/dist/agent-farm/commands/reset/reorient.d.ts +117 -0
- package/dist/agent-farm/commands/reset/reorient.d.ts.map +1 -0
- package/dist/agent-farm/commands/reset/reorient.js +313 -0
- package/dist/agent-farm/commands/reset/reorient.js.map +1 -0
- package/dist/agent-farm/commands/reset.d.ts +19 -0
- package/dist/agent-farm/commands/reset.d.ts.map +1 -0
- package/dist/agent-farm/commands/reset.js +292 -0
- package/dist/agent-farm/commands/reset.js.map +1 -0
- package/dist/agent-farm/commands/send.d.ts.map +1 -1
- package/dist/agent-farm/commands/send.js +49 -8
- package/dist/agent-farm/commands/send.js.map +1 -1
- package/dist/agent-farm/commands/spawn-worktree.d.ts +105 -6
- package/dist/agent-farm/commands/spawn-worktree.d.ts.map +1 -1
- package/dist/agent-farm/commands/spawn-worktree.js +289 -66
- package/dist/agent-farm/commands/spawn-worktree.js.map +1 -1
- package/dist/agent-farm/commands/spawn.d.ts.map +1 -1
- package/dist/agent-farm/commands/spawn.js +13 -1
- package/dist/agent-farm/commands/spawn.js.map +1 -1
- package/dist/agent-farm/commands/status.d.ts.map +1 -1
- package/dist/agent-farm/commands/status.js +80 -8
- package/dist/agent-farm/commands/status.js.map +1 -1
- package/dist/agent-farm/db/index.d.ts +1 -1
- package/dist/agent-farm/db/index.d.ts.map +1 -1
- package/dist/agent-farm/db/index.js +80 -1
- package/dist/agent-farm/db/index.js.map +1 -1
- package/dist/agent-farm/db/mailbox.d.ts +222 -0
- package/dist/agent-farm/db/mailbox.d.ts.map +1 -0
- package/dist/agent-farm/db/mailbox.js +304 -0
- package/dist/agent-farm/db/mailbox.js.map +1 -0
- package/dist/agent-farm/db/schema.d.ts +1 -1
- package/dist/agent-farm/db/schema.d.ts.map +1 -1
- package/dist/agent-farm/db/schema.js +35 -0
- package/dist/agent-farm/db/schema.js.map +1 -1
- package/dist/agent-farm/db/types.d.ts +45 -0
- package/dist/agent-farm/db/types.d.ts.map +1 -1
- package/dist/agent-farm/db/types.js.map +1 -1
- package/dist/agent-farm/lib/builder-lookup.d.ts +1 -1
- package/dist/agent-farm/lib/builder-lookup.js +2 -2
- package/dist/agent-farm/lib/builder-lookup.js.map +1 -1
- package/dist/agent-farm/lib/reconnect-backoff.d.ts +54 -0
- package/dist/agent-farm/lib/reconnect-backoff.d.ts.map +1 -0
- package/dist/agent-farm/lib/reconnect-backoff.js +47 -0
- package/dist/agent-farm/lib/reconnect-backoff.js.map +1 -0
- package/dist/agent-farm/lib/tower-client.d.ts +16 -6
- package/dist/agent-farm/lib/tower-client.d.ts.map +1 -1
- package/dist/agent-farm/lib/tower-client.js +37 -6
- package/dist/agent-farm/lib/tower-client.js.map +1 -1
- package/dist/agent-farm/lib/tunnel-client.d.ts +69 -1
- package/dist/agent-farm/lib/tunnel-client.d.ts.map +1 -1
- package/dist/agent-farm/lib/tunnel-client.js +218 -27
- package/dist/agent-farm/lib/tunnel-client.js.map +1 -1
- package/dist/agent-farm/servers/cron-delivery.d.ts +72 -0
- package/dist/agent-farm/servers/cron-delivery.d.ts.map +1 -0
- package/dist/agent-farm/servers/cron-delivery.js +80 -0
- package/dist/agent-farm/servers/cron-delivery.js.map +1 -0
- package/dist/agent-farm/servers/delayed-send.d.ts +85 -0
- package/dist/agent-farm/servers/delayed-send.d.ts.map +1 -0
- package/dist/agent-farm/servers/delayed-send.js +165 -0
- package/dist/agent-farm/servers/delayed-send.js.map +1 -0
- package/dist/agent-farm/servers/gate-profiles.d.ts +67 -0
- package/dist/agent-farm/servers/gate-profiles.d.ts.map +1 -0
- package/dist/agent-farm/servers/gate-profiles.js +113 -0
- package/dist/agent-farm/servers/gate-profiles.js.map +1 -0
- package/dist/agent-farm/servers/mailbox-delivery.d.ts +370 -0
- package/dist/agent-farm/servers/mailbox-delivery.d.ts.map +1 -0
- package/dist/agent-farm/servers/mailbox-delivery.js +617 -0
- package/dist/agent-farm/servers/mailbox-delivery.js.map +1 -0
- package/dist/agent-farm/servers/mailbox-wiring.d.ts +125 -0
- package/dist/agent-farm/servers/mailbox-wiring.d.ts.map +1 -0
- package/dist/agent-farm/servers/mailbox-wiring.js +425 -0
- package/dist/agent-farm/servers/mailbox-wiring.js.map +1 -0
- package/dist/agent-farm/servers/message-write.d.ts +53 -1
- package/dist/agent-farm/servers/message-write.d.ts.map +1 -1
- package/dist/agent-farm/servers/message-write.js +63 -0
- package/dist/agent-farm/servers/message-write.js.map +1 -1
- package/dist/agent-farm/servers/overview.d.ts.map +1 -1
- package/dist/agent-farm/servers/overview.js +23 -4
- package/dist/agent-farm/servers/overview.js.map +1 -1
- package/dist/agent-farm/servers/render-gate.d.ts +141 -0
- package/dist/agent-farm/servers/render-gate.d.ts.map +1 -0
- package/dist/agent-farm/servers/render-gate.js +251 -0
- package/dist/agent-farm/servers/render-gate.js.map +1 -0
- package/dist/agent-farm/servers/session-submit.d.ts +90 -0
- package/dist/agent-farm/servers/session-submit.d.ts.map +1 -0
- package/dist/agent-farm/servers/session-submit.js +127 -0
- package/dist/agent-farm/servers/session-submit.js.map +1 -0
- package/dist/agent-farm/servers/tower-cron.d.ts +10 -7
- package/dist/agent-farm/servers/tower-cron.d.ts.map +1 -1
- package/dist/agent-farm/servers/tower-cron.js +58 -53
- package/dist/agent-farm/servers/tower-cron.js.map +1 -1
- package/dist/agent-farm/servers/tower-instances.d.ts +14 -2
- package/dist/agent-farm/servers/tower-instances.d.ts.map +1 -1
- package/dist/agent-farm/servers/tower-instances.js +78 -16
- package/dist/agent-farm/servers/tower-instances.js.map +1 -1
- package/dist/agent-farm/servers/tower-messages.d.ts +38 -1
- package/dist/agent-farm/servers/tower-messages.d.ts.map +1 -1
- package/dist/agent-farm/servers/tower-messages.js +106 -1
- package/dist/agent-farm/servers/tower-messages.js.map +1 -1
- package/dist/agent-farm/servers/tower-routes.d.ts +16 -4
- package/dist/agent-farm/servers/tower-routes.d.ts.map +1 -1
- package/dist/agent-farm/servers/tower-routes.js +670 -105
- package/dist/agent-farm/servers/tower-routes.js.map +1 -1
- package/dist/agent-farm/servers/tower-server.js +163 -31
- package/dist/agent-farm/servers/tower-server.js.map +1 -1
- package/dist/agent-farm/servers/tower-terminals.d.ts +17 -2
- package/dist/agent-farm/servers/tower-terminals.d.ts.map +1 -1
- package/dist/agent-farm/servers/tower-terminals.js +104 -122
- package/dist/agent-farm/servers/tower-terminals.js.map +1 -1
- package/dist/agent-farm/servers/tower-tunnel.d.ts.map +1 -1
- package/dist/agent-farm/servers/tower-tunnel.js +63 -4
- package/dist/agent-farm/servers/tower-tunnel.js.map +1 -1
- package/dist/agent-farm/servers/tower-types.d.ts +2 -1
- package/dist/agent-farm/servers/tower-types.d.ts.map +1 -1
- package/dist/agent-farm/servers/tower-utils.d.ts +64 -1
- package/dist/agent-farm/servers/tower-utils.d.ts.map +1 -1
- package/dist/agent-farm/servers/tower-utils.js +168 -1
- package/dist/agent-farm/servers/tower-utils.js.map +1 -1
- package/dist/agent-farm/servers/tower-websocket.d.ts.map +1 -1
- package/dist/agent-farm/servers/tower-websocket.js +6 -21
- package/dist/agent-farm/servers/tower-websocket.js.map +1 -1
- package/dist/agent-farm/servers/write-queue.d.ts +35 -0
- package/dist/agent-farm/servers/write-queue.d.ts.map +1 -0
- package/dist/agent-farm/servers/write-queue.js +56 -0
- package/dist/agent-farm/servers/write-queue.js.map +1 -0
- package/dist/agent-farm/types.d.ts +39 -0
- package/dist/agent-farm/types.d.ts.map +1 -1
- package/dist/agent-farm/utils/agent-names.d.ts +2 -2
- package/dist/agent-farm/utils/agent-names.d.ts.map +1 -1
- package/dist/agent-farm/utils/agent-names.js +3 -3
- package/dist/agent-farm/utils/agent-names.js.map +1 -1
- package/dist/agent-farm/utils/architect-name.d.ts +2 -2
- package/dist/agent-farm/utils/architect-name.d.ts.map +1 -1
- package/dist/agent-farm/utils/architect-name.js +3 -3
- package/dist/agent-farm/utils/architect-name.js.map +1 -1
- package/dist/agent-farm/utils/config.d.ts +13 -0
- package/dist/agent-farm/utils/config.d.ts.map +1 -1
- package/dist/agent-farm/utils/config.js +33 -1
- package/dist/agent-farm/utils/config.js.map +1 -1
- package/dist/agent-farm/utils/harness.d.ts +103 -7
- package/dist/agent-farm/utils/harness.d.ts.map +1 -1
- package/dist/agent-farm/utils/harness.js +126 -19
- package/dist/agent-farm/utils/harness.js.map +1 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +6 -32
- package/dist/cli.js.map +1 -1
- package/dist/commands/consult/cli-options.d.ts +34 -0
- package/dist/commands/consult/cli-options.d.ts.map +1 -0
- package/dist/commands/consult/cli-options.js +67 -0
- package/dist/commands/consult/cli-options.js.map +1 -0
- package/dist/commands/consult/index.d.ts +56 -2
- package/dist/commands/consult/index.d.ts.map +1 -1
- package/dist/commands/consult/index.js +272 -28
- package/dist/commands/consult/index.js.map +1 -1
- package/dist/commands/consult/metrics.d.ts +42 -0
- package/dist/commands/consult/metrics.d.ts.map +1 -1
- package/dist/commands/consult/metrics.js +129 -9
- package/dist/commands/consult/metrics.js.map +1 -1
- package/dist/commands/doctor.d.ts.map +1 -1
- package/dist/commands/doctor.js +71 -19
- package/dist/commands/doctor.js.map +1 -1
- package/dist/commands/porch/checks.d.ts +23 -0
- package/dist/commands/porch/checks.d.ts.map +1 -1
- package/dist/commands/porch/checks.js +60 -0
- package/dist/commands/porch/checks.js.map +1 -1
- package/dist/commands/porch/config.d.ts +20 -0
- package/dist/commands/porch/config.d.ts.map +1 -1
- package/dist/commands/porch/config.js +20 -0
- package/dist/commands/porch/config.js.map +1 -1
- package/dist/commands/porch/index.js +21 -28
- package/dist/commands/porch/index.js.map +1 -1
- package/dist/commands/porch/next.d.ts.map +1 -1
- package/dist/commands/porch/next.js +2 -41
- package/dist/commands/porch/next.js.map +1 -1
- package/dist/lib/config.d.ts +69 -3
- package/dist/lib/config.d.ts.map +1 -1
- package/dist/lib/config.js +67 -3
- package/dist/lib/config.js.map +1 -1
- package/dist/lib/consult-lanes.d.ts +149 -0
- package/dist/lib/consult-lanes.d.ts.map +1 -0
- package/dist/lib/consult-lanes.js +360 -0
- package/dist/lib/consult-lanes.js.map +1 -0
- package/dist/lib/github.js +1 -1
- package/dist/lib/github.js.map +1 -1
- package/dist/lib/prompt-behavior-metrics.d.ts +67 -0
- package/dist/lib/prompt-behavior-metrics.d.ts.map +1 -0
- package/dist/lib/prompt-behavior-metrics.js +290 -0
- package/dist/lib/prompt-behavior-metrics.js.map +1 -0
- package/dist/lib/skeleton.d.ts +22 -1
- package/dist/lib/skeleton.d.ts.map +1 -1
- package/dist/lib/skeleton.js +120 -0
- package/dist/lib/skeleton.js.map +1 -1
- package/dist/lib/test-env.d.ts +74 -0
- package/dist/lib/test-env.d.ts.map +1 -0
- package/dist/lib/test-env.js +92 -0
- package/dist/lib/test-env.js.map +1 -0
- package/dist/terminal/pty-manager.d.ts +2 -0
- package/dist/terminal/pty-manager.d.ts.map +1 -1
- package/dist/terminal/pty-manager.js +16 -6
- package/dist/terminal/pty-manager.js.map +1 -1
- package/dist/terminal/pty-session.d.ts +131 -0
- package/dist/terminal/pty-session.d.ts.map +1 -1
- package/dist/terminal/pty-session.js +232 -39
- package/dist/terminal/pty-session.js.map +1 -1
- package/dist/terminal/ring-buffer.d.ts +39 -6
- package/dist/terminal/ring-buffer.d.ts.map +1 -1
- package/dist/terminal/ring-buffer.js +74 -6
- package/dist/terminal/ring-buffer.js.map +1 -1
- package/dist/terminal/session-manager.d.ts +53 -0
- package/dist/terminal/session-manager.d.ts.map +1 -1
- package/dist/terminal/session-manager.js +94 -9
- package/dist/terminal/session-manager.js.map +1 -1
- package/dist/terminal/session-screen.d.ts +91 -0
- package/dist/terminal/session-screen.d.ts.map +1 -0
- package/dist/terminal/session-screen.js +135 -0
- package/dist/terminal/session-screen.js.map +1 -0
- package/dist/terminal/shellper-main.js +2 -1
- package/dist/terminal/shellper-main.js.map +1 -1
- package/dist/terminal/shellper-process.d.ts +1 -1
- package/dist/terminal/shellper-process.d.ts.map +1 -1
- package/dist/terminal/shellper-process.js +17 -8
- package/dist/terminal/shellper-process.js.map +1 -1
- package/dist/terminal/shellper-replay-buffer.d.ts +69 -7
- package/dist/terminal/shellper-replay-buffer.d.ts.map +1 -1
- package/dist/terminal/shellper-replay-buffer.js +167 -31
- package/dist/terminal/shellper-replay-buffer.js.map +1 -1
- package/package.json +8 -6
- package/skeleton/.claude/skills/afx/SKILL.md +14 -1
- package/skeleton/.claude/skills/arch-init/SKILL.md +22 -9
- package/skeleton/.claude/skills/arch-save/SKILL.md +183 -0
- package/skeleton/.claude/skills/codev/SKILL.md +31 -0
- package/skeleton/.claude/skills/runnable-worktrees/SKILL.md +119 -0
- package/skeleton/.codex/skills/afx/SKILL.md +14 -1
- package/skeleton/.codex/skills/arch-init/SKILL.md +22 -9
- package/skeleton/.codex/skills/arch-save/SKILL.md +183 -0
- package/skeleton/.codex/skills/codev/SKILL.md +31 -0
- package/skeleton/.codex/skills/runnable-worktrees/SKILL.md +119 -0
- package/skeleton/protocols/air/builder-prompt.md +28 -47
- package/skeleton/protocols/air/consult-types/impl-review.md +17 -34
- package/skeleton/protocols/air/consult-types/pr-review.md +14 -37
- package/skeleton/protocols/air/prompts/implement.md +20 -73
- package/skeleton/protocols/air/prompts/pr.md +22 -54
- package/skeleton/protocols/air/protocol.md +40 -71
- package/skeleton/protocols/aspir/builder-prompt.md +29 -66
- package/skeleton/protocols/aspir/consult-types/impl-review.md +17 -44
- package/skeleton/protocols/aspir/consult-types/phase-review.md +17 -44
- package/skeleton/protocols/aspir/consult-types/plan-review.md +14 -38
- package/skeleton/protocols/aspir/consult-types/pr-review.md +13 -39
- package/skeleton/protocols/aspir/consult-types/spec-review.md +13 -33
- package/skeleton/protocols/aspir/prompts/implement.md +27 -196
- package/skeleton/protocols/aspir/prompts/plan.md +21 -85
- package/skeleton/protocols/aspir/prompts/review.md +29 -228
- package/skeleton/protocols/aspir/prompts/specify.md +25 -111
- package/skeleton/protocols/aspir/protocol.json +10 -0
- package/skeleton/protocols/aspir/protocol.md +36 -82
- package/skeleton/protocols/bugfix/builder-prompt.md +23 -45
- package/skeleton/protocols/bugfix/consult-types/impl-review.md +21 -45
- package/skeleton/protocols/bugfix/consult-types/pr-review.md +18 -52
- package/skeleton/protocols/bugfix/prompts/fix.md +16 -61
- package/skeleton/protocols/bugfix/prompts/investigate.md +18 -62
- package/skeleton/protocols/bugfix/prompts/pr.md +22 -57
- package/skeleton/protocols/bugfix/protocol.md +44 -50
- package/skeleton/protocols/experiment/builder-prompt.md +25 -58
- package/skeleton/protocols/experiment/protocol.md +22 -188
- package/skeleton/protocols/experiment/templates/notes.md +11 -71
- package/skeleton/protocols/maintain/builder-prompt.md +24 -45
- package/skeleton/protocols/maintain/consult-types/impl-review.md +17 -44
- package/skeleton/protocols/maintain/consult-types/pr-review.md +13 -39
- package/skeleton/protocols/maintain/prompts/maintain.md +1 -1
- package/skeleton/protocols/maintain/protocol.md +32 -266
- package/skeleton/protocols/maintain/templates/maintenance-run.md +11 -0
- package/skeleton/protocols/pir/builder-prompt.md +30 -64
- package/skeleton/protocols/pir/consult-types/impl-review.md +17 -50
- package/skeleton/protocols/pir/consult-types/pr-review.md +11 -40
- package/skeleton/protocols/pir/prompts/implement.md +1 -2
- package/skeleton/protocols/pir/prompts/plan.md +0 -3
- package/skeleton/protocols/pir/prompts/review.md +1 -1
- package/skeleton/protocols/pir/protocol.md +54 -177
- package/skeleton/protocols/research/builder-prompt.md +27 -58
- package/skeleton/protocols/research/protocol.md +28 -153
- package/skeleton/protocols/spike/builder-prompt.md +30 -48
- package/skeleton/protocols/spike/protocol.md +24 -107
- package/skeleton/protocols/spike/templates/findings.md +9 -39
- package/skeleton/protocols/spir/builder-prompt.md +33 -62
- package/skeleton/protocols/spir/consult-types/impl-review.md +17 -44
- package/skeleton/protocols/spir/consult-types/phase-review.md +17 -44
- package/skeleton/protocols/spir/consult-types/plan-review.md +14 -38
- package/skeleton/protocols/spir/consult-types/pr-review.md +13 -39
- package/skeleton/protocols/spir/consult-types/spec-review.md +13 -33
- package/skeleton/protocols/spir/prompts/implement.md +26 -195
- package/skeleton/protocols/spir/prompts/plan.md +20 -84
- package/skeleton/protocols/spir/prompts/review.md +28 -227
- package/skeleton/protocols/spir/prompts/specify.md +24 -110
- package/skeleton/protocols/spir/protocol.json +10 -0
- package/skeleton/protocols/spir/protocol.md +84 -633
- package/skeleton/protocols/spir/templates/plan.md +28 -147
- package/skeleton/protocols/spir/templates/review.md +21 -80
- package/skeleton/protocols/spir/templates/spec.md +24 -118
- package/skeleton/resources/commands/agent-farm.md +259 -0
- package/skeleton/resources/commands/consult.md +236 -8
- package/skeleton/resources/commands/overview.md +1 -0
- package/skeleton/roles/architect.md +67 -317
- package/skeleton/roles/builder.md +77 -191
- package/skeleton/templates/AGENTS.md +2 -0
- package/skeleton/templates/CLAUDE.md +2 -0
- package/templates/tower.html +39 -2
- package/dashboard-dist/assets/index-BZ1x7x1X.js.map +0 -1
- package/dashboard-dist/assets/index-DB1KBSNE.css +0 -32
- package/dist/agent-farm/servers/send-buffer.d.ts +0 -59
- package/dist/agent-farm/servers/send-buffer.d.ts.map +0 -1
- package/dist/agent-farm/servers/send-buffer.js +0 -116
- package/dist/agent-farm/servers/send-buffer.js.map +0 -1
- package/skeleton/porch/prompts/defend.md +0 -108
- package/skeleton/porch/prompts/diagnose.md +0 -70
- package/skeleton/porch/prompts/evaluate.md +0 -132
- package/skeleton/porch/prompts/fix.md +0 -59
- package/skeleton/porch/prompts/implement.md +0 -88
- package/skeleton/porch/prompts/plan.md +0 -74
- package/skeleton/porch/prompts/pr.md +0 -84
- package/skeleton/porch/prompts/review.md +0 -249
- package/skeleton/porch/prompts/specify.md +0 -53
- package/skeleton/porch/prompts/test.md +0 -63
- package/skeleton/protocols/aspir/templates/plan.md +0 -184
- package/skeleton/protocols/aspir/templates/review.md +0 -120
- package/skeleton/protocols/aspir/templates/spec.md +0 -154
|
@@ -15,12 +15,14 @@ import { query as claudeQuery } from '@anthropic-ai/claude-agent-sdk';
|
|
|
15
15
|
import { Codex } from '@openai/codex-sdk';
|
|
16
16
|
import { readCodevFile, findWorkspaceRoot } from '../../lib/skeleton.js';
|
|
17
17
|
import { resolveDefaultBranch } from '../../lib/default-branch.js';
|
|
18
|
-
import { loadConfig } from '../../lib/config.js';
|
|
18
|
+
import { loadConfig, findConfigSource } from '../../lib/config.js';
|
|
19
|
+
import { resolveLaneModel, resolveReasoningEffort, validateModelId, assertLaneAcceptsModelOverride, } from '../../lib/consult-lanes.js';
|
|
19
20
|
import { getResolver, GitRefResolver } from '../porch/artifacts.js';
|
|
20
21
|
import { MetricsDB } from './metrics.js';
|
|
21
22
|
import { extractUsage, extractReviewText } from './usage-extractor.js';
|
|
22
23
|
import { executeForgeCommandSync } from '../../lib/forge.js';
|
|
23
24
|
import { preflightAgyAuth, recordAgyAuthState } from './agy-auth-cache.js';
|
|
25
|
+
import { assertAgyLaneAllowedUnderTest } from '../../lib/test-env.js';
|
|
24
26
|
const MODEL_CONFIGS = {
|
|
25
27
|
// gemini dispatches to the Antigravity CLI (`agy`) via runAgyConsultation —
|
|
26
28
|
// this entry exists only for model validation and the `pro` alias; its
|
|
@@ -50,6 +52,7 @@ function recordMetrics(ctx, extra) {
|
|
|
50
52
|
db.record({
|
|
51
53
|
timestamp: ctx.timestamp,
|
|
52
54
|
model: ctx.model,
|
|
55
|
+
modelId: extra.modelId,
|
|
53
56
|
reviewType: ctx.reviewType,
|
|
54
57
|
subcommand: ctx.subcommand,
|
|
55
58
|
protocol: ctx.protocol,
|
|
@@ -292,13 +295,147 @@ function commandExists(cmd) {
|
|
|
292
295
|
return false;
|
|
293
296
|
}
|
|
294
297
|
}
|
|
295
|
-
|
|
296
|
-
|
|
298
|
+
/**
|
|
299
|
+
* Shipped default model id for the codex consult lane (#1288).
|
|
300
|
+
*
|
|
301
|
+
* The `-sol` suffix is LOAD-BEARING. Both plain `gpt-5.6` and `gpt-5.6-codex`
|
|
302
|
+
* were live-probed on 2026-07-29 and rejected by Codex with a ChatGPT account
|
|
303
|
+
* ("The '<id>' model is not supported when using Codex with a ChatGPT
|
|
304
|
+
* account."). Do not "simplify" this id — `default-models.test.ts` guards it.
|
|
305
|
+
*/
|
|
306
|
+
export const DEFAULT_CODEX_MODEL = 'gpt-5.6-sol';
|
|
307
|
+
/** Shipped default reasoning effort for the codex consult lane. */
|
|
308
|
+
export const DEFAULT_CODEX_REASONING_EFFORT = 'medium';
|
|
309
|
+
/** Shipped default model id for the claude consult lane (#1288). */
|
|
310
|
+
export const DEFAULT_CLAUDE_MODEL = 'claude-opus-5';
|
|
311
|
+
/**
|
|
312
|
+
* Per-1M-token codex rates, keyed by model id. Verified against
|
|
313
|
+
* https://developers.openai.com/api/docs/pricing on 2026-07-30.
|
|
314
|
+
*
|
|
315
|
+
* Only OpenAI's *standard* tier is modelled; the separate long-context tier
|
|
316
|
+
* (charged above the standard context threshold) is not, so cost is
|
|
317
|
+
* under-reported for unusually large consultations.
|
|
318
|
+
*
|
|
319
|
+
* A model id absent from this table yields `costUsd: null` rather than a cost
|
|
320
|
+
* computed from some other model's rates — a confidently wrong number is worse
|
|
321
|
+
* than none.
|
|
322
|
+
*/
|
|
323
|
+
const CODEX_PRICING = {
|
|
324
|
+
'gpt-5.6-sol': { inputPer1M: 5.00, cachedInputPer1M: 0.50, outputPer1M: 30.00 },
|
|
325
|
+
};
|
|
326
|
+
/**
|
|
327
|
+
* Compute codex consultation cost in USD, or null when the model's published
|
|
328
|
+
* rates are unknown.
|
|
329
|
+
*/
|
|
330
|
+
export function computeCodexCost(model, inputTokens, cachedInputTokens, outputTokens, workspaceRoot) {
|
|
331
|
+
// `consult.pricing.codex` (spec 1286) outranks the shipped table, so a workspace running a model
|
|
332
|
+
// Codev has no rates for can still get real costs instead of nulls. Optional param: main's
|
|
333
|
+
// 4-arg callers and tests are unaffected.
|
|
334
|
+
const configured = workspaceRoot ? loadConfig(workspaceRoot).consult?.pricing?.codex : undefined;
|
|
335
|
+
const pricing = configured ?? CODEX_PRICING[model];
|
|
336
|
+
if (!pricing)
|
|
337
|
+
return null;
|
|
338
|
+
const uncached = inputTokens - cachedInputTokens;
|
|
339
|
+
return (uncached / 1_000_000) * pricing.inputPer1M
|
|
340
|
+
+ (cachedInputTokens / 1_000_000) * pricing.cachedInputPer1M
|
|
341
|
+
+ (outputTokens / 1_000_000) * pricing.outputPer1M;
|
|
342
|
+
}
|
|
343
|
+
/**
|
|
344
|
+
* Resolve which model id an SDK lane runs, and record where it came from.
|
|
345
|
+
*
|
|
346
|
+
* Precedence: `--model-id` > `consult.models.<lane>` > the shipped default constant.
|
|
347
|
+
*
|
|
348
|
+
* The provenance is not decoration: with five config layers, telling a user their
|
|
349
|
+
* `consult.models.codex` is wrong doesn't tell them which of five files to edit.
|
|
350
|
+
*/
|
|
351
|
+
export function resolveLaneModelChoice(workspaceRoot, lane, defaultId, modelIdOverride) {
|
|
352
|
+
if (modelIdOverride !== undefined) {
|
|
353
|
+
validateModelId(modelIdOverride, '--model-id');
|
|
354
|
+
return { id: modelIdOverride, key: '--model-id', source: null, fromFlag: true };
|
|
355
|
+
}
|
|
356
|
+
const { id, key } = resolveLaneModel(loadConfig(workspaceRoot).consult, lane);
|
|
357
|
+
if (id === undefined || key === undefined) {
|
|
358
|
+
return { id: defaultId, key: null, source: null, fromFlag: false };
|
|
359
|
+
}
|
|
360
|
+
return { id, key, source: findConfigSource(workspaceRoot, ['consult', 'models', lane]), fromFlag: false };
|
|
361
|
+
}
|
|
362
|
+
/**
|
|
363
|
+
* Remove a stale review file before failing a consultation.
|
|
364
|
+
*
|
|
365
|
+
* "No review file" has to mean none *exists*, not merely that this run declined to write one.
|
|
366
|
+
* Porch keys off the file's presence, and consult writes to a deterministic per-iteration path — so
|
|
367
|
+
* a review left by an earlier run of the same iteration would be accepted as though the failed run
|
|
368
|
+
* had succeeded, and the phase would advance on a stale verdict. Found by codex reviewing the agy
|
|
369
|
+
* lane; applied to all three lanes because the exposure is identical wherever a runner throws after
|
|
370
|
+
* a previous run wrote output.
|
|
371
|
+
*/
|
|
372
|
+
function discardStaleOutput(outputPath) {
|
|
373
|
+
if (!outputPath)
|
|
374
|
+
return;
|
|
375
|
+
try {
|
|
376
|
+
if (fs.existsSync(outputPath))
|
|
377
|
+
fs.unlinkSync(outputPath);
|
|
378
|
+
}
|
|
379
|
+
catch (err) {
|
|
380
|
+
// Best-effort — failing to unlink must not mask the underlying error. But it must not be
|
|
381
|
+
// silent either: this is precisely the state where porch could accept a stale review, so it
|
|
382
|
+
// has to be visible rather than undetectable.
|
|
383
|
+
console.error(`\n[warning] could not remove stale review at ${outputPath}: ` +
|
|
384
|
+
`${err instanceof Error ? err.message : String(err)}\n` +
|
|
385
|
+
`Delete it manually — porch may otherwise accept it as this iteration's review.`);
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
/**
|
|
389
|
+
* Resolve a model for a lane that has **no built-in default** — agy picks its own model when
|
|
390
|
+
* `--model` is absent, so there is nothing to fall back to.
|
|
391
|
+
*
|
|
392
|
+
* `null` means "omit the flag entirely", which is what preserves zero-config parity: an
|
|
393
|
+
* unconfigured gemini lane must produce byte-identical argv to before this spec.
|
|
394
|
+
*/
|
|
395
|
+
export function resolveOptionalLaneModelChoice(workspaceRoot, lane, modelIdOverride) {
|
|
396
|
+
if (modelIdOverride !== undefined) {
|
|
397
|
+
validateModelId(modelIdOverride, '--model-id');
|
|
398
|
+
return { id: modelIdOverride, key: '--model-id', source: null, fromFlag: true };
|
|
399
|
+
}
|
|
400
|
+
const { id, key } = resolveLaneModel(loadConfig(workspaceRoot).consult, lane);
|
|
401
|
+
if (id === undefined || key === undefined)
|
|
402
|
+
return null;
|
|
403
|
+
return { id, key, source: findConfigSource(workspaceRoot, ['consult', 'models', lane]), fromFlag: false };
|
|
404
|
+
}
|
|
405
|
+
/**
|
|
406
|
+
* Attach model provenance to a provider rejection.
|
|
407
|
+
*
|
|
408
|
+
* Deliberately does NOT substitute a working id or otherwise recover — a bad model id must fail
|
|
409
|
+
* loudly. The provider's own text is preserved verbatim and merely annotated, because paraphrasing
|
|
410
|
+
* a provider error is how you lose the one detail that identifies the real problem.
|
|
411
|
+
*/
|
|
412
|
+
function annotateModelError(err, lane, choice) {
|
|
413
|
+
// A shipped default can't be misconfigured by the user — nothing useful to add.
|
|
414
|
+
if (choice.key === null)
|
|
415
|
+
return err;
|
|
416
|
+
const providerText = err instanceof Error ? err.message : String(err);
|
|
417
|
+
const where = choice.fromFlag
|
|
418
|
+
? 'passed via --model-id'
|
|
419
|
+
: `from \`${choice.key}\`${choice.source ? ` in ${choice.source}` : ''}`;
|
|
420
|
+
const annotated = new Error(`${providerText}\n\n` +
|
|
421
|
+
`The ${lane} lane requested model "${choice.id}" (${where}).\n` +
|
|
422
|
+
`If the provider rejected that id, correct it at the source above. ` +
|
|
423
|
+
`Codev does not fall back to a default model.`);
|
|
424
|
+
if (err instanceof Error && err.stack)
|
|
425
|
+
annotated.stack = err.stack;
|
|
426
|
+
return annotated;
|
|
427
|
+
}
|
|
297
428
|
/**
|
|
298
429
|
* Run Codex consultation via @openai/codex-sdk.
|
|
299
430
|
* Mirrors runClaudeConsultation() — streams events, captures usage, records metrics.
|
|
300
431
|
*/
|
|
301
|
-
export async function runCodexConsultation(queryText, role, workspaceRoot, outputPath, metricsCtx) {
|
|
432
|
+
export async function runCodexConsultation(queryText, role, workspaceRoot, outputPath, metricsCtx, modelChoice, reasoningEffort) {
|
|
433
|
+
// Absent an explicit choice (direct callers), resolve from config so behavior is identical
|
|
434
|
+
// whether the caller threads it through or not.
|
|
435
|
+
const choice = modelChoice ?? resolveLaneModelChoice(workspaceRoot, 'codex', DEFAULT_CODEX_MODEL);
|
|
436
|
+
const effort = reasoningEffort
|
|
437
|
+
?? resolveReasoningEffort(loadConfig(workspaceRoot).consult)
|
|
438
|
+
?? DEFAULT_CODEX_REASONING_EFFORT;
|
|
302
439
|
const chunks = [];
|
|
303
440
|
const startTime = Date.now();
|
|
304
441
|
let usageData = null;
|
|
@@ -314,9 +451,9 @@ export async function runCodexConsultation(queryText, role, workspaceRoot, outpu
|
|
|
314
451
|
},
|
|
315
452
|
});
|
|
316
453
|
const thread = codex.startThread({
|
|
317
|
-
model:
|
|
454
|
+
model: choice.id,
|
|
318
455
|
sandboxMode: 'read-only',
|
|
319
|
-
modelReasoningEffort:
|
|
456
|
+
modelReasoningEffort: effort,
|
|
320
457
|
workingDirectory: workspaceRoot,
|
|
321
458
|
});
|
|
322
459
|
const { events } = await thread.runStreamed(queryText);
|
|
@@ -334,10 +471,7 @@ export async function runCodexConsultation(queryText, role, workspaceRoot, outpu
|
|
|
334
471
|
// output_tokens already includes reasoning_output_tokens (OpenAI Responses-API
|
|
335
472
|
// convention) — do NOT add the latter to cost or reasoning is double-billed.
|
|
336
473
|
const output = event.usage.output_tokens;
|
|
337
|
-
const
|
|
338
|
-
const cost = (uncached / 1_000_000) * CODEX_PRICING.inputPer1M
|
|
339
|
-
+ (cached / 1_000_000) * CODEX_PRICING.cachedInputPer1M
|
|
340
|
-
+ (output / 1_000_000) * CODEX_PRICING.outputPer1M;
|
|
474
|
+
const cost = computeCodexCost(choice.id, input, cached, output, workspaceRoot);
|
|
341
475
|
usageData = { inputTokens: input, cachedInputTokens: cached, outputTokens: output, costUsd: cost };
|
|
342
476
|
}
|
|
343
477
|
if (event.type === 'turn.failed') {
|
|
@@ -365,7 +499,8 @@ export async function runCodexConsultation(queryText, role, workspaceRoot, outpu
|
|
|
365
499
|
errorMessage = (err instanceof Error ? err.message : String(err)).substring(0, 500);
|
|
366
500
|
exitCode = 1;
|
|
367
501
|
}
|
|
368
|
-
|
|
502
|
+
discardStaleOutput(outputPath);
|
|
503
|
+
throw annotateModelError(err, 'codex', choice);
|
|
369
504
|
}
|
|
370
505
|
finally {
|
|
371
506
|
// Clean up temp file
|
|
@@ -375,6 +510,7 @@ export async function runCodexConsultation(queryText, role, workspaceRoot, outpu
|
|
|
375
510
|
if (metricsCtx) {
|
|
376
511
|
const duration = (Date.now() - startTime) / 1000;
|
|
377
512
|
recordMetrics(metricsCtx, {
|
|
513
|
+
modelId: choice.id,
|
|
378
514
|
durationSeconds: duration,
|
|
379
515
|
inputTokens: usageData?.inputTokens ?? null,
|
|
380
516
|
cachedInputTokens: usageData?.cachedInputTokens ?? null,
|
|
@@ -420,7 +556,10 @@ export function buildClaudeConsultEnv(processEnv) {
|
|
|
420
556
|
* Uses the SDK's query() function instead of CLI subprocess.
|
|
421
557
|
* This avoids the CLAUDECODE nesting guard and enables tool use during reviews.
|
|
422
558
|
*/
|
|
423
|
-
async function runClaudeConsultation(queryText, role, workspaceRoot, outputPath, metricsCtx) {
|
|
559
|
+
export async function runClaudeConsultation(queryText, role, workspaceRoot, outputPath, metricsCtx, modelChoice) {
|
|
560
|
+
// Absent an explicit choice (direct callers), resolve from config so behavior is identical
|
|
561
|
+
// whether the caller threads it through or not.
|
|
562
|
+
const choice = modelChoice ?? resolveLaneModelChoice(workspaceRoot, 'claude', DEFAULT_CLAUDE_MODEL);
|
|
424
563
|
const chunks = [];
|
|
425
564
|
const startTime = Date.now();
|
|
426
565
|
let sdkResult;
|
|
@@ -440,7 +579,7 @@ async function runClaudeConsultation(queryText, role, workspaceRoot, outputPath,
|
|
|
440
579
|
allowedTools: ['Read', 'Glob', 'Grep'],
|
|
441
580
|
permissionMode: 'bypassPermissions',
|
|
442
581
|
allowDangerouslySkipPermissions: true,
|
|
443
|
-
model:
|
|
582
|
+
model: choice.id,
|
|
444
583
|
maxTurns: CLAUDE_MAX_TURNS,
|
|
445
584
|
maxBudgetUsd: 25,
|
|
446
585
|
cwd: workspaceRoot,
|
|
@@ -481,7 +620,8 @@ async function runClaudeConsultation(queryText, role, workspaceRoot, outputPath,
|
|
|
481
620
|
errorMessage = (err instanceof Error ? err.message : String(err)).substring(0, 500);
|
|
482
621
|
exitCode = 1;
|
|
483
622
|
}
|
|
484
|
-
|
|
623
|
+
discardStaleOutput(outputPath);
|
|
624
|
+
throw annotateModelError(err, 'claude', choice);
|
|
485
625
|
}
|
|
486
626
|
finally {
|
|
487
627
|
if (savedClaudeCode !== undefined) {
|
|
@@ -492,6 +632,7 @@ async function runClaudeConsultation(queryText, role, workspaceRoot, outputPath,
|
|
|
492
632
|
const duration = (Date.now() - startTime) / 1000;
|
|
493
633
|
const usage = sdkResult ? extractUsage('claude', '', sdkResult) : null;
|
|
494
634
|
recordMetrics(metricsCtx, {
|
|
635
|
+
modelId: choice.id,
|
|
495
636
|
durationSeconds: duration,
|
|
496
637
|
inputTokens: usage?.inputTokens ?? null,
|
|
497
638
|
cachedInputTokens: usage?.cachedInputTokens ?? null,
|
|
@@ -520,6 +661,10 @@ const AGY_PRINT_TIMEOUT = '5m'; // passed to `agy --print-timeout`
|
|
|
520
661
|
const AGY_TIMEOUT_MS = 6 * 60 * 1000; // Codev-owned hard cap (> agy's own timeout)
|
|
521
662
|
// OAuth banner appears before any review text; only scan the early stream.
|
|
522
663
|
const AGY_MARKER_SCAN_LIMIT = 8192;
|
|
664
|
+
// Bounded tail of agy's own output retained for a configured-lane hard failure. agy's rejection
|
|
665
|
+
// text is the only thing that explains WHY a model id was refused, but it lands in an error
|
|
666
|
+
// message, so it is capped rather than accumulated.
|
|
667
|
+
const AGY_FAILURE_TAIL_MAX_CHARS = 2000;
|
|
523
668
|
/**
|
|
524
669
|
* How long a prober waits, marker-free, before publishing `auth` to the shared
|
|
525
670
|
* cache (#1077). The OAuth banner is the very first thing an unauthenticated agy
|
|
@@ -581,6 +726,12 @@ export function resolveAgyBin() {
|
|
|
581
726
|
const override = process.env.CODEV_AGY_BIN;
|
|
582
727
|
if (override)
|
|
583
728
|
return isRealAgyCli(override) ? override : null;
|
|
729
|
+
// Past this point we go looking for the developer's real install, so this is
|
|
730
|
+
// the true chokepoint for the test-isolation guard (#1323) — not the spawn
|
|
731
|
+
// sites. Resolution is not passive: the PATH branch below runs
|
|
732
|
+
// `agyRespondsToVersion`, which *executes* the candidate binary. Guarding only
|
|
733
|
+
// the spawn would still let a suite run the real agy.
|
|
734
|
+
assertAgyLaneAllowedUnderTest();
|
|
584
735
|
// Canonical install path — trusted location; realpath-reject the IDE only.
|
|
585
736
|
const preferred = path.join(homedir(), '.local', 'bin', 'agy');
|
|
586
737
|
if (isRealAgyCli(preferred))
|
|
@@ -638,10 +789,15 @@ function writeConsultOutput(outputPath, content) {
|
|
|
638
789
|
fs.writeFileSync(outputPath, content);
|
|
639
790
|
console.error(`\nOutput written to: ${outputPath}`);
|
|
640
791
|
}
|
|
641
|
-
function recordAgyMetrics(metricsCtx, startTime, exitCode, errorMessage
|
|
792
|
+
function recordAgyMetrics(metricsCtx, startTime, exitCode, errorMessage,
|
|
793
|
+
// No default: every caller states the id or states null. A default would quietly reintroduce
|
|
794
|
+
// the silent-NULL path that making modelId required on MetricsRecord exists to prevent.
|
|
795
|
+
modelId) {
|
|
642
796
|
if (!metricsCtx)
|
|
643
797
|
return;
|
|
644
798
|
recordMetrics(metricsCtx, {
|
|
799
|
+
// Null on a skip with no model configured — "no model was chosen", not "we forgot".
|
|
800
|
+
modelId,
|
|
645
801
|
durationSeconds: (Date.now() - startTime) / 1000,
|
|
646
802
|
// agy --print emits plain text, no token usage → cost rows degrade gracefully (null).
|
|
647
803
|
inputTokens: null,
|
|
@@ -658,15 +814,25 @@ function recordAgyMetrics(metricsCtx, startTime, exitCode, errorMessage) {
|
|
|
658
814
|
* prompt, and NEVER blocks the run: a missing/unauthed/invalid CLI or a
|
|
659
815
|
* timeout/error produces a non-blocking COMMENT skip instead of throwing.
|
|
660
816
|
*/
|
|
661
|
-
async function runAgyConsultation(queryText, role, workspaceRoot, outputPath, metricsCtx) {
|
|
817
|
+
async function runAgyConsultation(queryText, role, workspaceRoot, outputPath, metricsCtx, modelChoice) {
|
|
662
818
|
const startTime = Date.now();
|
|
819
|
+
// `undefined` means "resolve it yourself" (direct callers); an explicit `null` means "no model
|
|
820
|
+
// configured", which must stay distinguishable from "not yet resolved".
|
|
821
|
+
const choice = modelChoice === undefined
|
|
822
|
+
? resolveOptionalLaneModelChoice(workspaceRoot, 'gemini')
|
|
823
|
+
: modelChoice;
|
|
824
|
+
// The test-isolation guard (#1323) lives inside resolveAgyBin, at the point
|
|
825
|
+
// where an unpinned lookup would reach the real install. It throws rather than
|
|
826
|
+
// falling through to the non-blocking skip below — deliberately: a misconfigured
|
|
827
|
+
// test must fail loudly instead of passing on a machine that happens not to have
|
|
828
|
+
// agy installed while spawning the real CLI on one that does.
|
|
663
829
|
const bin = resolveAgyBin();
|
|
664
830
|
if (!bin) {
|
|
665
831
|
const reason = 'agy CLI not found (install: https://antigravity.google/cli/install.sh)';
|
|
666
832
|
const content = agySkipContent(reason);
|
|
667
833
|
process.stdout.write(content);
|
|
668
834
|
writeConsultOutput(outputPath, content);
|
|
669
|
-
recordAgyMetrics(metricsCtx, startTime, 0, reason);
|
|
835
|
+
recordAgyMetrics(metricsCtx, startTime, 0, reason, choice?.id ?? null);
|
|
670
836
|
console.error(`\n[gemini (agy) skipped: ${reason}]`);
|
|
671
837
|
return;
|
|
672
838
|
}
|
|
@@ -680,7 +846,7 @@ async function runAgyConsultation(queryText, role, workspaceRoot, outputPath, me
|
|
|
680
846
|
const content = agySkipContent(reason);
|
|
681
847
|
process.stdout.write(content);
|
|
682
848
|
writeConsultOutput(outputPath, content);
|
|
683
|
-
recordAgyMetrics(metricsCtx, startTime, 0, reason);
|
|
849
|
+
recordAgyMetrics(metricsCtx, startTime, 0, reason, choice?.id ?? null);
|
|
684
850
|
console.error(`\n[gemini (agy) skipped without spawning: ${reason}]`);
|
|
685
851
|
return;
|
|
686
852
|
}
|
|
@@ -716,6 +882,11 @@ async function runAgyConsultation(queryText, role, workspaceRoot, outputPath, me
|
|
|
716
882
|
const args = ['--sandbox', '--print-timeout', AGY_PRINT_TIMEOUT];
|
|
717
883
|
for (const d of addDirs)
|
|
718
884
|
args.push('--add-dir', d);
|
|
885
|
+
// Omitted entirely when unconfigured, so an unconfigured lane's argv is byte-identical to
|
|
886
|
+
// pre-1286 and agy keeps choosing its own model. Must precede --print: agy parses --print as a
|
|
887
|
+
// string-valued option, so its value has to be the immediately following argument.
|
|
888
|
+
if (choice)
|
|
889
|
+
args.push('--model', choice.id);
|
|
719
890
|
// agy 1.0.10 defines --print as a string-valued option, so its prompt must
|
|
720
891
|
// immediately follow the flag rather than another option such as --sandbox.
|
|
721
892
|
args.push('--print', promptArg);
|
|
@@ -727,7 +898,7 @@ async function runAgyConsultation(queryText, role, workspaceRoot, outputPath, me
|
|
|
727
898
|
catch { /* best-effort */ }
|
|
728
899
|
}
|
|
729
900
|
};
|
|
730
|
-
return new Promise((resolve) => {
|
|
901
|
+
return new Promise((resolve, reject) => {
|
|
731
902
|
const proc = spawn(bin, args, {
|
|
732
903
|
cwd: workspaceRoot,
|
|
733
904
|
stdio: ['ignore', 'pipe', 'pipe'],
|
|
@@ -735,6 +906,11 @@ async function runAgyConsultation(queryText, role, workspaceRoot, outputPath, me
|
|
|
735
906
|
const outChunks = [];
|
|
736
907
|
let scanBuf = '';
|
|
737
908
|
let settled = false;
|
|
909
|
+
// stderr is watched for auth markers but otherwise discarded today, so a hard failure would
|
|
910
|
+
// have nothing but an exit code to report. Retain a bounded tail of BOTH streams: agy's own
|
|
911
|
+
// text is the only thing that explains *why* a model was rejected, and this lands in an error
|
|
912
|
+
// message, so it must not be unbounded.
|
|
913
|
+
let outputTail = '';
|
|
738
914
|
// When we hold the probe lock, other consult processes are polling the cache
|
|
739
915
|
// for our verdict — publish it as soon as it is knowable, and always release
|
|
740
916
|
// the lock, whatever this run turns into.
|
|
@@ -766,7 +942,7 @@ async function runAgyConsultation(queryText, role, workspaceRoot, outputPath, me
|
|
|
766
942
|
const content = agySkipContent(reason);
|
|
767
943
|
process.stdout.write(content);
|
|
768
944
|
writeConsultOutput(outputPath, content);
|
|
769
|
-
recordAgyMetrics(metricsCtx, startTime, exitCode, reason);
|
|
945
|
+
recordAgyMetrics(metricsCtx, startTime, exitCode, reason, choice?.id ?? null);
|
|
770
946
|
console.error(`\n[gemini (agy) skipped: ${reason}]`);
|
|
771
947
|
resolve();
|
|
772
948
|
};
|
|
@@ -774,6 +950,7 @@ async function runAgyConsultation(queryText, role, workspaceRoot, outputPath, me
|
|
|
774
950
|
const watch = (buf, isStdout) => {
|
|
775
951
|
if (isStdout)
|
|
776
952
|
outChunks.push(buf);
|
|
953
|
+
outputTail = (outputTail + buf.toString('utf-8')).slice(-AGY_FAILURE_TAIL_MAX_CHARS);
|
|
777
954
|
if (scanBuf.length < AGY_MARKER_SCAN_LIMIT) {
|
|
778
955
|
scanBuf += buf.toString('utf-8');
|
|
779
956
|
if (AGY_OAUTH_MARKERS.some((m) => scanBuf.includes(m))) {
|
|
@@ -806,7 +983,45 @@ async function runAgyConsultation(queryText, role, workspaceRoot, outputPath, me
|
|
|
806
983
|
clearTimeout(timer);
|
|
807
984
|
cleanup();
|
|
808
985
|
const raw = Buffer.concat(outChunks).toString('utf-8').trim();
|
|
809
|
-
|
|
986
|
+
// THE PHASE 3 INVARIANT: a skip may only be reached for an ENVIRONMENT cause.
|
|
987
|
+
//
|
|
988
|
+
// Configuring `consult.models.gemini` is opting out of "quietly proceed without this lane" —
|
|
989
|
+
// a non-zero exit then means the model was probably rejected, and swallowing that as a
|
|
990
|
+
// COMMENT skip would let a typo'd model id silently reduce every review to two lanes.
|
|
991
|
+
//
|
|
992
|
+
// Deliberately narrow: ONLY a non-zero exit hard-fails. Auth, timeout, non-response and
|
|
993
|
+
// empty output stay skips even when configured, because those are environment causes and the
|
|
994
|
+
// degraded-agy lane (#1032/#1033) must keep its non-blocking property. Widening this to
|
|
995
|
+
// "any failure" would wedge phases for workspaces whose agy is merely unauthenticated.
|
|
996
|
+
// Environment causes are classified FIRST. agy can emit its non-response marker *and* exit
|
|
997
|
+
// non-zero, and checking the exit code before the marker would misfile that timeout as a
|
|
998
|
+
// configuration failure — breaking the "timeout stays a skip in both cases" half of the
|
|
999
|
+
// invariant for exactly the degraded lane it exists to protect. (Found by codex at review.)
|
|
1000
|
+
// ONLY the non-response marker, deliberately — NOT empty stdout. A rejected model id writes
|
|
1001
|
+
// its error to stderr and exits non-zero with empty stdout, so treating "no stdout" as an
|
|
1002
|
+
// environment cause would make the hard failure unreachable for the exact case it exists to
|
|
1003
|
+
// catch. (My own stale-review test caught that overcorrection.) Empty stdout still means
|
|
1004
|
+
// "no review" on the zero-exit path below.
|
|
1005
|
+
// Checked against BOTH streams: agy may print its timeout notice to stderr, and matching
|
|
1006
|
+
// stdout alone would hard-fail a configured lane on a plain timeout — the same invariant hole
|
|
1007
|
+
// twice over. `outputTail` is capped, but a timeout notice is by nature near the end of the
|
|
1008
|
+
// stream, so the tail is where it lands. (Found by claude at review.)
|
|
1009
|
+
const timedOutProducing = raw.includes(AGY_NONRESPONSE_MARKER) || outputTail.includes(AGY_NONRESPONSE_MARKER);
|
|
1010
|
+
// `code === null` means agy was killed by a signal (OOM, external kill) — an environment
|
|
1011
|
+
// cause, not a rejected model, so it must not hard-fail either. `code !== 0` alone is true
|
|
1012
|
+
// for null and would misfile it. (Found by claude at review.)
|
|
1013
|
+
if (code !== null && code !== 0 && choice && !timedOutProducing) {
|
|
1014
|
+
publishAuth();
|
|
1015
|
+
recordAgyMetrics(metricsCtx, startTime, code, `agy exited with code ${code}`, choice?.id ?? null);
|
|
1016
|
+
console.error(`\n[gemini (agy) FAILED: configured model "${choice.id}" — see error]`);
|
|
1017
|
+
// "No review file" must mean none EXISTS, not merely that this run wrote none.
|
|
1018
|
+
discardStaleOutput(outputPath);
|
|
1019
|
+
const providerError = new Error(`agy exited with code ${code}.` +
|
|
1020
|
+
(outputTail.trim() ? `\n\nagy output (last ${AGY_FAILURE_TAIL_MAX_CHARS} chars):\n${outputTail.trim()}` : ''));
|
|
1021
|
+
reject(annotateModelError(providerError, 'gemini', choice));
|
|
1022
|
+
return;
|
|
1023
|
+
}
|
|
1024
|
+
if (code !== 0 || raw.length === 0 || timedOutProducing) {
|
|
810
1025
|
// A broken run tells us nothing about auth — release without a verdict
|
|
811
1026
|
// and let the next call re-probe.
|
|
812
1027
|
publishAuth();
|
|
@@ -818,7 +1033,7 @@ async function runAgyConsultation(queryText, role, workspaceRoot, outputPath, me
|
|
|
818
1033
|
const content = agySkipContent(reason);
|
|
819
1034
|
process.stdout.write(content);
|
|
820
1035
|
writeConsultOutput(outputPath, content);
|
|
821
|
-
recordAgyMetrics(metricsCtx, startTime, code ?? 1, reason);
|
|
1036
|
+
recordAgyMetrics(metricsCtx, startTime, code ?? 1, reason, choice?.id ?? null);
|
|
822
1037
|
console.error(`\n[gemini (agy) skipped: ${reason}]`);
|
|
823
1038
|
resolve();
|
|
824
1039
|
return;
|
|
@@ -828,20 +1043,40 @@ async function runAgyConsultation(queryText, role, workspaceRoot, outputPath, me
|
|
|
828
1043
|
// Plain-text stdout IS the review.
|
|
829
1044
|
process.stdout.write(raw);
|
|
830
1045
|
writeConsultOutput(outputPath, raw);
|
|
831
|
-
recordAgyMetrics(metricsCtx, startTime, 0, null);
|
|
1046
|
+
recordAgyMetrics(metricsCtx, startTime, 0, null, choice?.id ?? null);
|
|
832
1047
|
console.error(`\n[gemini (agy) completed in ${((Date.now() - startTime) / 1000).toFixed(1)}s]`);
|
|
833
1048
|
resolve();
|
|
834
1049
|
});
|
|
835
1050
|
});
|
|
836
1051
|
}
|
|
1052
|
+
/**
|
|
1053
|
+
* Record the model a lane actually ran, so a transcript answers "what did this use?".
|
|
1054
|
+
*
|
|
1055
|
+
* Logged from the dispatch branch that owns the resolved `choice`, NOT re-derived for display: a
|
|
1056
|
+
* second resolution path is exactly how `--model-id` came to be documented, parsed, and inert.
|
|
1057
|
+
* Naming the source too means a surprising id points at the file to edit.
|
|
1058
|
+
*/
|
|
1059
|
+
function logResolvedModel(lane, id, key, effort) {
|
|
1060
|
+
const from = key ? ` (from ${key})` : '';
|
|
1061
|
+
const at = effort ? ` at ${effort} reasoning effort` : '';
|
|
1062
|
+
console.error(`[${lane.toUpperCase()}] model: ${id}${at}${from}`);
|
|
1063
|
+
}
|
|
837
1064
|
/**
|
|
838
1065
|
* Run the consultation — dispatches to the correct model runner.
|
|
839
1066
|
*/
|
|
840
|
-
async function runConsultation(model, query, workspaceRoot, role, outputPath, metricsCtx, generalMode) {
|
|
1067
|
+
async function runConsultation(model, query, workspaceRoot, role, outputPath, metricsCtx, generalMode, modelIdOverride) {
|
|
1068
|
+
// Fail before dispatch if the selected lane cannot honour the override. Checked here rather than
|
|
1069
|
+
// per-branch so a lane that never reads it can't silently ignore it (codex caught exactly that for
|
|
1070
|
+
// hermes). Syntax is validated per-lane in resolveLaneModelChoice.
|
|
1071
|
+
if (modelIdOverride !== undefined) {
|
|
1072
|
+
assertLaneAcceptsModelOverride(model);
|
|
1073
|
+
}
|
|
841
1074
|
// SDK-based models
|
|
842
1075
|
if (model === 'claude') {
|
|
843
1076
|
const startTime = Date.now();
|
|
844
|
-
|
|
1077
|
+
const choice = resolveLaneModelChoice(workspaceRoot, 'claude', DEFAULT_CLAUDE_MODEL, modelIdOverride);
|
|
1078
|
+
logResolvedModel(model, choice.id, choice.key);
|
|
1079
|
+
await runClaudeConsultation(query, role, workspaceRoot, outputPath, metricsCtx, choice);
|
|
845
1080
|
const duration = (Date.now() - startTime) / 1000;
|
|
846
1081
|
logQuery(workspaceRoot, model, query, duration);
|
|
847
1082
|
console.error(`\n[${model} completed in ${duration.toFixed(1)}s]`);
|
|
@@ -849,7 +1084,10 @@ async function runConsultation(model, query, workspaceRoot, role, outputPath, me
|
|
|
849
1084
|
}
|
|
850
1085
|
if (model === 'codex') {
|
|
851
1086
|
const startTime = Date.now();
|
|
852
|
-
|
|
1087
|
+
const choice = resolveLaneModelChoice(workspaceRoot, 'codex', DEFAULT_CODEX_MODEL, modelIdOverride);
|
|
1088
|
+
const effort = resolveReasoningEffort(loadConfig(workspaceRoot).consult) ?? DEFAULT_CODEX_REASONING_EFFORT;
|
|
1089
|
+
logResolvedModel(model, choice.id, choice.key, effort);
|
|
1090
|
+
await runCodexConsultation(query, role, workspaceRoot, outputPath, metricsCtx, choice, effort);
|
|
853
1091
|
const duration = (Date.now() - startTime) / 1000;
|
|
854
1092
|
logQuery(workspaceRoot, model, query, duration);
|
|
855
1093
|
console.error(`\n[${model} completed in ${duration.toFixed(1)}s]`);
|
|
@@ -859,7 +1097,10 @@ async function runConsultation(model, query, workspaceRoot, role, outputPath, me
|
|
|
859
1097
|
// and non-blocking skip (see runAgyConsultation).
|
|
860
1098
|
if (model === 'gemini') {
|
|
861
1099
|
const startTime = Date.now();
|
|
862
|
-
|
|
1100
|
+
const choice = resolveOptionalLaneModelChoice(workspaceRoot, 'gemini', modelIdOverride);
|
|
1101
|
+
// No configured id means agy chooses; say so rather than printing a value we did not set.
|
|
1102
|
+
logResolvedModel(model, choice?.id ?? "agy's own default", choice?.key ?? null);
|
|
1103
|
+
await runAgyConsultation(query, role, workspaceRoot, outputPath, metricsCtx, choice);
|
|
863
1104
|
logQuery(workspaceRoot, model, query, (Date.now() - startTime) / 1000);
|
|
864
1105
|
return;
|
|
865
1106
|
}
|
|
@@ -935,6 +1176,8 @@ async function runConsultation(model, query, workspaceRoot, role, outputPath, me
|
|
|
935
1176
|
if (metricsCtx) {
|
|
936
1177
|
const usage = extractUsage(model, rawOutput);
|
|
937
1178
|
recordMetrics(metricsCtx, {
|
|
1179
|
+
// Subprocess lanes (hermes) expose no model selector — see MODEL_CONFIGURABLE_LANES.
|
|
1180
|
+
modelId: null,
|
|
938
1181
|
durationSeconds: duration,
|
|
939
1182
|
inputTokens: usage?.inputTokens ?? null,
|
|
940
1183
|
cachedInputTokens: usage?.cachedInputTokens ?? null,
|
|
@@ -960,6 +1203,7 @@ async function runConsultation(model, query, workspaceRoot, role, outputPath, me
|
|
|
960
1203
|
if (metricsCtx) {
|
|
961
1204
|
const duration = (Date.now() - startTime) / 1000;
|
|
962
1205
|
recordMetrics(metricsCtx, {
|
|
1206
|
+
modelId: null,
|
|
963
1207
|
durationSeconds: duration,
|
|
964
1208
|
inputTokens: null,
|
|
965
1209
|
cachedInputTokens: null,
|
|
@@ -1833,7 +2077,7 @@ export async function consult(options) {
|
|
|
1833
2077
|
}
|
|
1834
2078
|
}
|
|
1835
2079
|
const isGeneralMode = !hasType;
|
|
1836
|
-
await runConsultation(model, query, workspaceRoot, role, outputPath, metricsCtx, isGeneralMode);
|
|
2080
|
+
await runConsultation(model, query, workspaceRoot, role, outputPath, metricsCtx, isGeneralMode, options.modelId);
|
|
1837
2081
|
}
|
|
1838
2082
|
// Exported for testing
|
|
1839
2083
|
export { getDiffStat as _getDiffStat, buildSpecQuery as _buildSpecQuery, buildPlanQuery as _buildPlanQuery, buildPRQuery as _buildPRQuery, composePRQueryText as _composePRQueryText, computeLocalPRDiff as _computeLocalPRDiff, resolveIntegrationBase as _resolveIntegrationBase, resolveArchitectQuery as _resolveArchitectQuery, computePersistentOutputPath as _computePersistentOutputPath, MODEL_CONFIGS as _MODEL_CONFIGS, MODEL_ALIASES as _MODEL_ALIASES, runAgyConsultation as _runAgyConsultation, agySkipContent as _agySkipContent, };
|