muonroi-cli 1.8.4 → 1.9.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/LICENSE +17 -5
- package/dist/packages/agent-harness-core/src/driver.d.ts +27 -1
- package/dist/packages/agent-harness-core/src/driver.js +46 -0
- package/dist/packages/agent-harness-core/src/event-filter.js +11 -0
- package/dist/packages/agent-harness-core/src/event-redact.js +7 -0
- package/dist/packages/agent-harness-core/src/event-tee.d.ts +64 -0
- package/dist/packages/agent-harness-core/src/event-tee.js +104 -0
- package/dist/packages/agent-harness-core/src/mcp-server.d.ts +25 -0
- package/dist/packages/agent-harness-core/src/mcp-server.js +169 -21
- package/dist/packages/agent-harness-core/src/predicate.d.ts +1 -1
- package/dist/packages/agent-harness-core/src/protocol.d.ts +90 -4
- package/dist/packages/agent-harness-core/src/protocol.js +15 -0
- package/dist/packages/agent-harness-core/src/visual-quality.d.ts +58 -0
- package/dist/packages/agent-harness-core/src/visual-quality.js +141 -0
- package/dist/packages/agent-harness-opentui/src/agent-mode.d.ts +6 -0
- package/dist/packages/agent-harness-opentui/src/agent-mode.js +14 -1
- package/dist/packages/agent-harness-opentui/src/input-bridge.d.ts +2 -10
- package/dist/packages/agent-harness-opentui/src/input-bridge.js +103 -16
- package/dist/packages/agent-harness-opentui/src/install.d.ts +8 -0
- package/dist/packages/agent-harness-opentui/src/install.js +10 -0
- package/dist/packages/agent-harness-opentui/src/semantic.js +12 -10
- package/dist/packages/agent-harness-opentui/src/visual-capture.d.ts +56 -0
- package/dist/packages/agent-harness-opentui/src/visual-capture.js +103 -0
- package/dist/src/agent-harness/mock-model.d.ts +38 -0
- package/dist/src/agent-harness/mock-model.js +69 -3
- package/dist/src/agent-harness/test-spawn.js +31 -0
- package/dist/src/chat/chat-keychain.d.ts +7 -12
- package/dist/src/chat/chat-keychain.js +19 -86
- package/dist/src/cli/config/screen-providers.js +1 -1
- package/dist/src/cli/cost-forensics.d.ts +10 -0
- package/dist/src/cli/cost-forensics.js +18 -3
- package/dist/src/cli/keys-bundle.d.ts +1 -1
- package/dist/src/cli/keys-bundle.js +1 -1
- package/dist/src/cli/keys.d.ts +10 -47
- package/dist/src/cli/keys.js +31 -399
- package/dist/src/council/clarifier.d.ts +31 -3
- package/dist/src/council/clarifier.js +220 -32
- package/dist/src/council/context.js +49 -15
- package/dist/src/council/debate-checkpoint.d.ts +129 -0
- package/dist/src/council/debate-checkpoint.js +176 -0
- package/dist/src/council/debate-planner.js +54 -5
- package/dist/src/council/debate-summary.d.ts +25 -0
- package/dist/src/council/debate-summary.js +85 -0
- package/dist/src/council/debate.d.ts +169 -2
- package/dist/src/council/debate.js +1265 -135
- package/dist/src/council/index.d.ts +108 -1
- package/dist/src/council/index.js +670 -197
- package/dist/src/council/leader.d.ts +26 -0
- package/dist/src/council/leader.js +150 -9
- package/dist/src/council/llm.d.ts +94 -0
- package/dist/src/council/llm.js +348 -55
- package/dist/src/council/panel-select.d.ts +30 -0
- package/dist/src/council/panel-select.js +82 -0
- package/dist/src/council/planner.js +40 -0
- package/dist/src/council/preflight.d.ts +17 -0
- package/dist/src/council/preflight.js +50 -2
- package/dist/src/council/prompts.d.ts +39 -4
- package/dist/src/council/prompts.js +256 -69
- package/dist/src/council/stance-recall.d.ts +42 -0
- package/dist/src/council/stance-recall.js +57 -0
- package/dist/src/council/strip-think.d.ts +17 -0
- package/dist/src/council/strip-think.js +33 -0
- package/dist/src/council/types.d.ts +138 -0
- package/dist/src/ee/artifact-cache.d.ts +16 -0
- package/dist/src/ee/artifact-cache.js +32 -0
- package/dist/src/ee/auth.d.ts +20 -0
- package/dist/src/ee/auth.js +54 -2
- package/dist/src/ee/bridge.d.ts +10 -0
- package/dist/src/ee/bridge.js +58 -0
- package/dist/src/ee/client.js +109 -21
- package/dist/src/ee/ee-onboarding.js +6 -26
- package/dist/src/ee/export-transcripts.d.ts +1 -0
- package/dist/src/ee/export-transcripts.js +8 -10
- package/dist/src/ee/extract-session.js +29 -0
- package/dist/src/ee/extract-style.d.ts +58 -0
- package/dist/src/ee/extract-style.js +270 -0
- package/dist/src/ee/recall-ledger.d.ts +9 -0
- package/dist/src/ee/recall-ledger.js +3 -0
- package/dist/src/ee/scope.d.ts +1 -0
- package/dist/src/ee/scope.js +26 -1
- package/dist/src/ee/search.d.ts +7 -0
- package/dist/src/ee/search.js +24 -0
- package/dist/src/ee/transcript-emit.js +2 -0
- package/dist/src/ee/types.d.ts +22 -0
- package/dist/src/ee/who-am-i-brain.d.ts +35 -0
- package/dist/src/ee/who-am-i-brain.js +220 -0
- package/dist/src/ee/who-am-i.d.ts +10 -3
- package/dist/src/ee/who-am-i.js +12 -0
- package/dist/src/ee/workflow-event.d.ts +48 -0
- package/dist/src/ee/workflow-event.js +81 -0
- package/dist/src/flow/compaction/compress.d.ts +3 -3
- package/dist/src/flow/compaction/compress.js +58 -8
- package/dist/src/flow/compaction/extract.d.ts +4 -7
- package/dist/src/flow/compaction/extract.js +50 -10
- package/dist/src/flow/compaction/index.d.ts +14 -1
- package/dist/src/flow/compaction/index.js +96 -3
- package/dist/src/flow/compaction/input-guard.d.ts +24 -0
- package/dist/src/flow/compaction/input-guard.js +43 -0
- package/dist/src/flow/compaction/progress.d.ts +35 -0
- package/dist/src/flow/compaction/progress.js +35 -0
- package/dist/src/flow/fold-planning.d.ts +36 -0
- package/dist/src/flow/fold-planning.js +83 -0
- package/dist/src/flow/hierarchy.d.ts +146 -0
- package/dist/src/flow/hierarchy.js +427 -0
- package/dist/src/flow/index.d.ts +1 -0
- package/dist/src/flow/index.js +2 -0
- package/dist/src/flow/run-artifacts.d.ts +102 -0
- package/dist/src/flow/run-artifacts.js +208 -0
- package/dist/src/generated/version.d.ts +1 -1
- package/dist/src/generated/version.js +1 -1
- package/dist/src/gsd/assessment-schema.d.ts +44 -0
- package/dist/src/gsd/assessment-schema.js +134 -0
- package/dist/src/gsd/capability-registry.d.ts +45 -0
- package/dist/src/gsd/capability-registry.js +337 -0
- package/dist/src/gsd/complexity-assessor.d.ts +39 -0
- package/dist/src/gsd/complexity-assessor.js +152 -0
- package/dist/src/gsd/config-bridge.d.ts +7 -0
- package/dist/src/gsd/config-bridge.js +114 -0
- package/dist/src/gsd/config-loader.d.ts +27 -0
- package/dist/src/gsd/config-loader.js +50 -0
- package/dist/src/gsd/council-context.d.ts +44 -0
- package/dist/src/gsd/council-context.js +114 -0
- package/dist/src/gsd/ee-closure.d.ts +28 -0
- package/dist/src/gsd/ee-closure.js +49 -0
- package/dist/src/gsd/flags.d.ts +66 -0
- package/dist/src/gsd/flags.js +102 -0
- package/dist/src/gsd/gsd-dispatch.d.ts +58 -0
- package/dist/src/gsd/gsd-dispatch.js +131 -0
- package/dist/src/gsd/gsd-runtime.d.ts +22 -0
- package/dist/src/gsd/gsd-runtime.js +37 -0
- package/dist/src/gsd/host-adapter.d.ts +11 -0
- package/dist/src/gsd/host-adapter.js +29 -0
- package/dist/src/gsd/index.d.ts +24 -1
- package/dist/src/gsd/index.js +27 -0
- package/dist/src/gsd/loop-host-contract.d.ts +21 -0
- package/dist/src/gsd/loop-host-contract.js +39 -0
- package/dist/src/gsd/loop-host.d.ts +69 -0
- package/dist/src/gsd/loop-host.js +245 -0
- package/dist/src/gsd/loop-resolver.d.ts +36 -0
- package/dist/src/gsd/loop-resolver.js +79 -0
- package/dist/src/gsd/model-tier.d.ts +13 -0
- package/dist/src/gsd/model-tier.js +45 -0
- package/dist/src/gsd/mutation-gate.d.ts +16 -0
- package/dist/src/gsd/mutation-gate.js +41 -0
- package/dist/src/gsd/native-roadmap.d.ts +89 -0
- package/dist/src/gsd/native-roadmap.js +343 -0
- package/dist/src/gsd/native-state.d.ts +47 -0
- package/dist/src/gsd/native-state.js +220 -0
- package/dist/src/gsd/paths.d.ts +23 -0
- package/dist/src/gsd/paths.js +66 -0
- package/dist/src/gsd/phase-dag.d.ts +12 -0
- package/dist/src/gsd/phase-dag.js +94 -0
- package/dist/src/gsd/phase-sync.d.ts +42 -0
- package/dist/src/gsd/phase-sync.js +321 -0
- package/dist/src/gsd/pil-gate-context.d.ts +13 -0
- package/dist/src/gsd/pil-gate-context.js +64 -0
- package/dist/src/gsd/pil-gate-critic.d.ts +19 -0
- package/dist/src/gsd/pil-gate-critic.js +74 -0
- package/dist/src/gsd/plan-council-prompts.d.ts +25 -0
- package/dist/src/gsd/plan-council-prompts.js +79 -0
- package/dist/src/gsd/plan-council.d.ts +44 -0
- package/dist/src/gsd/plan-council.js +283 -0
- package/dist/src/gsd/plan-gate-vocabulary.d.ts +40 -0
- package/dist/src/gsd/plan-gate-vocabulary.js +64 -0
- package/dist/src/gsd/product-workspace.d.ts +13 -0
- package/dist/src/gsd/product-workspace.js +124 -0
- package/dist/src/gsd/ship-bridge.d.ts +25 -0
- package/dist/src/gsd/ship-bridge.js +65 -0
- package/dist/src/gsd/state-document.d.ts +40 -0
- package/dist/src/gsd/state-document.js +163 -0
- package/dist/src/gsd/verdict-schema.d.ts +39 -0
- package/dist/src/gsd/verdict-schema.js +144 -0
- package/dist/src/gsd/verify-context.d.ts +22 -0
- package/dist/src/gsd/verify-context.js +27 -0
- package/dist/src/gsd/verify-council-prompts.d.ts +19 -0
- package/dist/src/gsd/verify-council-prompts.js +85 -0
- package/dist/src/gsd/verify-council.d.ts +25 -0
- package/dist/src/gsd/verify-council.js +119 -0
- package/dist/src/gsd/verify-gate-vocabulary.d.ts +25 -0
- package/dist/src/gsd/verify-gate-vocabulary.js +46 -0
- package/dist/src/gsd/workflow-engine.d.ts +60 -0
- package/dist/src/gsd/workflow-engine.js +207 -0
- package/dist/src/gsd/workflow-tools.d.ts +13 -0
- package/dist/src/gsd/workflow-tools.js +277 -0
- package/dist/src/headless/council-answers.js +4 -0
- package/dist/src/hooks/index.js +1 -1
- package/dist/src/index.js +172 -270
- package/dist/src/lsp/builtins.js +3 -1
- package/dist/src/lsp/manager.d.ts +5 -1
- package/dist/src/lsp/manager.js +249 -3
- package/dist/src/lsp/npm-cache.d.ts +11 -1
- package/dist/src/lsp/npm-cache.js +17 -1
- package/dist/src/lsp/runtime.d.ts +6 -1
- package/dist/src/lsp/runtime.js +17 -1
- package/dist/src/lsp/types.d.ts +83 -1
- package/dist/src/lsp/types.js +10 -0
- package/dist/src/maintain/pr-builder.js +23 -13
- package/dist/src/mcp/auto-setup.js +57 -32
- package/dist/src/mcp/client-pool.js +44 -16
- package/dist/src/mcp/lsp-tools.d.ts +5 -1
- package/dist/src/mcp/lsp-tools.js +93 -2
- package/dist/src/mcp/mcp-keychain.d.ts +3 -5
- package/dist/src/mcp/mcp-keychain.js +9 -49
- package/dist/src/mcp/research-onboarding.js +8 -7
- package/dist/src/mcp/runtime.js +34 -2
- package/dist/src/mcp/setup-guide-text.d.ts +1 -1
- package/dist/src/mcp/setup-guide-text.js +22 -2
- package/dist/src/mcp/tools-server.d.ts +10 -0
- package/dist/src/mcp/tools-server.js +10 -2
- package/dist/src/models/catalog-client.d.ts +87 -0
- package/dist/src/models/catalog-client.js +105 -38
- package/dist/src/models/catalog.json +528 -265
- package/dist/src/models/registry.d.ts +22 -7
- package/dist/src/models/registry.js +73 -10
- package/dist/src/ops/doctor.js +1 -1
- package/dist/src/orchestrator/ask-user.d.ts +61 -0
- package/dist/src/orchestrator/ask-user.js +65 -0
- package/dist/src/orchestrator/auto-commit.js +1 -1
- package/dist/src/orchestrator/batch-turn-runner.js +2 -2
- package/dist/src/orchestrator/cache-prefix.d.ts +67 -0
- package/dist/src/orchestrator/cache-prefix.js +83 -0
- package/dist/src/orchestrator/compact-request.d.ts +32 -0
- package/dist/src/orchestrator/compact-request.js +41 -0
- package/dist/src/orchestrator/compaction.d.ts +12 -3
- package/dist/src/orchestrator/compaction.js +35 -15
- package/dist/src/orchestrator/council-manager.d.ts +12 -3
- package/dist/src/orchestrator/council-manager.js +74 -32
- package/dist/src/orchestrator/council-request.d.ts +49 -0
- package/dist/src/orchestrator/council-request.js +62 -0
- package/dist/src/orchestrator/cross-turn-dedup.d.ts +6 -0
- package/dist/src/orchestrator/cross-turn-dedup.js +22 -1
- package/dist/src/orchestrator/error-utils.d.ts +29 -0
- package/dist/src/orchestrator/error-utils.js +132 -24
- package/dist/src/orchestrator/grounding-check.js +39 -1
- package/dist/src/orchestrator/interactive-pause.d.ts +26 -0
- package/dist/src/orchestrator/interactive-pause.js +36 -0
- package/dist/src/orchestrator/message-processor.d.ts +4 -0
- package/dist/src/orchestrator/message-processor.js +268 -41
- package/dist/src/orchestrator/orchestrator.d.ts +64 -3
- package/dist/src/orchestrator/orchestrator.js +823 -120
- package/dist/src/orchestrator/preprocessor.js +3 -3
- package/dist/src/orchestrator/proactive-compact-detector.d.ts +26 -0
- package/dist/src/orchestrator/proactive-compact-detector.js +36 -0
- package/dist/src/orchestrator/prompts.js +17 -17
- package/dist/src/orchestrator/reactive-delegation.d.ts +39 -0
- package/dist/src/orchestrator/reactive-delegation.js +59 -0
- package/dist/src/orchestrator/retry-classifier.d.ts +2 -1
- package/dist/src/orchestrator/retry-classifier.js +46 -2
- package/dist/src/orchestrator/safety-askcard.d.ts +1 -1
- package/dist/src/orchestrator/safety-askcard.js +5 -2
- package/dist/src/orchestrator/safety-intercept.d.ts +50 -0
- package/dist/src/orchestrator/safety-intercept.js +62 -0
- package/dist/src/orchestrator/scope-reminder.js +1 -1
- package/dist/src/orchestrator/session-experience.d.ts +2 -1
- package/dist/src/orchestrator/session-experience.js +2 -1
- package/dist/src/orchestrator/should-run-gate.d.ts +5 -0
- package/dist/src/orchestrator/should-run-gate.js +18 -0
- package/dist/src/orchestrator/stall-watchdog.d.ts +31 -3
- package/dist/src/orchestrator/stall-watchdog.js +65 -10
- package/dist/src/orchestrator/stream-runner.d.ts +13 -3
- package/dist/src/orchestrator/stream-runner.js +115 -49
- package/dist/src/orchestrator/sub-agent-cap.d.ts +13 -1
- package/dist/src/orchestrator/sub-agent-cap.js +16 -1
- package/dist/src/orchestrator/sub-agent-model-tier.d.ts +13 -1
- package/dist/src/orchestrator/sub-agent-model-tier.js +16 -2
- package/dist/src/orchestrator/subagent-compactor.d.ts +53 -1
- package/dist/src/orchestrator/subagent-compactor.js +126 -15
- package/dist/src/orchestrator/tool-engine.d.ts +41 -0
- package/dist/src/orchestrator/tool-engine.js +846 -66
- package/dist/src/orchestrator/tool-limit-auto-recover.d.ts +22 -0
- package/dist/src/orchestrator/tool-limit-auto-recover.js +30 -0
- package/dist/src/orchestrator/turn-runner-deps.d.ts +19 -0
- package/dist/src/orchestrator/turn-watchdog.d.ts +44 -0
- package/dist/src/orchestrator/turn-watchdog.js +84 -0
- package/dist/src/pil/agent-operating-contract.d.ts +1 -1
- package/dist/src/pil/agent-operating-contract.js +6 -4
- package/dist/src/pil/cheap-model-playbook.d.ts +1 -1
- package/dist/src/pil/cheap-model-playbook.js +5 -1
- package/dist/src/pil/cheap-model-workbooks.d.ts +1 -1
- package/dist/src/pil/cheap-model-workbooks.js +1 -1
- package/dist/src/pil/discovery-types.d.ts +1 -0
- package/dist/src/pil/discovery.d.ts +1 -1
- package/dist/src/pil/discovery.js +18 -13
- package/dist/src/pil/layer1-intent.d.ts +18 -6
- package/dist/src/pil/layer1-intent.js +66 -757
- package/dist/src/pil/layer15-context-scan.js +15 -1
- package/dist/src/pil/layer1_5-complexity-size.d.ts +7 -0
- package/dist/src/pil/layer1_5-complexity-size.js +31 -5
- package/dist/src/pil/layer3-ee-injection.js +23 -8
- package/dist/src/pil/layer4-gsd.js +69 -16
- package/dist/src/pil/layer5-context.js +7 -3
- package/dist/src/pil/layer6-output.d.ts +23 -0
- package/dist/src/pil/layer6-output.js +5 -1
- package/dist/src/pil/llm-classify.d.ts +111 -5
- package/dist/src/pil/llm-classify.js +421 -189
- package/dist/src/pil/native-capabilities-workbook.d.ts +1 -1
- package/dist/src/pil/native-capabilities-workbook.js +8 -0
- package/dist/src/pil/pipeline.js +36 -2
- package/dist/src/pil/repo-grounding-probe.d.ts +15 -0
- package/dist/src/pil/repo-grounding-probe.js +136 -0
- package/dist/src/pil/repo-structure-hints.d.ts +7 -0
- package/dist/src/pil/repo-structure-hints.js +45 -0
- package/dist/src/pil/response-tools.js +5 -3
- package/dist/src/pil/schema.d.ts +1 -0
- package/dist/src/pil/schema.js +2 -0
- package/dist/src/pil/types.d.ts +18 -0
- package/dist/src/playbook/directives.d.ts +4 -0
- package/dist/src/playbook/directives.js +17 -5
- package/dist/src/product-loop/artifact-io.js +4 -0
- package/dist/src/product-loop/backlog-builder.d.ts +14 -1
- package/dist/src/product-loop/backlog-builder.js +30 -6
- package/dist/src/product-loop/criteria-seed.d.ts +51 -0
- package/dist/src/product-loop/criteria-seed.js +200 -0
- package/dist/src/product-loop/discovery-context-format.js +3 -1
- package/dist/src/product-loop/discovery-ecosystem.js +4 -1
- package/dist/src/product-loop/discovery-interview.d.ts +9 -0
- package/dist/src/product-loop/discovery-interview.js +60 -12
- package/dist/src/product-loop/discovery-recommender.js +2 -1
- package/dist/src/product-loop/discovery-schema.js +19 -2
- package/dist/src/product-loop/discovery-triage.d.ts +23 -0
- package/dist/src/product-loop/discovery-triage.js +109 -0
- package/dist/src/product-loop/gather.js +150 -2
- package/dist/src/product-loop/ideal-trace.d.ts +7 -0
- package/dist/src/product-loop/ideal-trace.js +64 -0
- package/dist/src/product-loop/index.d.ts +13 -1
- package/dist/src/product-loop/index.js +340 -52
- package/dist/src/product-loop/loop-driver.d.ts +7 -0
- package/dist/src/product-loop/loop-driver.js +330 -106
- package/dist/src/product-loop/phase-plan.d.ts +21 -0
- package/dist/src/product-loop/phase-plan.js +81 -6
- package/dist/src/product-loop/phase-rituals.d.ts +3 -0
- package/dist/src/product-loop/phase-rituals.js +8 -3
- package/dist/src/product-loop/phase-runner.js +39 -12
- package/dist/src/product-loop/plan-adherence-review.d.ts +26 -0
- package/dist/src/product-loop/plan-adherence-review.js +144 -0
- package/dist/src/product-loop/sprint-runner.d.ts +173 -0
- package/dist/src/product-loop/sprint-runner.js +863 -19
- package/dist/src/product-loop/types.d.ts +61 -5
- package/dist/src/providers/adapter.d.ts +1 -1
- package/dist/src/providers/adapter.js +3 -4
- package/dist/src/providers/anthropic.d.ts +9 -8
- package/dist/src/providers/anthropic.js +13 -47
- package/dist/src/providers/auth/browser-flow.d.ts +1 -1
- package/dist/src/providers/auth/browser-flow.js +1 -1
- package/dist/src/providers/auth/grok-oauth.d.ts +1 -0
- package/dist/src/providers/auth/grok-oauth.js +30 -5
- package/dist/src/providers/auth/openai-oauth.d.ts +1 -0
- package/dist/src/providers/auth/openai-oauth.js +15 -1
- package/dist/src/providers/auth/registry.js +0 -34
- package/dist/src/providers/auth/token-store.d.ts +9 -9
- package/dist/src/providers/auth/token-store.js +8 -67
- package/dist/src/providers/auth/types.d.ts +9 -1
- package/dist/src/providers/auth/types.js +1 -1
- package/dist/src/providers/capabilities.d.ts +24 -5
- package/dist/src/providers/capabilities.js +42 -24
- package/dist/src/providers/endpoints.d.ts +2 -2
- package/dist/src/providers/endpoints.js +11 -10
- package/dist/src/providers/env-store.d.ts +17 -0
- package/dist/src/providers/env-store.js +228 -0
- package/dist/src/providers/keychain.d.ts +22 -18
- package/dist/src/providers/keychain.js +127 -140
- package/dist/src/providers/mcp-vision-bridge.js +56 -146
- package/dist/src/providers/openai-compatible.js +8 -1
- package/dist/src/providers/pricing.d.ts +2 -2
- package/dist/src/providers/pricing.js +3 -13
- package/dist/src/providers/runtime.d.ts +43 -3
- package/dist/src/providers/runtime.js +88 -14
- package/dist/src/providers/strategies/base.strategy.d.ts +16 -0
- package/dist/src/providers/strategies/base.strategy.js +24 -1
- package/dist/src/providers/strategies/{siliconflow.strategy.d.ts → opencode-go.strategy.d.ts} +3 -3
- package/dist/src/providers/strategies/opencode-go.strategy.js +83 -0
- package/dist/src/providers/strategies/registry.js +4 -4
- package/dist/src/providers/strategies/thinking-mode.d.ts +109 -0
- package/dist/src/providers/strategies/thinking-mode.js +288 -1
- package/dist/src/providers/strategies/xai.strategy.js +27 -0
- package/dist/src/providers/strategies/zai.strategy.d.ts +14 -0
- package/dist/src/providers/strategies/zai.strategy.js +44 -0
- package/dist/src/providers/types.d.ts +5 -6
- package/dist/src/providers/types.js +2 -2
- package/dist/src/providers/vision-backend.d.ts +47 -0
- package/dist/src/providers/vision-backend.js +258 -0
- package/dist/src/providers/vision-proxy.d.ts +22 -9
- package/dist/src/providers/vision-proxy.js +63 -132
- package/dist/src/providers/warm.d.ts +65 -0
- package/dist/src/providers/warm.js +145 -0
- package/dist/src/providers/wire-debug.js +95 -0
- package/dist/src/router/decide.d.ts +13 -0
- package/dist/src/router/decide.js +138 -36
- package/dist/src/router/peak-hour.d.ts +38 -0
- package/dist/src/router/peak-hour.js +107 -0
- package/dist/src/router/step-router.js +3 -2
- package/dist/src/router/warm.js +4 -5
- package/dist/src/scaffold/continuation-prompt.d.ts +11 -0
- package/dist/src/scaffold/continuation-prompt.js +26 -0
- package/dist/src/scaffold/point-to-existing.d.ts +21 -0
- package/dist/src/scaffold/point-to-existing.js +25 -0
- package/dist/src/self-qa/agentic-loop.js +6 -5
- package/dist/src/{ui/state → state}/active-run.d.ts +19 -0
- package/dist/src/{ui/state → state}/active-run.js +21 -0
- package/dist/src/{ui/status-bar/store.d.ts → state/status-bar-store.d.ts} +4 -1
- package/dist/src/{ui/status-bar/store.js → state/status-bar-store.js} +12 -9
- package/dist/src/state/turn-trace.d.ts +43 -0
- package/dist/src/state/turn-trace.js +32 -0
- package/dist/src/storage/db.js +2 -1
- package/dist/src/storage/index.d.ts +1 -1
- package/dist/src/storage/index.js +1 -1
- package/dist/src/storage/interaction-log.d.ts +1 -1
- package/dist/src/storage/migrations.js +71 -1
- package/dist/src/storage/sessions.d.ts +28 -10
- package/dist/src/storage/sessions.js +78 -21
- package/dist/src/storage/transcript-view.js +1 -1
- package/dist/src/storage/transcript.d.ts +51 -0
- package/dist/src/storage/transcript.js +340 -15
- package/dist/src/tools/file.d.ts +15 -0
- package/dist/src/tools/file.js +32 -0
- package/dist/src/tools/git-safety.d.ts +19 -0
- package/dist/src/tools/git-safety.js +168 -0
- package/dist/src/tools/native-tools.d.ts +1 -1
- package/dist/src/tools/native-tools.js +81 -1
- package/dist/src/tools/registry.d.ts +20 -0
- package/dist/src/tools/registry.js +576 -23
- package/dist/src/tools/research.d.ts +29 -0
- package/dist/src/tools/research.js +233 -0
- package/dist/src/types/index.d.ts +147 -4
- package/dist/src/ui/app.js +0 -0
- package/dist/src/ui/cards/product-status-card.js +1 -1
- package/dist/src/ui/components/agent-rail-activities.d.ts +26 -0
- package/dist/src/ui/components/agent-rail-activities.js +47 -0
- package/dist/src/ui/components/bubble-body-guard.d.ts +27 -0
- package/dist/src/ui/components/bubble-body-guard.js +50 -0
- package/dist/src/ui/components/compact-progress-card.d.ts +24 -0
- package/dist/src/ui/components/compact-progress-card.js +42 -0
- package/dist/src/ui/components/context-rail.d.ts +26 -0
- package/dist/src/ui/components/context-rail.js +33 -0
- package/dist/src/ui/components/council-conclusion-card.d.ts +73 -0
- package/dist/src/ui/components/council-conclusion-card.js +420 -0
- package/dist/src/ui/components/council-debate-pill.d.ts +36 -0
- package/dist/src/ui/components/council-debate-pill.js +34 -0
- package/dist/src/ui/components/council-info-card.js +2 -2
- package/dist/src/ui/components/council-leader-bubble.d.ts +10 -3
- package/dist/src/ui/components/council-leader-bubble.js +21 -11
- package/dist/src/ui/components/council-message-bubble.d.ts +15 -2
- package/dist/src/ui/components/council-message-bubble.js +16 -15
- package/dist/src/ui/components/council-phase-timeline.d.ts +9 -1
- package/dist/src/ui/components/council-phase-timeline.js +66 -17
- package/dist/src/ui/components/council-placeholder-bubble.d.ts +16 -9
- package/dist/src/ui/components/council-placeholder-bubble.js +32 -29
- package/dist/src/ui/components/council-question-card.js +13 -12
- package/dist/src/ui/components/council-rail-rounds.d.ts +26 -0
- package/dist/src/ui/components/council-rail-rounds.js +57 -0
- package/dist/src/ui/components/council-round-group.d.ts +38 -0
- package/dist/src/ui/components/council-round-group.js +88 -0
- package/dist/src/ui/components/council-status-list.d.ts +3 -1
- package/dist/src/ui/components/council-status-list.js +36 -24
- package/dist/src/ui/components/council-synthesis-banner.d.ts +7 -2
- package/dist/src/ui/components/council-synthesis-banner.js +20 -5
- package/dist/src/ui/components/halt-recovery-card.js +9 -5
- package/dist/src/ui/components/jump-to-latest-pill.d.ts +11 -0
- package/dist/src/ui/components/jump-to-latest-pill.js +14 -0
- package/dist/src/ui/components/message-view.d.ts +15 -0
- package/dist/src/ui/components/message-view.js +50 -1
- package/dist/src/ui/components/prompt-box.js +18 -16
- package/dist/src/ui/components/session-tree-card.d.ts +14 -0
- package/dist/src/ui/components/session-tree-card.js +46 -0
- package/dist/src/ui/components/slash-inline-menu.d.ts +12 -0
- package/dist/src/ui/components/slash-inline-menu.js +26 -5
- package/dist/src/ui/components/task-list-panel.d.ts +14 -1
- package/dist/src/ui/components/task-list-panel.js +22 -2
- package/dist/src/ui/components/tool-group.d.ts +15 -3
- package/dist/src/ui/components/tool-group.js +69 -11
- package/dist/src/ui/containers/modals-layer.d.ts +4 -2
- package/dist/src/ui/containers/modals-layer.js +2 -2
- package/dist/src/ui/council-harness-event.d.ts +57 -0
- package/dist/src/ui/council-harness-event.js +46 -0
- package/dist/src/ui/heartbeat-debug.d.ts +29 -0
- package/dist/src/ui/heartbeat-debug.js +45 -0
- package/dist/src/ui/hooks/use-session-picker.d.ts +3 -3
- package/dist/src/ui/mcp-modal.js +2 -4
- package/dist/src/ui/modals/api-key-modal.js +1 -1
- package/dist/src/ui/modals/connect-modal.js +4 -3
- package/dist/src/ui/modals/model-picker-modal.d.ts +8 -18
- package/dist/src/ui/modals/model-picker-modal.js +8 -10
- package/dist/src/ui/modals/session-picker-modal.d.ts +2 -2
- package/dist/src/ui/modals/session-picker-modal.js +3 -5
- package/dist/src/ui/picker-providers.d.ts +1 -1
- package/dist/src/ui/picker-providers.js +1 -1
- package/dist/src/ui/primitives/index.d.ts +1 -0
- package/dist/src/ui/primitives/index.js +2 -0
- package/dist/src/ui/primitives/semantic-primitives.d.ts +76 -0
- package/dist/src/ui/primitives/semantic-primitives.js +81 -0
- package/dist/src/ui/slash/compact.js +5 -7
- package/dist/src/ui/slash/cost.js +1 -1
- package/dist/src/ui/slash/council.js +19 -1
- package/dist/src/ui/slash/debug.d.ts +3 -31
- package/dist/src/ui/slash/debug.js +9 -20
- package/dist/src/ui/slash/ee.js +81 -0
- package/dist/src/ui/slash/ideal.d.ts +6 -2
- package/dist/src/ui/slash/ideal.js +97 -7
- package/dist/src/ui/slash/menu-items.d.ts +7 -0
- package/dist/src/ui/slash/menu-items.js +23 -20
- package/dist/src/ui/slash/registry.d.ts +2 -0
- package/dist/src/ui/slash/registry.js +4 -0
- package/dist/src/ui/status-bar/cache-hit.d.ts +6 -0
- package/dist/src/ui/status-bar/cache-hit.js +9 -0
- package/dist/src/ui/status-bar/index.d.ts +1 -1
- package/dist/src/ui/status-bar/index.js +7 -3
- package/dist/src/ui/status-bar/usd-meter.d.ts +5 -4
- package/dist/src/ui/status-bar/usd-meter.js +6 -4
- package/dist/src/ui/theme.d.ts +1 -0
- package/dist/src/ui/theme.js +2 -0
- package/dist/src/ui/types.d.ts +7 -0
- package/dist/src/ui/use-app-logic.js +0 -0
- package/dist/src/ui/utils/agent-activities.d.ts +39 -0
- package/dist/src/ui/utils/agent-activities.js +96 -0
- package/dist/src/ui/utils/format.d.ts +14 -0
- package/dist/src/ui/utils/format.js +23 -3
- package/dist/src/ui/utils/group-tool-entries.d.ts +26 -0
- package/dist/src/ui/utils/group-tool-entries.js +111 -0
- package/dist/src/ui/utils/tool-summary.d.ts +21 -0
- package/dist/src/ui/utils/tool-summary.js +91 -0
- package/dist/src/usage/downgrade.js +2 -2
- package/dist/src/usage/product-ledger.js +2 -2
- package/dist/src/utils/event-loop-monitor.d.ts +85 -0
- package/dist/src/utils/event-loop-monitor.js +107 -0
- package/dist/src/utils/install-manager.js +2 -1
- package/dist/src/utils/llm-deadline.d.ts +14 -0
- package/dist/src/utils/llm-deadline.js +19 -0
- package/dist/src/utils/logger.js +2 -2
- package/dist/src/utils/loop-profiler.d.ts +102 -0
- package/dist/src/utils/loop-profiler.js +202 -0
- package/dist/src/utils/permission-mode.js +5 -3
- package/dist/src/utils/redactor.js +1 -1
- package/dist/src/utils/settings.d.ts +180 -5
- package/dist/src/utils/settings.js +271 -31
- package/dist/src/utils/side-question.d.ts +1 -2
- package/dist/src/utils/side-question.js +2 -2
- package/dist/src/utils/visible-retry.d.ts +11 -0
- package/dist/src/utils/visible-retry.js +10 -1
- package/dist/src/verify/entrypoint.d.ts +1 -1
- package/dist/src/verify/entrypoint.js +52 -17
- package/dist/src/verify/orchestrator.d.ts +1 -1
- package/dist/src/verify/orchestrator.js +20 -3
- package/dist/src/verify/recipes.d.ts +13 -0
- package/dist/src/verify/recipes.js +15 -0
- package/package.json +134 -132
- package/dist/src/cli/bw-vault.d.ts +0 -55
- package/dist/src/cli/bw-vault.js +0 -133
- package/dist/src/mcp/ee-tools.d.ts +0 -46
- package/dist/src/mcp/ee-tools.js +0 -193
- package/dist/src/providers/auth/gcloud.d.ts +0 -28
- package/dist/src/providers/auth/gcloud.js +0 -102
- package/dist/src/providers/auth/gemini-oauth.d.ts +0 -82
- package/dist/src/providers/auth/gemini-oauth.js +0 -472
- package/dist/src/providers/gemini.d.ts +0 -11
- package/dist/src/providers/gemini.js +0 -45
- package/dist/src/providers/siliconflow-sse-repair.d.ts +0 -58
- package/dist/src/providers/siliconflow-sse-repair.js +0 -177
- package/dist/src/providers/strategies/google.strategy.d.ts +0 -22
- package/dist/src/providers/strategies/google.strategy.js +0 -174
- package/dist/src/providers/strategies/siliconflow.strategy.js +0 -29
- package/dist/src/ui/containers/chat-feed.d.ts +0 -40
- package/dist/src/ui/containers/chat-feed.js +0 -66
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import { queryExperience } from "../ee/council-bridge.js";
|
|
2
2
|
import { judgeCouncilOutcome } from "../ee/judge.js";
|
|
3
3
|
import { recordCouncilOutcome } from "../ee/phase-outcome.js";
|
|
4
|
+
import { isTaskAwarePanelEnabled } from "../gsd/flags.js";
|
|
4
5
|
import { runPipeline } from "../pil/pipeline.js";
|
|
6
|
+
import { idealTrace } from "../product-loop/ideal-trace.js";
|
|
5
7
|
import { appendSystemMessage, logInteraction } from "../storage/index.js";
|
|
6
8
|
import { SessionStore } from "../storage/sessions.js";
|
|
7
9
|
import { getCouncilExperienceMode, isCouncilCostAware, isCouncilMultiProviderPreferred } from "../utils/settings.js";
|
|
@@ -11,7 +13,8 @@ import { evaluateResearchNeed, runDebate } from "./debate.js";
|
|
|
11
13
|
import { planDebate } from "./debate-planner.js";
|
|
12
14
|
import { detectOutOfStackProposals, writeDecisionsLock } from "./decisions-lock.js";
|
|
13
15
|
import { runExecution } from "./executor.js";
|
|
14
|
-
import { resolveLeaderModelDetailed, resolveParticipants } from "./leader.js";
|
|
16
|
+
import { buildCouncilCandidatePool, resolveLeaderModelDetailed, resolveParticipants } from "./leader.js";
|
|
17
|
+
import { selectTaskAwarePanel } from "./panel-select.js";
|
|
15
18
|
import { phaseDone, phaseStart } from "./phase-events.js";
|
|
16
19
|
import { runPlanning } from "./planner.js";
|
|
17
20
|
import { runPreflight } from "./preflight.js";
|
|
@@ -59,6 +62,22 @@ const EXPLICIT_COUNCIL_CLARIFY_ROUNDS = 1;
|
|
|
59
62
|
* generate_plan OPTION is still offered downstream; it's just no longer the
|
|
60
63
|
* pre-selected default for non-build topics.
|
|
61
64
|
*/
|
|
65
|
+
/**
|
|
66
|
+
* F1 — summarize how the debate did against its PINNED success criteria, so the
|
|
67
|
+
* post-debate card can distinguish "the criteria were actually met" from "the
|
|
68
|
+
* synthesis reads confidently" (evidence density). `metFlags` is index-aligned
|
|
69
|
+
* to `pinned` (from DebateState.finalCriteriaMet); a missing/short array treats
|
|
70
|
+
* the unmapped criteria as not-met. `inconclusive` is true when the spec had
|
|
71
|
+
* pinned criteria and at least one is still open — the caller ANDs this with
|
|
72
|
+
* `!synthesisFailed` before reframing the card.
|
|
73
|
+
*/
|
|
74
|
+
export function summarizeCriteriaOutcome(pinned, metFlags) {
|
|
75
|
+
const flags = metFlags ?? [];
|
|
76
|
+
const total = pinned.length;
|
|
77
|
+
const metCount = pinned.filter((_, i) => flags[i] === true).length;
|
|
78
|
+
const unmetLabels = pinned.filter((_, i) => flags[i] !== true);
|
|
79
|
+
return { total, metCount, unmetLabels, inconclusive: total > 0 && unmetLabels.length > 0 };
|
|
80
|
+
}
|
|
62
81
|
export function pickPostDebateRecommendation(input) {
|
|
63
82
|
if (input.synthesisFailed) {
|
|
64
83
|
return {
|
|
@@ -66,6 +85,13 @@ export function pickPostDebateRecommendation(input) {
|
|
|
66
85
|
reason: "Re-run synthesis with a compact prompt — usually clears provider-timeout failures.",
|
|
67
86
|
};
|
|
68
87
|
}
|
|
88
|
+
if (input.criteriaUnmet && input.criteriaUnmet > 0) {
|
|
89
|
+
const n = input.criteriaUnmet;
|
|
90
|
+
return {
|
|
91
|
+
value: "ask_followup",
|
|
92
|
+
reason: `${n} success criteri${n === 1 ? "on" : "a"} still unmet — press the council to close ${n === 1 ? "it" : "them"} before treating this as settled.`,
|
|
93
|
+
};
|
|
94
|
+
}
|
|
69
95
|
if (input.hasEmptySections) {
|
|
70
96
|
return { value: "refine", reason: `Fill in ${input.refinementTopics.length} section(s) the debate left empty.` };
|
|
71
97
|
}
|
|
@@ -85,6 +111,96 @@ export function pickPostDebateRecommendation(input) {
|
|
|
85
111
|
}
|
|
86
112
|
return { value: "save_exit", reason: "Outcome looks solid — save and move on." };
|
|
87
113
|
}
|
|
114
|
+
/**
|
|
115
|
+
* Decide whether — and with what prompt — the agent session should keep working
|
|
116
|
+
* after the post-debate askcard, given the action the user chose.
|
|
117
|
+
*
|
|
118
|
+
* Single source of truth for BOTH continuation callers (the `/council` slash path
|
|
119
|
+
* in orchestrator.runCouncilV2 and the auto-council path in tool-engine), which
|
|
120
|
+
* previously diverged: the slash path only continued on `continue_session`, while
|
|
121
|
+
* auto-council continued UNCONDITIONALLY with a fixed "Proceed with the recommended
|
|
122
|
+
* action items" prompt — meaningless for an evaluation/decision debate that has no
|
|
123
|
+
* action items, so the chosen action was effectively ignored.
|
|
124
|
+
*
|
|
125
|
+
* Returns the re-entry prompt to feed back into processMessage, or `null` to stop
|
|
126
|
+
* at the composer (the synthesis IS the deliverable).
|
|
127
|
+
* - continue_session → carry the conclusion forward on the ORIGINAL task, but
|
|
128
|
+
* ONLY for an implementation-shaped debate. For an analysis/evaluation debate
|
|
129
|
+
* the conclusion IS the deliverable, so re-enter WITHOUT an implementation
|
|
130
|
+
* mandate (session 578b2eae7099: "Continue the original task using this
|
|
131
|
+
* conclusion" on an evaluation made the model invent phantom Phase-1..7 todos
|
|
132
|
+
* and start editing files, then the rogue turn wedged the UI).
|
|
133
|
+
* - generate_plan / implement → execute the recommended action items.
|
|
134
|
+
* - save_exit / refine / retry_synthesis / follow-up / undefined → stop (those
|
|
135
|
+
* either already re-synthesized inside runCouncil or are terminal by intent).
|
|
136
|
+
*/
|
|
137
|
+
const IMPLEMENTATION_OUTPUT_KINDS = new Set(["implementation_plan"]);
|
|
138
|
+
/** Recover the output-shape kind the synthesis was produced under (```json { "type": … }). */
|
|
139
|
+
function synthesisOutputKind(synthesis) {
|
|
140
|
+
const m = synthesis.match(/"type"\s*:\s*"([^"]+)"/);
|
|
141
|
+
return m?.[1];
|
|
142
|
+
}
|
|
143
|
+
export function postDebateContinuation(action, synthesis, outputKind) {
|
|
144
|
+
if (!synthesis || !action)
|
|
145
|
+
return null;
|
|
146
|
+
// IMPLEMENT — the user decided there is enough to build. Load the council
|
|
147
|
+
// conclusion back as the approved spec and carry it out through the normal
|
|
148
|
+
// workflow (the native GSD depth pipeline plans → executes → verifies). Works
|
|
149
|
+
// for ANY output kind: an analysis/decision synthesis is itself a sufficient
|
|
150
|
+
// spec, so this no longer needs a separate plan artifact. Scoped so the agent
|
|
151
|
+
// builds exactly what was decided and cannot balloon into phantom phases.
|
|
152
|
+
if (action === "generate_plan" || action === "implement") {
|
|
153
|
+
return (`Council debate completed. Approved conclusion:\n\n${synthesis}\n\n` +
|
|
154
|
+
`Implement this now. Treat the council conclusion above as the approved spec ` +
|
|
155
|
+
`— load it as your working context and carry it out through your normal ` +
|
|
156
|
+
`workflow: plan the concrete steps, make the changes in the smallest correct ` +
|
|
157
|
+
`increments, and verify (build/tests) as you go. Do NOT re-litigate the ` +
|
|
158
|
+
`decision or expand scope beyond it. If a required detail is genuinely ` +
|
|
159
|
+
`ambiguous, ask ONE focused question before editing.`);
|
|
160
|
+
}
|
|
161
|
+
if (action === "continue_session") {
|
|
162
|
+
const kind = outputKind ?? synthesisOutputKind(synthesis);
|
|
163
|
+
// Only an implementation-shaped debate has an "original task" left to build
|
|
164
|
+
// (the /ideal build flow relies on this carry-forward — do NOT null it out).
|
|
165
|
+
if (kind && IMPLEMENTATION_OUTPUT_KINDS.has(kind)) {
|
|
166
|
+
return `Council debate completed. Conclusion:\n\n${synthesis}\n\nContinue the original task using this conclusion.`;
|
|
167
|
+
}
|
|
168
|
+
// Analysis/evaluation/decision/investigation (or unknown → analysis): the
|
|
169
|
+
// user chose to KEEP THE SESSION GOING without implementing. Stop at the
|
|
170
|
+
// composer — the synthesis was already shown on the debate card and is
|
|
171
|
+
// persisted as [Council Decision]/[Council Memory] system messages, so the
|
|
172
|
+
// user's NEXT message inherits the full council context automatically
|
|
173
|
+
// (buildCouncilContextBundle surfaces it under "Key Decisions"). Returning
|
|
174
|
+
// null avoids the wasteful re-present turn AND the old forbid lecture, while
|
|
175
|
+
// still preventing the phantom-implementation drift (nothing runs). To
|
|
176
|
+
// actually build, the user picks Implement above; to keep discussing, they
|
|
177
|
+
// just type — that turn inherits the council context.
|
|
178
|
+
return null;
|
|
179
|
+
}
|
|
180
|
+
return null;
|
|
181
|
+
}
|
|
182
|
+
/**
|
|
183
|
+
* Neutral post-council continuation. Used by the auto-council path (tool-engine)
|
|
184
|
+
* and the `/council` slash path (runCouncilV2) once they run with
|
|
185
|
+
* `convenePath: true` — the hardcoded post-debate option card is suppressed, so
|
|
186
|
+
* there is no `chosenAction` to branch on. Instead of the CLI deciding the next
|
|
187
|
+
* step, we hand the synthesis back to a normal agent turn with a NON-BINDING
|
|
188
|
+
* nudge and let the agent's own intent drive the follow-up (respond / ask_user /
|
|
189
|
+
* implement). Returns "" for an empty synthesis so the caller skips re-entry.
|
|
190
|
+
*/
|
|
191
|
+
export function buildNeutralPostCouncilContinuation(synthesis) {
|
|
192
|
+
if (!synthesis || !synthesis.trim())
|
|
193
|
+
return "";
|
|
194
|
+
return (`Council debate completed. Conclusion:\n\n${synthesis}\n\n` +
|
|
195
|
+
`You now decide the next step based on the user's original request — do not ` +
|
|
196
|
+
`stop without doing one of these:\n` +
|
|
197
|
+
` • If the conclusion IS the deliverable (analysis/evaluation/decision), ` +
|
|
198
|
+
`respond to the user with it.\n` +
|
|
199
|
+
` • If a choice genuinely needs the human before proceeding, call ask_user.\n` +
|
|
200
|
+
` • If the task calls for building and the conclusion is a sufficient spec, ` +
|
|
201
|
+
`implement it now through your normal workflow — do NOT re-litigate the ` +
|
|
202
|
+
`decision or expand scope beyond it.`);
|
|
203
|
+
}
|
|
88
204
|
export async function* runCouncil(topic, sessionModelId, messages, sessionId, rawLlm, respondToQuestion, respondToPreflight, processMessageFn, options) {
|
|
89
205
|
const stats = options?.councilStats ?? { calls: 0, startMs: Date.now(), phases: [] };
|
|
90
206
|
const costAware = isCouncilCostAware();
|
|
@@ -103,7 +219,21 @@ export async function* runCouncil(topic, sessionModelId, messages, sessionId, ra
|
|
|
103
219
|
// ── Resolve models ──────────────────────────────────────────────────────────
|
|
104
220
|
const leaderResolution = await resolveLeaderModelDetailed(sessionModelId);
|
|
105
221
|
const leaderModelId = leaderResolution.modelId;
|
|
106
|
-
|
|
222
|
+
let participants = await resolveParticipants(sessionModelId, isCouncilMultiProviderPreferred());
|
|
223
|
+
// U3 — task-aware panel: let the leader read the task and pick which reachable
|
|
224
|
+
// models should debate it, instead of the prompt-blind capability roster.
|
|
225
|
+
// Fails open to the default roster on any provider/parse failure.
|
|
226
|
+
if (participants.length >= 2 && isTaskAwarePanelEnabled()) {
|
|
227
|
+
try {
|
|
228
|
+
const pool = await buildCouncilCandidatePool(participants);
|
|
229
|
+
const taskAware = yield* selectTaskAwarePanel({ topic, pool, leaderModelId, llm });
|
|
230
|
+
if (taskAware && taskAware.length >= 2)
|
|
231
|
+
participants = taskAware;
|
|
232
|
+
}
|
|
233
|
+
catch {
|
|
234
|
+
/* fail-open — keep the default roster */
|
|
235
|
+
}
|
|
236
|
+
}
|
|
107
237
|
if (participants.length < 2) {
|
|
108
238
|
yield {
|
|
109
239
|
type: "content",
|
|
@@ -112,21 +242,48 @@ export async function* runCouncil(topic, sessionModelId, messages, sessionId, ra
|
|
|
112
242
|
yield { type: "done" };
|
|
113
243
|
return null;
|
|
114
244
|
}
|
|
115
|
-
|
|
245
|
+
// When the TUI Context Rail is active it renders the leader/panel/cost data as
|
|
246
|
+
// ambient sidebar rows from the council_meta patch below, so emitting the same
|
|
247
|
+
// data inline would both duplicate it AND read as a roster "decided" before any
|
|
248
|
+
// task assessment. Railless sinks (headless, telegram) keep the inline summary.
|
|
249
|
+
const suppressInlineMeta = options?.suppressInlineMeta === true;
|
|
250
|
+
if (!suppressInlineMeta) {
|
|
251
|
+
if (leaderResolution.promotedFrom) {
|
|
252
|
+
yield {
|
|
253
|
+
type: "content",
|
|
254
|
+
content: `\n> Leader auto-promoted within session provider: \`${leaderResolution.promotedFrom.modelId}\`` +
|
|
255
|
+
`${leaderResolution.promotedFrom.tier ? ` (${leaderResolution.promotedFrom.tier})` : ""}` +
|
|
256
|
+
` → \`${leaderModelId}\`. Synthesis benefits from the highest tier available on the same provider. ` +
|
|
257
|
+
`Set \`roleModels.leader\` to override.\n`,
|
|
258
|
+
};
|
|
259
|
+
}
|
|
116
260
|
yield {
|
|
117
261
|
type: "content",
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
262
|
+
// Show models only — the `implement/verify/research` roles are internal
|
|
263
|
+
// cost-tier routing slots, NOT debate personas (those are task-adaptive and
|
|
264
|
+
// shown in the Debate Plan card once assigned). Printing the slot names here
|
|
265
|
+
// misleadingly implied implementation intent on analysis/decision topics.
|
|
266
|
+
content: `\n> Leader: \`${leaderModelId}\` · Panel: ${participants.map((p) => `\`${p.model}\``).join(", ")}${costAware ? " · Cost-aware sub-tasks: ON" : ""}\n`,
|
|
122
267
|
};
|
|
123
268
|
}
|
|
269
|
+
// P3 — mirror the leader/panel/cost metadata as a structured council_meta patch
|
|
270
|
+
// so the context rail can show it as rows instead of transcript spam. The round
|
|
271
|
+
// budget/ceiling arrive later from inside runDebate (locals unavailable here).
|
|
124
272
|
yield {
|
|
125
|
-
type: "
|
|
126
|
-
|
|
273
|
+
type: "council_meta",
|
|
274
|
+
councilMeta: {
|
|
275
|
+
topic,
|
|
276
|
+
leader: leaderModelId,
|
|
277
|
+
panel: participants.map((p) => p.model),
|
|
278
|
+
costAware,
|
|
279
|
+
},
|
|
127
280
|
};
|
|
128
281
|
const baseContext = buildCouncilContext(messages);
|
|
129
|
-
|
|
282
|
+
// Fall back to process.cwd() when the caller omits cwd. The old default of
|
|
283
|
+
// { isEmpty: true } forced internet-first research (and skipped codebase-first
|
|
284
|
+
// analysis) even when the council was invoked inside a real repo.
|
|
285
|
+
const projectCwd = options?.cwd ?? process.cwd();
|
|
286
|
+
const projectInfo = await buildProjectSnapshot(projectCwd);
|
|
130
287
|
const conversationContext = projectInfo.snapshot
|
|
131
288
|
? `## Current Project\n${projectInfo.snapshot}\n\n---\n\n${baseContext}`
|
|
132
289
|
: baseContext;
|
|
@@ -163,7 +320,7 @@ export async function* runCouncil(topic, sessionModelId, messages, sessionId, ra
|
|
|
163
320
|
content: `\n> Clarification seeded by PIL (${pilSeed.length} gray-area question${pilSeed.length === 1 ? "" : "s"}).\n`,
|
|
164
321
|
};
|
|
165
322
|
}
|
|
166
|
-
const clarifyGen = runClarification(topic, leaderModelId, conversationContext, respondToQuestion, llm, options?.signal, pilSeed, options?.clarifyMaxRounds ?? EXPLICIT_COUNCIL_CLARIFY_ROUNDS, undefined, costAware);
|
|
323
|
+
const clarifyGen = runClarification(topic, leaderModelId, conversationContext, respondToQuestion, llm, options?.signal, pilSeed, options?.clarifyMaxRounds ?? EXPLICIT_COUNCIL_CLARIFY_ROUNDS, undefined, costAware, participants.map((p) => p.model));
|
|
167
324
|
let clarifyResult;
|
|
168
325
|
do {
|
|
169
326
|
clarifyResult = await clarifyGen.next();
|
|
@@ -177,13 +334,48 @@ export async function* runCouncil(topic, sessionModelId, messages, sessionId, ra
|
|
|
177
334
|
spec = buildSpecFromTopic(topic, conversationContext);
|
|
178
335
|
yield { type: "content", content: `\n> Auto-council: skipping clarification (PIL pre-classified).\n` };
|
|
179
336
|
}
|
|
337
|
+
// Guarantee context continuity on BOTH paths: the explicit `/council`
|
|
338
|
+
// clarifier (synthesizeSpec / inferSpecFromTopicOnly) does not always set
|
|
339
|
+
// parentContext, and the skip path sets it via buildSpecFromTopic. Attach it
|
|
340
|
+
// centrally here so every downstream debate stage sees the ongoing task
|
|
341
|
+
// context regardless of how the council was triggered.
|
|
342
|
+
if (!spec.parentContext) {
|
|
343
|
+
spec.parentContext = conversationContext?.trim() || undefined;
|
|
344
|
+
}
|
|
345
|
+
// B2: pin the outcome criteria into the Context Rail so the user SEES what
|
|
346
|
+
// the debate is graded against (not a leader-improvised per-round criterion).
|
|
347
|
+
// Emitted once here; per-round met/pending arrives via later council_meta
|
|
348
|
+
// patches from debate.ts. Only emit when there is something meaningful (skip
|
|
349
|
+
// the single "Address the topic" auto-fallback).
|
|
350
|
+
if (spec.successCriteria.length > 0) {
|
|
351
|
+
// Emit a count-matched all-false criteriaMet ALONGSIDE successCriteria so
|
|
352
|
+
// the rail's Outcome block starts at 0/N. councilMeta is upsert-merged
|
|
353
|
+
// ({...prev, ...patch}); without this reset a previous council's
|
|
354
|
+
// criteriaMet array bleeds through (e.g. after an Esc-interrupt that
|
|
355
|
+
// skipped clearLiveTurnUi) and paints stale ✓ / a wrong "N/N met" counter
|
|
356
|
+
// before this debate has graded anything. debate.ts overwrites it post-eval.
|
|
357
|
+
yield {
|
|
358
|
+
type: "council_meta",
|
|
359
|
+
councilMeta: {
|
|
360
|
+
successCriteria: spec.successCriteria,
|
|
361
|
+
criteriaMet: spec.successCriteria.map(() => false),
|
|
362
|
+
},
|
|
363
|
+
};
|
|
364
|
+
}
|
|
180
365
|
// Cancelled during clarification — don't pop the preflight approval card.
|
|
181
366
|
if (userAborted())
|
|
182
367
|
break;
|
|
183
368
|
const researchNeeded = true;
|
|
369
|
+
// ROI: when the clarifier judged the spec ready (high confidence, no gaps),
|
|
370
|
+
// the approve card is a rubber-stamp — auto-approve after showing the brief.
|
|
184
371
|
const preflightGen = runPreflight(spec, participants, researchNeeded, respondToPreflight, {
|
|
185
372
|
repoEmpty: internetFirst,
|
|
186
373
|
researchOverridable: true,
|
|
374
|
+
// convenePath auto-approves the pre-debate plan card too: the agent
|
|
375
|
+
// already decided to convene, so re-gating the discussion plan is a
|
|
376
|
+
// redundant interruption of the autonomous tool call (same rationale as
|
|
377
|
+
// sprintPlanningMode). The brief is still shown; it just isn't blocking.
|
|
378
|
+
autoApprove: spec.ready === true || options?.autoApprovePreflight === true || options?.convenePath === true,
|
|
187
379
|
});
|
|
188
380
|
let preflightResult;
|
|
189
381
|
do {
|
|
@@ -204,59 +396,47 @@ export async function* runCouncil(topic, sessionModelId, messages, sessionId, ra
|
|
|
204
396
|
// Leader-LLM decides if research is required. If yes, give the user a chance
|
|
205
397
|
// to skip — research is the slowest part of council and trivial questions
|
|
206
398
|
// (e.g. "what did we just decide?") should not pay that cost.
|
|
207
|
-
|
|
399
|
+
// When the caller (sprint-planning) already has product-level research from
|
|
400
|
+
// CB-1, skip the second research pass entirely: force researchSkipOverride so
|
|
401
|
+
// runDebate does not re-run it, and short-circuit leaderNeedsResearch to false.
|
|
402
|
+
const researchSkipOverride = options?.skipResearch === true;
|
|
208
403
|
// Hoisted so the leader's research decision can be reused by runDebate instead
|
|
209
404
|
// of re-running the classifier LLM call (see CouncilConfig.leaderNeedsResearch).
|
|
210
405
|
// Stays undefined if the classifier throws — fail-open: runDebate re-evaluates.
|
|
211
406
|
let leaderNeedsResearch;
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
do {
|
|
216
|
-
needStep = await needGen.next();
|
|
217
|
-
if (!needStep.done && needStep.value)
|
|
218
|
-
yield needStep.value;
|
|
219
|
-
} while (!needStep.done);
|
|
220
|
-
leaderNeedsResearch = needStep.value;
|
|
221
|
-
if (leaderNeedsResearch) {
|
|
222
|
-
const { randomUUID } = await import("crypto");
|
|
223
|
-
const overrideId = randomUUID();
|
|
224
|
-
yield {
|
|
225
|
-
type: "council_question",
|
|
226
|
-
content: `\n## Research decision\nLeader recommends a research phase before debate` +
|
|
227
|
-
(internetFirst ? " (internet-first — empty workspace)" : " (codebase-first)") +
|
|
228
|
-
`. Want to skip it?`,
|
|
229
|
-
councilQuestion: {
|
|
230
|
-
questionId: overrideId,
|
|
231
|
-
phase: "post-debate",
|
|
232
|
-
question: "Skip the research phase?",
|
|
233
|
-
context: internetFirst
|
|
234
|
-
? "Workspace is empty — research will search the internet. Skip if you already have the answer."
|
|
235
|
-
: "Research will grep/read the codebase. Skip for trivial topics that don't need code evidence.",
|
|
236
|
-
isRequired: false,
|
|
237
|
-
options: [
|
|
238
|
-
{
|
|
239
|
-
label: "No — run research (recommended)",
|
|
240
|
-
description: "Leader thinks evidence is needed.",
|
|
241
|
-
value: "no",
|
|
242
|
-
kind: "choice",
|
|
243
|
-
},
|
|
244
|
-
{ label: "Yes — skip research", description: "Go straight to debate.", value: "yes", kind: "choice" },
|
|
245
|
-
],
|
|
246
|
-
defaultIndex: 0,
|
|
247
|
-
},
|
|
248
|
-
};
|
|
249
|
-
const overrideAnswer = await respondToQuestion(overrideId);
|
|
250
|
-
researchSkipOverride = overrideAnswer === "yes";
|
|
251
|
-
yield {
|
|
252
|
-
type: "content",
|
|
253
|
-
content: `\n ↳ ${researchSkipOverride ? "Skipping research per user override." : "Running research."}\n`,
|
|
254
|
-
};
|
|
255
|
-
}
|
|
407
|
+
if (options?.skipResearch) {
|
|
408
|
+
leaderNeedsResearch = false;
|
|
409
|
+
yield { type: "council_meta", councilMeta: { researchMode: false } };
|
|
256
410
|
}
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
411
|
+
else {
|
|
412
|
+
try {
|
|
413
|
+
const needGen = evaluateResearchNeed(spec, leaderModelId, conversationContext, llm, costAware);
|
|
414
|
+
let needStep;
|
|
415
|
+
do {
|
|
416
|
+
needStep = await needGen.next();
|
|
417
|
+
if (!needStep.done && needStep.value)
|
|
418
|
+
yield needStep.value;
|
|
419
|
+
} while (!needStep.done);
|
|
420
|
+
leaderNeedsResearch = needStep.value;
|
|
421
|
+
if (leaderNeedsResearch !== undefined) {
|
|
422
|
+
yield { type: "council_meta", councilMeta: { researchMode: leaderNeedsResearch } };
|
|
423
|
+
}
|
|
424
|
+
// ROI: the leader already decided research is needed and the card's default
|
|
425
|
+
// was always "run research" — asking the user to confirm is a rubber-stamp
|
|
426
|
+
// (measured 0 information at real cost). Auto-proceed with research; the
|
|
427
|
+
// leaderNeedsResearch signal still flows to runDebate. researchSkipOverride
|
|
428
|
+
// stays false. (Deliberately no card — see council-UX ROI pass.)
|
|
429
|
+
if (leaderNeedsResearch) {
|
|
430
|
+
yield {
|
|
431
|
+
type: "content",
|
|
432
|
+
content: `\n ↳ Leader recommends research${internetFirst ? " (internet-first — empty workspace)" : " (codebase-first)"} — running it.\n`,
|
|
433
|
+
};
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
catch (err) {
|
|
437
|
+
// fail-open — leaderNeedsResearch stays undefined so runDebate re-evaluates.
|
|
438
|
+
console.error(`[council] research-need pre-check failed (fail-open): ${err?.message}`);
|
|
439
|
+
}
|
|
260
440
|
}
|
|
261
441
|
// Await EE pre-fetch (started in parallel with clarifier — latency already hidden)
|
|
262
442
|
const eeResult = await eePromise;
|
|
@@ -341,6 +521,17 @@ export async function* runCouncil(topic, sessionModelId, messages, sessionId, ra
|
|
|
341
521
|
internetFirst,
|
|
342
522
|
costAware,
|
|
343
523
|
runId: sessionId,
|
|
524
|
+
// #2 — isolated research bridge; when wired, runDebate runs research in a
|
|
525
|
+
// budget-capped explore sub-agent instead of an in-process 15-step call.
|
|
526
|
+
runIsolatedTask: options?.runIsolatedTask,
|
|
527
|
+
// B4 interactive escalation — same responder the clarifier + post-debate
|
|
528
|
+
// askcards use. When the debate is about to stop with pinned criteria
|
|
529
|
+
// unmet, runDebate asks the user (extend / accept / rescope) instead of
|
|
530
|
+
// silently synthesizing a partial outcome.
|
|
531
|
+
respondToQuestion,
|
|
532
|
+
// convene_council path — auto-accept escalation (no blocking card) since
|
|
533
|
+
// the council runs autonomously mid-agent-turn with no interactive user.
|
|
534
|
+
convenePath: options?.convenePath,
|
|
344
535
|
}, llm);
|
|
345
536
|
let debateResult;
|
|
346
537
|
do {
|
|
@@ -393,6 +584,10 @@ export async function* runCouncil(topic, sessionModelId, messages, sessionId, ra
|
|
|
393
584
|
} while (!planResult.done);
|
|
394
585
|
let { outcome, plan, synthesisText } = planResult.value;
|
|
395
586
|
const synthesisFailReason = planResult.value.synthesisFailReason;
|
|
587
|
+
// Post-debate action the user picked (hoisted so the completed-status guard +
|
|
588
|
+
// the caller's auto-continue can both read it). Undefined until the card is
|
|
589
|
+
// answered.
|
|
590
|
+
let postDebateAction;
|
|
396
591
|
stats.phases.push({ name: "planning", durationMs: Date.now() - planStart });
|
|
397
592
|
// Log interaction: synthesis
|
|
398
593
|
logInteraction(sessionId ?? "unknown", "council", {
|
|
@@ -402,7 +597,13 @@ export async function* runCouncil(topic, sessionModelId, messages, sessionId, ra
|
|
|
402
597
|
data: { topic, roundCount: debateState.roundCount, participantCount: debateState.active.length },
|
|
403
598
|
});
|
|
404
599
|
// ── Post-Debate AskCard: What next? ─────────────────────────────────────────
|
|
405
|
-
|
|
600
|
+
// convenePath skips this ENTIRE interactive block (recommendation, option set,
|
|
601
|
+
// card, respondToQuestion, postDebateAction, onPostDebateAction, and the whole
|
|
602
|
+
// routing tree). On that path the agent that called `convene_council` decides
|
|
603
|
+
// what happens next — the CLI must not hardcode a post-council pick. The
|
|
604
|
+
// persistence block below still runs (audit trail, not a decision), and the
|
|
605
|
+
// function returns synthesisText as usual.
|
|
606
|
+
if (sessionId && !options?.convenePath) {
|
|
406
607
|
try {
|
|
407
608
|
const { randomUUID } = await import("crypto");
|
|
408
609
|
const refinementTopics = [];
|
|
@@ -422,7 +623,19 @@ export async function* runCouncil(topic, sessionModelId, messages, sessionId, ra
|
|
|
422
623
|
const hasEmptySections = refinementTopics.length > 0;
|
|
423
624
|
// ── Confidence badge (CQ-6) ──────────────────────────────────────────
|
|
424
625
|
const evidenceDensity = debateState.finalEvidenceDensity ?? 0;
|
|
626
|
+
const taggedClaims = debateState.finalTaggedClaims ?? 0;
|
|
425
627
|
const synthesisFailed = !!synthesisFailReason || !outcome || synthesisText.trim().length < 20;
|
|
628
|
+
// "Not measured" ≠ "0%". When the debate emitted zero tagged claims the
|
|
629
|
+
// density formula returns 0 by convention, but that means grounding was
|
|
630
|
+
// never measured — not that every claim was refuted. Surfacing "Low 0%"
|
|
631
|
+
// there reads as a scoring failure on debates that are actually fine
|
|
632
|
+
// (session de4bafe5ecb7). Only applies when synthesis itself succeeded.
|
|
633
|
+
// Also treat a genuine 0 density (tags emitted but none resolved to a
|
|
634
|
+
// citation) as "not measured" rather than a literal "Low 0%" score — a
|
|
635
|
+
// bare 0% reads as a scoring failure on debates that were degraded (e.g.
|
|
636
|
+
// the debate model tripped the tool-verification circuit breaker and ran
|
|
637
|
+
// tool-free, so no claims could be grounded). Session 65b66c99ed36.
|
|
638
|
+
const confidenceNotMeasured = !synthesisFailed && (taggedClaims === 0 || evidenceDensity === 0);
|
|
426
639
|
const confidenceLevel = synthesisFailed
|
|
427
640
|
? "low"
|
|
428
641
|
: evidenceDensity >= 0.6
|
|
@@ -437,18 +650,29 @@ export async function* runCouncil(topic, sessionModelId, messages, sessionId, ra
|
|
|
437
650
|
const confidenceReason = synthesisFailed
|
|
438
651
|
? (synthesisFailReason ??
|
|
439
652
|
"The synthesizer produced no usable output. The debate exchanges above are still readable, but no structured outcome could be extracted.")
|
|
440
|
-
:
|
|
441
|
-
?
|
|
442
|
-
: confidenceLevel === "
|
|
443
|
-
?
|
|
444
|
-
:
|
|
653
|
+
: confidenceNotMeasured
|
|
654
|
+
? "The debate produced no explicitly tagged claims ([CONFIRMED]/[REFUTED]/[UNVERIFIED]), so evidence grounding could not be measured — this is NOT a 0% score. The exchanges above may still be substantive; read them directly, or re-run with research enabled to force citations."
|
|
655
|
+
: confidenceLevel === "low"
|
|
656
|
+
? `Only ${(evidenceDensity * 100).toFixed(0)}% of claims in the final round carried citations or were resolved — most positions remained asserted without backing evidence.`
|
|
657
|
+
: confidenceLevel === "medium"
|
|
658
|
+
? `${(evidenceDensity * 100).toFixed(0)}% of claims carried citations or were resolved — some open points remain.`
|
|
659
|
+
: `${(evidenceDensity * 100).toFixed(0)}% of claims were cited or resolved.`;
|
|
445
660
|
const confidenceBadge = synthesisFailed
|
|
446
661
|
? `❌ Synthesis failed — confidence cannot be computed`
|
|
447
|
-
:
|
|
448
|
-
?
|
|
449
|
-
: confidenceLevel === "
|
|
450
|
-
?
|
|
451
|
-
:
|
|
662
|
+
: confidenceNotMeasured
|
|
663
|
+
? `◐ Confidence not measured — the debate emitted no tagged claims`
|
|
664
|
+
: confidenceLevel === "high"
|
|
665
|
+
? `✅ High confidence (evidence density ${evidenceDensity.toFixed(2)})`
|
|
666
|
+
: confidenceLevel === "medium"
|
|
667
|
+
? `⚠ Medium confidence (evidence density ${evidenceDensity.toFixed(2)})`
|
|
668
|
+
: `⚠ Low confidence (evidence density ${evidenceDensity.toFixed(2)})`;
|
|
669
|
+
// F1 — did the debate actually satisfy its PINNED success criteria? This is
|
|
670
|
+
// distinct from evidence density (a confidently-worded synthesis can still
|
|
671
|
+
// leave every criterion open). When criteria remain unmet on a successful
|
|
672
|
+
// synthesis the outcome is provisional, and the card must not recommend
|
|
673
|
+
// committing (implement/plan/save) as if it were settled.
|
|
674
|
+
const critOutcome = summarizeCriteriaOutcome(spec.successCriteria ?? [], debateState.finalCriteriaMet);
|
|
675
|
+
const inconclusive = !synthesisFailed && critOutcome.inconclusive;
|
|
452
676
|
// Recommendation surfaced to the user as the default action. The
|
|
453
677
|
// implementation_plan-vs-decision/evaluation split lives in
|
|
454
678
|
// pickPostDebateRecommendation (issue #3 — see its doc comment).
|
|
@@ -459,83 +683,282 @@ export async function* runCouncil(topic, sessionModelId, messages, sessionId, ra
|
|
|
459
683
|
confidenceLevel,
|
|
460
684
|
hasPlan: !!hasPlan,
|
|
461
685
|
outputKind: debatePlan.outputShape.kind,
|
|
686
|
+
criteriaUnmet: inconclusive ? critOutcome.unmetLabels.length : 0,
|
|
462
687
|
});
|
|
463
688
|
const baseOptions = [];
|
|
464
|
-
|
|
689
|
+
// Model-first post-debate options. The leader synthesis picks intent-fit
|
|
690
|
+
// next actions (a bug investigation, evaluation, plan, and pure discussion
|
|
691
|
+
// each warrant different follow-ups — the old fixed "accept / research /
|
|
692
|
+
// apply" menu was wrong regardless of intent). Fall back to the
|
|
693
|
+
// deterministic set on synthesis failure or when the model emitted none.
|
|
694
|
+
const modelActions = !synthesisFailed && outcome?.nextActions && outcome.nextActions.length > 0 ? outcome.nextActions : null;
|
|
695
|
+
if (modelActions) {
|
|
696
|
+
for (const a of modelActions) {
|
|
697
|
+
// "implement" no longer needs a separate plan artifact — an analysis /
|
|
698
|
+
// decision synthesis IS the spec (postDebateContinuation loads it and
|
|
699
|
+
// runs the normal plan→change→verify workflow). The old `!hasPlan` drop
|
|
700
|
+
// is why a decision-to-change-code debate had NO build path and the
|
|
701
|
+
// user's "implement"-labelled pick did nothing (session 8191ecaee149).
|
|
702
|
+
baseOptions.push({
|
|
703
|
+
label: a.label,
|
|
704
|
+
// Description is the model's own `reason` (model-first — no hardcoded
|
|
705
|
+
// per-action prose). If the model was terse and omitted it, repeat
|
|
706
|
+
// the label rather than inventing system copy.
|
|
707
|
+
description: a.reason && a.reason.length > 0 ? a.reason : a.label,
|
|
708
|
+
value: a.action,
|
|
709
|
+
kind: a.action === "ask_followup" ? "freetext" : "choice",
|
|
710
|
+
});
|
|
711
|
+
}
|
|
712
|
+
// Context-only option the model doesn't own — surfaced when the debate
|
|
713
|
+
// left shape sections empty.
|
|
714
|
+
if (hasEmptySections) {
|
|
715
|
+
baseOptions.push({
|
|
716
|
+
label: `Refine: ${refinementTopics.join(", ")}`,
|
|
717
|
+
description: `Answer questions about ${refinementTopics.length} unresolved aspect(s)`,
|
|
718
|
+
value: "refine",
|
|
719
|
+
kind: "choice",
|
|
720
|
+
});
|
|
721
|
+
}
|
|
722
|
+
// Guarantee an escape hatch even if the model omitted one.
|
|
723
|
+
if (!baseOptions.some((o) => o.value === "save_exit" || o.value === "continue_session")) {
|
|
724
|
+
baseOptions.push({
|
|
725
|
+
label: "Save & Exit",
|
|
726
|
+
description: "Save the debate outcome and finish",
|
|
727
|
+
value: "save_exit",
|
|
728
|
+
kind: "choice",
|
|
729
|
+
});
|
|
730
|
+
}
|
|
731
|
+
}
|
|
732
|
+
else {
|
|
733
|
+
// ── Fallback: deterministic option set ──────────────────────────────
|
|
734
|
+
if (synthesisFailed) {
|
|
735
|
+
baseOptions.push({
|
|
736
|
+
label: "Retry Synthesis (compact)",
|
|
737
|
+
description: "Re-synthesize from final positions only (drop full exchange history). Fastest recovery from provider timeouts.",
|
|
738
|
+
value: "retry_synthesis",
|
|
739
|
+
kind: "choice",
|
|
740
|
+
});
|
|
741
|
+
}
|
|
465
742
|
baseOptions.push({
|
|
466
|
-
label: "
|
|
467
|
-
description:
|
|
468
|
-
|
|
743
|
+
label: "Save & Exit",
|
|
744
|
+
description: synthesisFailed
|
|
745
|
+
? "Save raw debate exchanges as-is; no structured outcome will be persisted"
|
|
746
|
+
: "Save the debate outcome and finish",
|
|
747
|
+
value: "save_exit",
|
|
469
748
|
kind: "choice",
|
|
470
749
|
});
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
750
|
+
if (!hasPlan && !synthesisFailed) {
|
|
751
|
+
baseOptions.push({
|
|
752
|
+
label: "Lock plan and execute Sprint 1",
|
|
753
|
+
description: "Commit the council outcome as the sprint plan and hand control to the sprint runner (planning → implementation → verification → judgment). Does NOT exit to /gsd.",
|
|
754
|
+
value: "generate_plan",
|
|
755
|
+
kind: "choice",
|
|
756
|
+
});
|
|
757
|
+
}
|
|
758
|
+
if (hasEmptySections && !synthesisFailed) {
|
|
759
|
+
baseOptions.push({
|
|
760
|
+
label: `Refine: ${refinementTopics.join(", ")}`,
|
|
761
|
+
description: `Answer questions about ${refinementTopics.length} unresolved aspect(s)`,
|
|
762
|
+
value: "refine",
|
|
763
|
+
kind: "choice",
|
|
764
|
+
});
|
|
765
|
+
}
|
|
766
|
+
// CQ-3: free-text follow-up to the council on the same debate context.
|
|
481
767
|
baseOptions.push({
|
|
482
|
-
label: "
|
|
483
|
-
description: "
|
|
484
|
-
value: "
|
|
485
|
-
kind: "
|
|
768
|
+
label: "Ask Council a follow-up",
|
|
769
|
+
description: "Pose a new question that re-uses this debate's context (no new clarification).",
|
|
770
|
+
value: "ask_followup",
|
|
771
|
+
kind: "freetext",
|
|
486
772
|
});
|
|
773
|
+
if (!synthesisFailed) {
|
|
774
|
+
baseOptions.push({
|
|
775
|
+
label: "Start Implementation",
|
|
776
|
+
description: "Load the council conclusion as the spec and build it (plan → change → verify)",
|
|
777
|
+
value: "implement",
|
|
778
|
+
kind: "choice",
|
|
779
|
+
});
|
|
780
|
+
}
|
|
487
781
|
}
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
782
|
+
// Canonicalize the post-analysis choices to the user's mental model:
|
|
783
|
+
// IMPLEMENT / CONTINUE / SAVE (session 8191ecaee149 redesign).
|
|
784
|
+
// (a) "ask a follow-up" and "continue with council context" are the same
|
|
785
|
+
// thing to the user (both = keep the session going with the debate as
|
|
786
|
+
// context), so collapse a generic ask_followup into continue_session.
|
|
787
|
+
// A pinned criteria-recovery follow-up is added LATER (inconclusive /
|
|
788
|
+
// lowGrounding) and is intentionally distinct, so this only affects
|
|
789
|
+
// the base set built above.
|
|
790
|
+
// (b) guarantee a CONTINUE option exists.
|
|
791
|
+
// (c) offer IMPLEMENT whenever the synthesis is substantive (grounded &
|
|
792
|
+
// conclusive) — the conclusion IS the spec, no plan artifact needed.
|
|
793
|
+
if (!options?.sprintPlanningMode) {
|
|
794
|
+
const CONTINUE_OPT = {
|
|
795
|
+
label: "Continue with council context",
|
|
796
|
+
description: "Return to the composer — your next message keeps this debate's conclusion as context.",
|
|
797
|
+
value: "continue_session",
|
|
493
798
|
kind: "choice",
|
|
799
|
+
};
|
|
800
|
+
const hasContinue = baseOptions.some((o) => o.value === "continue_session");
|
|
801
|
+
for (let i = baseOptions.length - 1; i >= 0; i--) {
|
|
802
|
+
if (baseOptions[i].value !== "ask_followup")
|
|
803
|
+
continue;
|
|
804
|
+
if (hasContinue)
|
|
805
|
+
baseOptions.splice(i, 1); // merged away — continue already covers it
|
|
806
|
+
else
|
|
807
|
+
baseOptions[i] = { ...CONTINUE_OPT }; // convert the lone follow-up into continue
|
|
808
|
+
}
|
|
809
|
+
if (!baseOptions.some((o) => o.value === "continue_session"))
|
|
810
|
+
baseOptions.push({ ...CONTINUE_OPT });
|
|
811
|
+
if (!synthesisFailed && !inconclusive && !baseOptions.some((o) => o.value === "implement")) {
|
|
812
|
+
// Insert at index 1, NOT 0 — the model's own best-first pick stays the
|
|
813
|
+
// default (defaultIndex is 0 for model-first). We only GUARANTEE the
|
|
814
|
+
// build path is present + prominent; we don't override the model's
|
|
815
|
+
// judgment that building wasn't the recommended next move.
|
|
816
|
+
baseOptions.splice(1, 0, {
|
|
817
|
+
label: "Start Implementation",
|
|
818
|
+
description: "Load the council conclusion as the spec and build it (plan → change → verify)",
|
|
819
|
+
value: "implement",
|
|
820
|
+
kind: "choice",
|
|
821
|
+
});
|
|
822
|
+
}
|
|
823
|
+
}
|
|
824
|
+
// F1 — when the pinned criteria were not met, the model's best-first action
|
|
825
|
+
// (or the deterministic default) may be a commit/hand-back-the-decision step
|
|
826
|
+
// that treats the outcome as settled. Pin a criteria-aware "keep working"
|
|
827
|
+
// option at the front and make it the default so the recommended next move
|
|
828
|
+
// is honest about the unmet bar. Reuses ask_followup routing (freetext,
|
|
829
|
+
// re-runs on this debate's context) — no new downstream action. Deduped so
|
|
830
|
+
// the list never shows two ask_followup rows.
|
|
831
|
+
if (inconclusive) {
|
|
832
|
+
const openList = critOutcome.unmetLabels.join("; ");
|
|
833
|
+
const n = critOutcome.unmetLabels.length;
|
|
834
|
+
for (let i = baseOptions.length - 1; i >= 0; i--) {
|
|
835
|
+
if (baseOptions[i].value === "ask_followup")
|
|
836
|
+
baseOptions.splice(i, 1);
|
|
837
|
+
}
|
|
838
|
+
baseOptions.unshift({
|
|
839
|
+
label: `Keep working the ${n} unmet criteri${n === 1 ? "on" : "a"}`,
|
|
840
|
+
description: `Still open: ${openList}. Pose a targeted follow-up to close ${n === 1 ? "it" : "them"} before committing.`,
|
|
841
|
+
value: "ask_followup",
|
|
842
|
+
kind: "freetext",
|
|
494
843
|
});
|
|
495
844
|
}
|
|
496
|
-
//
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
845
|
+
// A2 — synthesis succeeded but grounding is weak (density 0 / low /
|
|
846
|
+
// "not measured"). The honest next move is to RAISE confidence, not to
|
|
847
|
+
// commit or to ask a blind clarification — the user reported the askcard
|
|
848
|
+
// asked "clarify more?" without saying WHAT would help. Pin a guided
|
|
849
|
+
// follow-up that names the concrete confidence-raising ask (make the
|
|
850
|
+
// council cite/verify its weakest claims) and make it the default.
|
|
851
|
+
// Reuses ask_followup routing (freetext, re-runs on this debate's
|
|
852
|
+
// context) — no new downstream action. Skipped when `inconclusive`
|
|
853
|
+
// already pinned a criteria-aware follow-up, or when synthesis failed
|
|
854
|
+
// (the retry_synthesis path owns that recovery).
|
|
855
|
+
const lowGrounding = !synthesisFailed && !inconclusive && (confidenceNotMeasured || confidenceLevel === "low");
|
|
856
|
+
if (lowGrounding) {
|
|
857
|
+
for (let i = baseOptions.length - 1; i >= 0; i--) {
|
|
858
|
+
if (baseOptions[i].value === "ask_followup")
|
|
859
|
+
baseOptions.splice(i, 1);
|
|
860
|
+
}
|
|
861
|
+
baseOptions.unshift({
|
|
862
|
+
label: "Raise confidence — have the council cite & verify",
|
|
863
|
+
description: "Grounding is weak: no claims were cited or resolved, so evidence density stayed at 0. Pose a follow-up that forces the council to back its weakest claims against the codebase or sources — that lifts confidence instead of committing on thin evidence.",
|
|
864
|
+
value: "ask_followup",
|
|
865
|
+
kind: "freetext",
|
|
509
866
|
});
|
|
510
867
|
}
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
868
|
+
// Model orders actions best-first (index 0 = recommended default); the
|
|
869
|
+
// fallback set uses the deterministic recommendation. When inconclusive,
|
|
870
|
+
// the pinned criteria option at index 0 is the honest default regardless of
|
|
871
|
+
// path.
|
|
872
|
+
const defaultIndex = inconclusive || lowGrounding
|
|
873
|
+
? 0
|
|
874
|
+
: modelActions
|
|
875
|
+
? 0
|
|
876
|
+
: Math.max(0, baseOptions.findIndex((o) => o.value === recommendation.value));
|
|
877
|
+
const recommendReason = inconclusive || lowGrounding
|
|
878
|
+
? (baseOptions[0]?.description ?? recommendation.reason)
|
|
879
|
+
: modelActions
|
|
880
|
+
? (baseOptions[0]?.description ?? recommendation.reason)
|
|
881
|
+
: recommendation.reason;
|
|
882
|
+
const heading = synthesisFailed
|
|
883
|
+
? "## Debate Synthesis Failed"
|
|
884
|
+
: inconclusive
|
|
885
|
+
? `## Debate Synthesis — Inconclusive (${critOutcome.metCount}/${critOutcome.total} criteria met)`
|
|
886
|
+
: "## Debate Synthesis Complete";
|
|
887
|
+
// F1 — an explicit provisional-outcome line so the user sees the unmet bar
|
|
888
|
+
// even if they skim past the recommendation.
|
|
889
|
+
const outcomeLine = inconclusive
|
|
890
|
+
? `\n\n⚠ Outcome: ${critOutcome.metCount}/${critOutcome.total} criteria met. Unmet: ${critOutcome.unmetLabels.join("; ")}. Treat the synthesis as provisional — not a settled decision.`
|
|
891
|
+
: "";
|
|
892
|
+
const recommendLine = `**Recommended:** ${baseOptions[defaultIndex]?.label ?? recommendation.value} — ${recommendReason}`;
|
|
893
|
+
// B — the live per-round transcript is cleared from the view at turn end
|
|
894
|
+
// (it renders as a bottom block decoupled from the timeline, so keeping it
|
|
895
|
+
// would mis-order later messages). The full exchange IS persisted though —
|
|
896
|
+
// point the user at it so the rounds aren't "lost" (user report: after a
|
|
897
|
+
// debate the rounds vanish with no way to re-read them). `/council inspect`
|
|
898
|
+
// is a registered slash command that replays [Council Round N] / [Council
|
|
899
|
+
// Memory] from the DB.
|
|
900
|
+
const roundsArchivedLine = debateState.roundCount > 0
|
|
901
|
+
? `\n\n📋 All ${debateState.roundCount} debate round(s) are archived — run \`/council inspect ${sessionId}\` to re-read the full exchange.`
|
|
902
|
+
: "";
|
|
903
|
+
const headerBlock = `${heading}\n\n> ${confidenceBadge}\n>\n> **Why:** ${confidenceReason}${outcomeLine}\n\n${recommendLine}${roundsArchivedLine}\n\nLeader: \`${leaderModelId}\`. What would you like to do next?`;
|
|
904
|
+
let answer;
|
|
905
|
+
if (options?.sprintPlanningMode) {
|
|
906
|
+
// Blocker 4/5 fix: no interactive post-debate menu inside automated
|
|
907
|
+
// per-sprint planning. Presenting it stranded the sprint before
|
|
908
|
+
// implementation — picking "Save & Exit" ended the run with no Sprint
|
|
909
|
+
// Implementation, and "Refine" (the default) looped back into more
|
|
910
|
+
// debate. Auto-lock the synthesized plan (== "Lock plan and execute
|
|
911
|
+
// Sprint 1") and hand control back to the sprint runner.
|
|
912
|
+
answer = "generate_plan";
|
|
913
|
+
idealTrace("council.postDebate.autoLock", { sessionId });
|
|
914
|
+
yield {
|
|
915
|
+
type: "content",
|
|
916
|
+
content: "\n> Sprint plan synthesized — auto-locked and handed to the sprint runner " +
|
|
917
|
+
"(the product plan was already approved at the /ideal preflight).\n",
|
|
918
|
+
};
|
|
919
|
+
}
|
|
920
|
+
else {
|
|
921
|
+
yield {
|
|
922
|
+
type: "council_question",
|
|
923
|
+
content: headerBlock,
|
|
924
|
+
councilQuestion: {
|
|
925
|
+
questionId,
|
|
926
|
+
phase: "post-debate",
|
|
927
|
+
question: synthesisFailed
|
|
928
|
+
? "Synthesis did not produce a structured outcome. How do you want to recover?"
|
|
929
|
+
: inconclusive
|
|
930
|
+
? `${critOutcome.metCount}/${critOutcome.total} success criteria met — the outcome is provisional. Keep working the unmet criteria, or save it as-is?`
|
|
931
|
+
: hasEmptySections
|
|
932
|
+
? `The debate left ${refinementTopics.length} area(s) unresolved. Refine them or save the current outcome?`
|
|
933
|
+
: "What would you like to do next?",
|
|
934
|
+
context: `${confidenceBadge}\n${confidenceReason}` +
|
|
935
|
+
(inconclusive ? `\nUnmet criteria: ${critOutcome.unmetLabels.join("; ")}` : "") +
|
|
936
|
+
(hasEmptySections ? `\nUnresolved areas: ${refinementTopics.join(", ")}` : "") +
|
|
937
|
+
`\n→ ${recommendation.reason}`,
|
|
938
|
+
isRequired: false,
|
|
939
|
+
options: baseOptions,
|
|
940
|
+
defaultIndex,
|
|
941
|
+
},
|
|
942
|
+
};
|
|
943
|
+
answer = await respondToQuestion(questionId);
|
|
944
|
+
}
|
|
945
|
+
postDebateAction = answer;
|
|
946
|
+
idealTrace("council.postDebate.answer", { sessionId, answer });
|
|
947
|
+
options?.onPostDebateAction?.(answer);
|
|
948
|
+
// Echo the human-readable option label, never the raw action id
|
|
949
|
+
// (`continue_session`, `save_exit`, …) — the id is an internal routing
|
|
950
|
+
// token users should never see. Free-text follow-ups (no matching option)
|
|
951
|
+
// echo verbatim.
|
|
952
|
+
const answeredLabel = baseOptions.find((o) => o.value === answer)?.label ?? answer;
|
|
953
|
+
// No "↳ choice" echo in sprint-planning mode — there was no user choice to
|
|
954
|
+
// echo (the plan was auto-locked above with its own status line).
|
|
955
|
+
if (!options?.sprintPlanningMode) {
|
|
956
|
+
yield { type: "content", content: `\n ↳ ${answeredLabel}\n` };
|
|
957
|
+
}
|
|
536
958
|
// Treat any non-empty answer that doesn't match a known choice value as a follow-up question.
|
|
537
959
|
const knownValues = new Set([
|
|
538
960
|
"save_exit",
|
|
961
|
+
"continue_session",
|
|
539
962
|
"generate_plan",
|
|
540
963
|
"refine",
|
|
541
964
|
"ask_followup",
|
|
@@ -603,6 +1026,10 @@ export async function* runCouncil(topic, sessionModelId, messages, sessionId, ra
|
|
|
603
1026
|
synthesisText =
|
|
604
1027
|
`Sprint plan locked (${existingActionItems.length} steps):\n` +
|
|
605
1028
|
synthesizedPlan.steps.map((s) => `- [${s.priority}] ${s.description}`).join("\n");
|
|
1029
|
+
idealTrace("council.generatePlan.locked.fast", {
|
|
1030
|
+
sessionId,
|
|
1031
|
+
actionItems: existingActionItems.length,
|
|
1032
|
+
});
|
|
606
1033
|
}
|
|
607
1034
|
else {
|
|
608
1035
|
yield { type: "content", content: "\n> Synthesizing sprint plan...\n" };
|
|
@@ -621,6 +1048,10 @@ export async function* runCouncil(topic, sessionModelId, messages, sessionId, ra
|
|
|
621
1048
|
type: "content",
|
|
622
1049
|
content: "\n> Plan locked — sprint runner will execute planning → implementation → verification → judgment.\n",
|
|
623
1050
|
};
|
|
1051
|
+
idealTrace("council.generatePlan.locked.synth", {
|
|
1052
|
+
sessionId,
|
|
1053
|
+
synthesisLen: synthesisText?.length ?? 0,
|
|
1054
|
+
});
|
|
624
1055
|
}
|
|
625
1056
|
// Do NOT call runExecution here. Return synthesisText to the sprint-runner
|
|
626
1057
|
// caller so it drives the full sprint lifecycle (Step 4–8 in sprint-runner.ts).
|
|
@@ -655,7 +1086,13 @@ export async function* runCouncil(topic, sessionModelId, messages, sessionId, ra
|
|
|
655
1086
|
};
|
|
656
1087
|
const ans = await respondToQuestion(sqId);
|
|
657
1088
|
refinedAnswers.push({ section: label, answer: ans });
|
|
658
|
-
|
|
1089
|
+
// Only echo sections the user actually filled. "Skip — leave as-is"
|
|
1090
|
+
// returns an empty value; echoing it emits a blank "↳ " bubble per
|
|
1091
|
+
// section (6 skips = 6 empty rows of transcript garbage). Prefix the
|
|
1092
|
+
// section label so a real answer reads as "↳ <section>: <answer>".
|
|
1093
|
+
if (ans.trim().length > 0) {
|
|
1094
|
+
yield { type: "content", content: `\n ↳ ${label}: ${ans}\n` };
|
|
1095
|
+
}
|
|
659
1096
|
}
|
|
660
1097
|
// Build refineContext string from user answers
|
|
661
1098
|
const refineCtx = refinedAnswers
|
|
@@ -678,69 +1115,85 @@ export async function* runCouncil(topic, sessionModelId, messages, sessionId, ra
|
|
|
678
1115
|
}
|
|
679
1116
|
// "save_exit" and "implement" fall through to normal persistence
|
|
680
1117
|
}
|
|
681
|
-
catch {
|
|
682
|
-
|
|
1118
|
+
catch (err) {
|
|
1119
|
+
// Post-debate interaction (menu, follow-up re-synthesis, refine) is
|
|
1120
|
+
// non-critical to the persisted outcome, so we swallow — but NEVER
|
|
1121
|
+
// silently: a throw here previously vanished, hiding a "generate_plan
|
|
1122
|
+
// stalled" root cause. Log it and breadcrumb it so blocker-5 forensics
|
|
1123
|
+
// can see whether the tail was reached via an exception.
|
|
1124
|
+
console.error(`[council] post-debate interaction failed: ${err?.message}`);
|
|
1125
|
+
idealTrace("council.postDebate.threw", { sessionId, err: err?.message });
|
|
683
1126
|
}
|
|
684
1127
|
}
|
|
1128
|
+
idealTrace("council.persist.start", { sessionId, hasOutcome: !!outcome, postDebateAction });
|
|
685
1129
|
// ── Persist outcome ─────────────────────────────────────────────────────────
|
|
686
1130
|
if (sessionId) {
|
|
687
1131
|
try {
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
synthesis: synthesisText,
|
|
705
|
-
confidence: {
|
|
706
|
-
level: confidenceLevelPersist,
|
|
707
|
-
evidenceDensity: evidenceDensityPersist,
|
|
708
|
-
rounds: debateState.roundCount,
|
|
709
|
-
},
|
|
710
|
-
stats: { calls: stats.calls, durationMs: Date.now() - stats.startMs, phases: stats.phases },
|
|
711
|
-
timestamp: new Date().toISOString(),
|
|
712
|
-
};
|
|
713
|
-
appendSystemMessage(sessionId, `[Council Memory] ${JSON.stringify(councilRecord)}`);
|
|
714
|
-
// Forensics-friendly summary row in interaction_logs. The full
|
|
715
|
-
// [Council Memory] system message above is great for context replay but
|
|
716
|
-
// can't be queried — `usage forensics` reads interaction_logs only.
|
|
717
|
-
// Excerpts are capped to keep metadata_json small (~2-4KB per run).
|
|
718
|
-
const stancesForLog = debateState.active.slice(0, 8).map((a) => ({
|
|
719
|
-
role: a.role,
|
|
720
|
-
model: a.model,
|
|
721
|
-
stanceName: a.stance?.name,
|
|
722
|
-
finalPositionExcerpt: (a.position ?? "").slice(0, 400),
|
|
723
|
-
}));
|
|
724
|
-
logInteraction(sessionId, "council", {
|
|
725
|
-
eventSubtype: "council_summary",
|
|
726
|
-
model: leaderModelId,
|
|
727
|
-
durationMs: Date.now() - stats.startMs,
|
|
728
|
-
data: {
|
|
1132
|
+
// Skip session-scoped persistence in sprintPlanningMode: messages /
|
|
1133
|
+
// interaction_logs FK-reference sessions(id), but the sprint-planning caller
|
|
1134
|
+
// passes the product-RUN id (no session row) → "FOREIGN KEY constraint
|
|
1135
|
+
// failed" on the FIRST write, which under the catch below previously aborted
|
|
1136
|
+
// the whole block — silently taking writeDecisionsLock down with it. The
|
|
1137
|
+
// file-based decisions.lock still writes below (outside this guard).
|
|
1138
|
+
if (!options?.sprintPlanningMode) {
|
|
1139
|
+
if (outcome) {
|
|
1140
|
+
const agreedLine = outcome.agreed?.length ? `\nAgreed: ${outcome.agreed.join("; ")}` : "";
|
|
1141
|
+
const recLine = outcome.recommendation ? `\nRecommendation: ${outcome.recommendation}` : "";
|
|
1142
|
+
appendSystemMessage(sessionId, `[Council Decision]\nTopic: ${topic}\n${outcome.summary}${agreedLine}${recLine}`);
|
|
1143
|
+
appendSystemMessage(sessionId, `[Council Outcome]\n${JSON.stringify(outcome)}`);
|
|
1144
|
+
}
|
|
1145
|
+
const evidenceDensityPersist = debateState.finalEvidenceDensity ?? 0;
|
|
1146
|
+
const confidenceLevelPersist = evidenceDensityPersist >= 0.6 ? "high" : evidenceDensityPersist >= 0.3 ? "medium" : "low";
|
|
1147
|
+
const councilRecord = {
|
|
729
1148
|
topic,
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
1149
|
+
spec,
|
|
1150
|
+
debatePlan,
|
|
1151
|
+
leaderModel: leaderModelId,
|
|
1152
|
+
participants: debateState.active.map((a) => ({ role: a.role, model: a.model, stance: a.stance })),
|
|
1153
|
+
finalPositions: debateState.active.map((a) => ({ role: a.role, position: a.position })),
|
|
1154
|
+
archive: debateState.archive ?? [],
|
|
1155
|
+
synthesis: synthesisText,
|
|
1156
|
+
confidence: {
|
|
1157
|
+
level: confidenceLevelPersist,
|
|
1158
|
+
evidenceDensity: evidenceDensityPersist,
|
|
1159
|
+
rounds: debateState.roundCount,
|
|
1160
|
+
},
|
|
1161
|
+
stats: { calls: stats.calls, durationMs: Date.now() - stats.startMs, phases: stats.phases },
|
|
1162
|
+
timestamp: new Date().toISOString(),
|
|
1163
|
+
};
|
|
1164
|
+
appendSystemMessage(sessionId, `[Council Memory] ${JSON.stringify(councilRecord)}`);
|
|
1165
|
+
// Forensics-friendly summary row in interaction_logs. The full
|
|
1166
|
+
// [Council Memory] system message above is great for context replay but
|
|
1167
|
+
// can't be queried — `usage forensics` reads interaction_logs only.
|
|
1168
|
+
// Excerpts are capped to keep metadata_json small (~2-4KB per run).
|
|
1169
|
+
const stancesForLog = debateState.active.slice(0, 8).map((a) => ({
|
|
1170
|
+
role: a.role,
|
|
1171
|
+
model: a.model,
|
|
1172
|
+
stanceName: a.stance?.name,
|
|
1173
|
+
finalPositionExcerpt: (a.position ?? "").slice(0, 400),
|
|
1174
|
+
}));
|
|
1175
|
+
logInteraction(sessionId, "council", {
|
|
1176
|
+
eventSubtype: "council_summary",
|
|
1177
|
+
model: leaderModelId,
|
|
1178
|
+
durationMs: Date.now() - stats.startMs,
|
|
1179
|
+
data: {
|
|
1180
|
+
topic,
|
|
1181
|
+
roundCount: debateState.roundCount,
|
|
1182
|
+
participantCount: debateState.active.length,
|
|
1183
|
+
stances: stancesForLog,
|
|
1184
|
+
synthesisExcerpt: synthesisText.slice(0, 1500),
|
|
1185
|
+
evidenceDensity: evidenceDensityPersist,
|
|
1186
|
+
confidenceLevel: confidenceLevelPersist,
|
|
1187
|
+
recommendation: outcome?.recommendation?.slice(0, 400) ?? null,
|
|
1188
|
+
agreedCount: outcome?.agreed?.length ?? 0,
|
|
1189
|
+
},
|
|
1190
|
+
});
|
|
1191
|
+
}
|
|
740
1192
|
// C2: Persist decisions.lock.md to the run directory so sprint-runner
|
|
741
1193
|
// can inject locked decisions into the implementation prompt.
|
|
742
1194
|
if (options?.runDir) {
|
|
743
1195
|
const rejectedProposals = detectOutOfStackProposals(synthesisText, spec);
|
|
1196
|
+
idealTrace("council.persist.writeDecisionsLock.before", { sessionId, runDir: options.runDir });
|
|
744
1197
|
await writeDecisionsLock({
|
|
745
1198
|
runId: sessionId,
|
|
746
1199
|
runDir: options.runDir,
|
|
@@ -758,14 +1211,22 @@ export async function* runCouncil(topic, sessionModelId, messages, sessionId, ra
|
|
|
758
1211
|
// only fires on an unexpected throw — log it (No-Silent-Catch), never break council.
|
|
759
1212
|
console.error(`[council] decisions.lock write guard caught: ${err?.message}`);
|
|
760
1213
|
});
|
|
1214
|
+
idealTrace("council.persist.writeDecisionsLock.after", { sessionId });
|
|
761
1215
|
}
|
|
762
1216
|
}
|
|
763
|
-
catch {
|
|
764
|
-
|
|
1217
|
+
catch (err) {
|
|
1218
|
+
// Persistence is best-effort (session-message / interaction-log writes),
|
|
1219
|
+
// but log so a storage fault is not mistaken for a hang in blocker-5
|
|
1220
|
+
// forensics.
|
|
1221
|
+
console.error(`[council] outcome persistence failed: ${err?.message}`);
|
|
1222
|
+
idealTrace("council.persist.threw", { sessionId, err: err?.message });
|
|
765
1223
|
}
|
|
766
1224
|
}
|
|
767
|
-
|
|
768
|
-
|
|
1225
|
+
idealTrace("council.persist.done", { sessionId });
|
|
1226
|
+
// Update session status to completed — EXCEPT when the user chose
|
|
1227
|
+
// "continue_session", where the agent keeps working in this session; marking
|
|
1228
|
+
// it completed here is what dropped it from the resume picker.
|
|
1229
|
+
if (sessionId && postDebateAction !== "continue_session") {
|
|
769
1230
|
try {
|
|
770
1231
|
new SessionStore(options?.cwd ?? process.cwd()).setStatus(sessionId, "completed");
|
|
771
1232
|
}
|
|
@@ -802,15 +1263,27 @@ export async function* runCouncil(topic, sessionModelId, messages, sessionId, ra
|
|
|
802
1263
|
stats.phases.push({ name: "execution", durationMs: Date.now() - execStart });
|
|
803
1264
|
}
|
|
804
1265
|
// ── Stats ───────────────────────────────────────────────────────────────────
|
|
1266
|
+
idealTrace("council.stats", { sessionId });
|
|
805
1267
|
const totalMs = Date.now() - stats.startMs;
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
1268
|
+
// Blocker-5 root cause: in sprintPlanningMode this runCouncil is a SUB-STEP of
|
|
1269
|
+
// runSprint, not a standalone turn. The terminal `{type:"done"}` chunk (and the
|
|
1270
|
+
// stats banner) are turn-terminal signals — forwarded verbatim by sprint-runner
|
|
1271
|
+
// they made the app's stream consumer STOP pulling right here, so the generator
|
|
1272
|
+
// suspended at these yields and never returned. sprint-runner's `planGen.next()`
|
|
1273
|
+
// therefore never saw `done`, and the Sprint Implementation stage never ran
|
|
1274
|
+
// (idle at the composer, no error). A sub-step must not emit them: skip both so
|
|
1275
|
+
// the generator returns cleanly and the sprint runner proceeds to implementation.
|
|
1276
|
+
if (!options?.sprintPlanningMode) {
|
|
1277
|
+
yield {
|
|
1278
|
+
type: "content",
|
|
1279
|
+
content: `\n---\n` +
|
|
1280
|
+
`> Council stats: ${stats.calls} API calls, ${(totalMs / 1000).toFixed(1)}s total, ` +
|
|
1281
|
+
`${active.length} participants, ${debateState.roundCount} rounds\n` +
|
|
1282
|
+
`> Phases: ${stats.phases.map((p) => `${p.name}=${(p.durationMs / 1000).toFixed(1)}s`).join(", ")}\n`,
|
|
1283
|
+
};
|
|
1284
|
+
yield { type: "done" };
|
|
1285
|
+
}
|
|
1286
|
+
idealTrace("council.return", { sessionId, synthesisLen: (synthesisText || "").length });
|
|
814
1287
|
return synthesisText || null;
|
|
815
1288
|
}
|
|
816
1289
|
// ── P7: action-item reuse helpers ─────────────────────────────────────────────
|