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
|
@@ -12,25 +12,140 @@
|
|
|
12
12
|
* Cost target: <200 input tokens, <10 output tokens per call (~$0.0001 on
|
|
13
13
|
* DeepSeek Flash). Timeout 2500ms — bails fast if the model stalls.
|
|
14
14
|
*/
|
|
15
|
+
import { appendFileSync } from "node:fs";
|
|
15
16
|
import { streamText } from "ai";
|
|
16
|
-
import {
|
|
17
|
+
import { getModelInfo, SWITCH_PROVIDER_ORDER } from "../models/registry.js";
|
|
17
18
|
import { getProviderCapabilities } from "../providers/capabilities.js";
|
|
18
|
-
import {
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
//
|
|
23
|
-
//
|
|
24
|
-
//
|
|
25
|
-
//
|
|
26
|
-
//
|
|
27
|
-
//
|
|
28
|
-
|
|
29
|
-
//
|
|
30
|
-
//
|
|
31
|
-
|
|
32
|
-
|
|
19
|
+
import { getConfiguredProviders, loadKeyForProvider, ProviderKeyMissingError } from "../providers/keychain.js";
|
|
20
|
+
import { createProviderFactoryAsync, resolveModelRuntime } from "../providers/runtime.js";
|
|
21
|
+
import { getRoutedModelByTier } from "../router/peak-hour.js";
|
|
22
|
+
import { isProviderDisabled } from "../utils/settings.js";
|
|
23
|
+
// Single flat classify ceiling for EVERY model. Harness-measured latency
|
|
24
|
+
// (2026-07-15, grok-composer via OAuth, 7 samples) is 1045–1277ms — no tested
|
|
25
|
+
// model (agentic balanced OR fast flash) approaches even the legacy 2.5s cap,
|
|
26
|
+
// so a tier-scaled timeout was solving a non-existent latency problem: the
|
|
27
|
+
// /ideal over-engineering root cause was NOT a timeout abort but grok-composer
|
|
28
|
+
// ignoring the terse contract (it emits task-planning prose, never the 8-word
|
|
29
|
+
// line → null → fail-open). The ceiling is a pure safety net: a healthy model
|
|
30
|
+
// returns in <1.5s, so the headroom only bites a genuinely stuck call. Data-
|
|
31
|
+
// driven off measurement, not an identity/tier proxy string.
|
|
32
|
+
const CLASSIFY_TIMEOUT_MS = 8000;
|
|
33
|
+
// Absolute wall-clock cap for the WHOLE classify (every candidate AND its
|
|
34
|
+
// self-repair). Enforced via a shared deadline passed into attemptClassify, so
|
|
35
|
+
// a run of dead/hanging keys — or one legitimately slow reasoning model —
|
|
36
|
+
// degrades to fail-open in bounded time. Sized to fit a healthy reasoning fast
|
|
37
|
+
// model twice over: deepseek-v4-flash measured 2026-07-15 answers a classify in
|
|
38
|
+
// ~2–5s (with a valid key), so 10s leaves room for one candidate + a self-repair
|
|
39
|
+
// before the deadline. A dead key ahead of it (auth-fails in ~0.2s) barely eats
|
|
40
|
+
// the budget; only a genuinely hanging candidate consumes it.
|
|
41
|
+
const CLASSIFY_TOTAL_BUDGET_MS = 10_000;
|
|
42
|
+
// Floor for any single streamText attempt's own timeout, so a nearly-exhausted
|
|
43
|
+
// deadline still gives a final candidate a real (if short) chance rather than an
|
|
44
|
+
// instant abort.
|
|
45
|
+
const CLASSIFY_MIN_ATTEMPT_MS = 1200;
|
|
46
|
+
// Eight comma-separated words now (added <clarity>) — ~20-30 tokens worst case
|
|
47
|
+
// ("documentation,balanced,task,report,standard,ecosystem,vietnamese,underspecified").
|
|
48
|
+
// 56 keeps headroom without padding (the model still stops after eight words).
|
|
49
|
+
const NONREASONING_MAX_OUTPUT_TOKENS = 56;
|
|
33
50
|
const REASONING_MAX_OUTPUT_TOKENS = 2048;
|
|
51
|
+
/**
|
|
52
|
+
* Compute the classify call budget from the resolved model's catalog metadata.
|
|
53
|
+
*
|
|
54
|
+
* TIMEOUT is a flat safety-net ceiling for all models — see CLASSIFY_TIMEOUT_MS.
|
|
55
|
+
* Measurement showed classify latency is provider-independent and well under
|
|
56
|
+
* the ceiling, so keying the timeout off `tier`/`reasoning` added no value and
|
|
57
|
+
* amounted to an identity-proxy soft-hardcode.
|
|
58
|
+
*
|
|
59
|
+
* MAX-OUTPUT scales with REASONING only: a reasoning model burns its output
|
|
60
|
+
* budget on reasoning tokens before any visible text, so it needs the room in
|
|
61
|
+
* tokens; a non-reasoning model emits the 8 words directly. This knob is real —
|
|
62
|
+
* it is the fix for reasoning models routing the verdict into the reasoning
|
|
63
|
+
* channel — and stays decoupled from the (now flat) timeout.
|
|
64
|
+
*/
|
|
65
|
+
export function classifierBudget(modelInfo) {
|
|
66
|
+
const isReasoning = modelInfo?.reasoning === true;
|
|
67
|
+
return {
|
|
68
|
+
isReasoning,
|
|
69
|
+
timeoutMs: CLASSIFY_TIMEOUT_MS,
|
|
70
|
+
maxOutputTokens: isReasoning ? REASONING_MAX_OUTPUT_TOKENS : NONREASONING_MAX_OUTPUT_TOKENS,
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Session-scoped cache of cross-provider factories built for the throwaway
|
|
75
|
+
* classify. Keyed by provider so the OAuth-aware build (keychain read + token
|
|
76
|
+
* refresh) happens at most once per provider per process, not per turn.
|
|
77
|
+
*/
|
|
78
|
+
const crossFactoryCache = new Map();
|
|
79
|
+
/** Test seam — clear the cross-provider factory cache between specs. */
|
|
80
|
+
export function __resetClassifyFactoryCache() {
|
|
81
|
+
crossFactoryCache.clear();
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Build (or reuse) a real factory for a DIFFERENT provider than the session's,
|
|
85
|
+
* so the throwaway classify can run on a keyed instruction-following model when
|
|
86
|
+
* the session provider has none. Mirrors the council's `resolveCouncilFactory`:
|
|
87
|
+
* `loadKeyForProvider` for API-key providers, falling back to
|
|
88
|
+
* `createProviderFactoryAsync` for OAuth-only providers (injects the bearer
|
|
89
|
+
* token). Failures degrade gracefully (logged, returns undefined → caller keeps
|
|
90
|
+
* the session model). Never throws.
|
|
91
|
+
*/
|
|
92
|
+
async function resolveCrossProviderClassifyFactory(providerId) {
|
|
93
|
+
const cached = crossFactoryCache.get(providerId);
|
|
94
|
+
if (cached)
|
|
95
|
+
return cached;
|
|
96
|
+
try {
|
|
97
|
+
let apiKey;
|
|
98
|
+
try {
|
|
99
|
+
apiKey = await loadKeyForProvider(providerId);
|
|
100
|
+
}
|
|
101
|
+
catch (err) {
|
|
102
|
+
if (!(err instanceof ProviderKeyMissingError))
|
|
103
|
+
throw err;
|
|
104
|
+
// OAuth-only provider — createProviderFactoryAsync injects the bearer token.
|
|
105
|
+
}
|
|
106
|
+
const { factory } = await createProviderFactoryAsync(providerId, apiKey ? { apiKey } : {});
|
|
107
|
+
crossFactoryCache.set(providerId, factory);
|
|
108
|
+
return factory;
|
|
109
|
+
}
|
|
110
|
+
catch (err) {
|
|
111
|
+
console.error(`[pil.llm-classify] cross-provider classify factory build failed for ${providerId}: ${err?.message}`);
|
|
112
|
+
return undefined;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* Ordered list of keyed fast-tier models from providers OTHER than the session's,
|
|
117
|
+
* for the throwaway classify. Candidate order is the catalog's vendor-defined
|
|
118
|
+
* `switch_provider_order` (zero-hardcode), gated by `getConfiguredProviders`
|
|
119
|
+
* (the authoritative credential check — unifies API key / env / OAuth) and the
|
|
120
|
+
* user's disabled-provider setting. The caller tries them in order and falls
|
|
121
|
+
* through to the next on an auth/stream failure — so a configured-but-DEAD key
|
|
122
|
+
* (e.g. an expired deepseek key) doesn't strand the classify at fail-open.
|
|
123
|
+
* Empty when no other provider is configured with a fast tier → caller keeps the
|
|
124
|
+
* session model (status quo).
|
|
125
|
+
*/
|
|
126
|
+
async function pickCrossProviderClassifyModels(excludeProvider) {
|
|
127
|
+
let configured;
|
|
128
|
+
try {
|
|
129
|
+
configured = new Set(await getConfiguredProviders());
|
|
130
|
+
}
|
|
131
|
+
catch (err) {
|
|
132
|
+
console.error(`[pil.llm-classify] getConfiguredProviders failed for classify route: ${err?.message}`);
|
|
133
|
+
return [];
|
|
134
|
+
}
|
|
135
|
+
const out = [];
|
|
136
|
+
for (const p of SWITCH_PROVIDER_ORDER) {
|
|
137
|
+
if (p === excludeProvider)
|
|
138
|
+
continue;
|
|
139
|
+
if (isProviderDisabled(p))
|
|
140
|
+
continue;
|
|
141
|
+
if (!configured.has(p))
|
|
142
|
+
continue;
|
|
143
|
+
const m = getRoutedModelByTier("fast", p);
|
|
144
|
+
if (m && m.provider === p)
|
|
145
|
+
out.push({ modelId: m.id, providerId: p });
|
|
146
|
+
}
|
|
147
|
+
return out;
|
|
148
|
+
}
|
|
34
149
|
/**
|
|
35
150
|
* Per-namespace shallow merge of providerOptions. The base already carries
|
|
36
151
|
* factory-level defaults folded into the provider namespace (e.g. OAuth
|
|
@@ -85,8 +200,11 @@ const KNOWN_CLASSIFY_WORDS = new Set([
|
|
|
85
200
|
"heavy",
|
|
86
201
|
"ecosystem",
|
|
87
202
|
"local",
|
|
203
|
+
"clear",
|
|
204
|
+
"underspecified",
|
|
88
205
|
]);
|
|
89
|
-
const SYSTEM_PROMPT = "You classify user prompts for a coding assistant. Reply with ONE line of
|
|
206
|
+
const SYSTEM_PROMPT = "You classify user prompts for a coding assistant. Reply with ONE line of EIGHT lowercase words separated by commas: <taskType>,<style>,<intent>,<deliverable>,<depth>,<scope>,<lang>,<clarity>\n\n" +
|
|
207
|
+
"The message may be preceded by a '[RECENT CONVERSATION]' block. Use it ONLY to resolve what a terse follow-up refers to (e.g. 'từ các phần đó', 'làm tiếp', 'debate mode đi', 'this one'); then classify the NEW message. Crucially, if the new message points back at heavy prior work, its depth is the depth of THAT work — a short sentence like 'ok debate these parts and plan improvements' is NOT quick just because it is short. Never classify the conversation block itself.\n\n" +
|
|
90
208
|
"taskType ∈ { refactor | debug | plan | analyze | documentation | generate | general }\n" +
|
|
91
209
|
"style ∈ { concise | balanced | detailed }\n" +
|
|
92
210
|
"intent ∈ { task | chat } — 'chat' ONLY for a pure greeting, thanks, or acknowledgement with NO work request (e.g. 'hi', 'cảm ơn nhé', 'ok great'). EVERYTHING else is 'task', including questions about code or the CLI, 'are you done?', and requests to call a tool. When unsure, choose 'task'.\n" +
|
|
@@ -99,8 +217,12 @@ const SYSTEM_PROMPT = "You classify user prompts for a coding assistant. Reply w
|
|
|
99
217
|
"- quick — a trivial single-shot change or a small direct answer: typo, rename one symbol, one-line edit, a quick lookup, 'what does X do'. No plan needed.\n" +
|
|
100
218
|
"- standard — ordinary feature or bugfix touching a handful of files/functions; needs a short plan + a verify step, but no upfront research or user discussion.\n" +
|
|
101
219
|
"- heavy — architectural, cross-cutting, multi-file/multi-module, a migration, 'redo/rebuild', a vague 'make it better', or a request with real unresolved design choices. Needs discussion + research + a checked plan before any code.\n" +
|
|
220
|
+
" BREADTH decides heavy, NOT how clearly the steps are spelled out. A migration, vendoring an external dependency's code in-tree, or a rename/restructure that spans MANY files or modules is ALWAYS heavy — even when the plan is fully specified and it 'just' has to keep tests green. Do not downgrade a wide change to standard because it sounds mechanical.\n" +
|
|
102
221
|
" For a pure question/answer (deliverable=answer), depth reflects how much investigation the answer needs: 'quick' for a simple fact, 'standard' for a normal explanation, 'heavy' for a deep architectural review.\n" +
|
|
103
222
|
" When unsure between quick and standard, choose standard. When the task is genuinely wide or ambiguous, choose heavy.\n" +
|
|
223
|
+
"clarity ∈ { clear | underspecified } — whether the request gives enough to proceed WITHOUT guessing:\n" +
|
|
224
|
+
"- underspecified — missing information the agent would need: an unstated target/scope ('add auth' — which flow?), a vague 'make it better' with no direction, competing interpretations, or an unresolved design choice. Such a task should be clarified with the user before code.\n" +
|
|
225
|
+
"- clear — well-specified enough to plan and execute directly, even if large. A fully-spelled-out migration is 'clear'. When unsure, choose 'clear' (do NOT over-ask on ordinary work).\n" +
|
|
104
226
|
"scope ∈ { ecosystem | local }:\n" +
|
|
105
227
|
"- ecosystem — the turn is about the Muonroi PLATFORM as a whole: the building-block / .NET packages, open-core boundary, the rule engine / decision tables, NuGet packages, or platform setup/install. These are documented in an authoritative docs source.\n" +
|
|
106
228
|
"- local — EVERYTHING else, including questions about this CLI's own internals (even when they mention the word 'muonroi'). When unsure, choose local.\n" +
|
|
@@ -129,21 +251,29 @@ const SYSTEM_PROMPT = "You classify user prompts for a coding assistant. Reply w
|
|
|
129
251
|
"- documentation → balanced (examples + explanation)\n" +
|
|
130
252
|
"- general → concise\n" +
|
|
131
253
|
"Only output 'detailed' if the user prompt LITERALLY contains words like 'explain in detail', 'thorough analysis', 'walk me through', 'giải thích chi tiết', 'phân tích kỹ'.\n\n" +
|
|
132
|
-
"Full examples (taskType,style,intent,deliverable,depth,scope,lang):\n" +
|
|
133
|
-
"- 'hi' → general,concise,chat,answer,quick,local,english\n" +
|
|
134
|
-
"- 'cảm ơn bạn nhé' → general,concise,chat,answer,quick,local,vietnamese\n" +
|
|
135
|
-
"- 'bạn xong chưa' → general,concise,task,answer,quick,local,vietnamese (a question — NOT chat)\n" +
|
|
136
|
-
"- 'fix the typo in the README title' → generate,concise,task,code,quick,local,english\n" +
|
|
137
|
-
"- 'fix CI failing on Windows' → debug,concise,task,code,standard,local,english\n" +
|
|
138
|
-
"- 'rename function shouldInject to needsReminder' → refactor,concise,task,code,quick,local,english\n" +
|
|
139
|
-
"- 'thêm caching cho provider layer và update tests' → generate,concise,task,code,standard,local,vietnamese\n" +
|
|
140
|
-
"- 'tại sao bash_output_get trả empty' → analyze,concise,task,answer,standard,local,vietnamese\n" +
|
|
141
|
-
"- 'liệt kê tất cả env var CLI đọc' → analyze,concise,task,report,standard,local,vietnamese\n" +
|
|
142
|
-
"- 'refactor the entire auth system to use OAuth' → refactor,concise,task,code,heavy,local,english\n" +
|
|
143
|
-
"- '
|
|
144
|
-
"- '
|
|
145
|
-
"- '
|
|
146
|
-
"
|
|
254
|
+
"Full examples (taskType,style,intent,deliverable,depth,scope,lang,clarity):\n" +
|
|
255
|
+
"- 'hi' → general,concise,chat,answer,quick,local,english,clear\n" +
|
|
256
|
+
"- 'cảm ơn bạn nhé' → general,concise,chat,answer,quick,local,vietnamese,clear\n" +
|
|
257
|
+
"- 'bạn xong chưa' → general,concise,task,answer,quick,local,vietnamese,clear (a question — NOT chat)\n" +
|
|
258
|
+
"- 'fix the typo in the README title' → generate,concise,task,code,quick,local,english,clear\n" +
|
|
259
|
+
"- 'fix CI failing on Windows' → debug,concise,task,code,standard,local,english,clear\n" +
|
|
260
|
+
"- 'rename function shouldInject to needsReminder' → refactor,concise,task,code,quick,local,english,clear\n" +
|
|
261
|
+
"- 'thêm caching cho provider layer và update tests' → generate,concise,task,code,standard,local,vietnamese,clear\n" +
|
|
262
|
+
"- 'tại sao bash_output_get trả empty' → analyze,concise,task,answer,standard,local,vietnamese,clear\n" +
|
|
263
|
+
"- 'liệt kê tất cả env var CLI đọc' → analyze,concise,task,report,standard,local,vietnamese,clear\n" +
|
|
264
|
+
"- 'refactor the entire auth system to use OAuth' → refactor,concise,task,code,heavy,local,english,clear\n" +
|
|
265
|
+
"- 'vendor the used subset of the gsd package natively into src and rename gsd to workflow, keep tests green' → refactor,concise,task,code,heavy,local,english,clear (a migration spanning many files — heavy even though fully specified)\n" +
|
|
266
|
+
"- 'add auth' → generate,concise,task,code,standard,local,english,underspecified (which flow/provider? unstated)\n" +
|
|
267
|
+
"- 'làm cho CLI tốt hơn' → generate,concise,task,code,heavy,local,vietnamese,underspecified (vague 'make it better', no target)\n" +
|
|
268
|
+
"- 'how does the building-block rule engine work' → analyze,concise,task,answer,standard,ecosystem,english,clear\n" +
|
|
269
|
+
"- 'hệ sinh thái muonroi gồm những gì' → analyze,balanced,task,answer,standard,ecosystem,vietnamese,clear\n" +
|
|
270
|
+
"- 'plan the migration to hooks' → plan,balanced,task,report,heavy,local,english,clear\n\n" +
|
|
271
|
+
"Prompts may be Vietnamese, English, or mixed. Reply with exactly eight words separated by commas. No other text.";
|
|
272
|
+
// Appended to SYSTEM_PROMPT on the self-repair retry (see createLlmClassifier).
|
|
273
|
+
// The first attempt produced an unparseable reply; this reminder + the full
|
|
274
|
+
// (untrimmed) prompt is the agent-first recovery the design mandates INSTEAD of
|
|
275
|
+
// a keyword-regex fallback.
|
|
276
|
+
const CLASSIFY_REPAIR_INSTRUCTION = "REPAIR MODE: your previous reply could NOT be parsed. Output NOTHING except the single line of eight lowercase words separated by commas — no prose, no explanation, no code fences, no quotes. If you are unsure of a field, pick the safe default (task, standard, clear, local).";
|
|
147
277
|
function parseResponse(raw) {
|
|
148
278
|
const cleaned = raw.trim().toLowerCase().replace(/[`*"]/g, "");
|
|
149
279
|
const firstLine = cleaned.split(/\r?\n/)[0] ?? "";
|
|
@@ -177,6 +307,12 @@ function parseResponse(raw) {
|
|
|
177
307
|
// anything else (incl. absent) → not ecosystem. Position-independent.
|
|
178
308
|
const scopeWord = parts.find((p) => p === "ecosystem" || p === "local");
|
|
179
309
|
const ecosystemScope = scopeWord ? scopeWord === "ecosystem" : null;
|
|
310
|
+
// Eighth word is the clarity signal. "underspecified" → the request is missing
|
|
311
|
+
// information the agent needs → earn a clarify/council pass. Anything else
|
|
312
|
+
// (incl. absent) → not underspecified (don't-over-ask safe direction).
|
|
313
|
+
// Position-independent so a reordered/garbled reply still recovers it.
|
|
314
|
+
const clarityWord = parts.find((p) => p === "clear" || p === "underspecified");
|
|
315
|
+
const needsClarification = clarityWord ? clarityWord === "underspecified" : null;
|
|
180
316
|
// Seventh word is the user's language. It is the one alphabetic token that is
|
|
181
317
|
// NOT a known enum value (open vocabulary). null when English / absent so
|
|
182
318
|
// Layer 4 skips the language re-anchor for English turns.
|
|
@@ -191,6 +327,7 @@ function parseResponse(raw) {
|
|
|
191
327
|
intentKind,
|
|
192
328
|
deliverableKind,
|
|
193
329
|
depthTier,
|
|
330
|
+
needsClarification,
|
|
194
331
|
ecosystemScope,
|
|
195
332
|
replyLanguage,
|
|
196
333
|
};
|
|
@@ -202,26 +339,64 @@ function parseResponse(raw) {
|
|
|
202
339
|
* Returns null if the call fails / times out / parses to garbage. Callers must
|
|
203
340
|
* fail-open (keep prior taskType, do not block the turn).
|
|
204
341
|
*/
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
342
|
+
/**
|
|
343
|
+
* Order the classify candidate list.
|
|
344
|
+
*
|
|
345
|
+
* - `hasSameFast` (e.g. openai → gpt-5.4-mini): the same-provider fast tier is a
|
|
346
|
+
* real fast model, so try it FIRST, then keyed cross-provider models as a
|
|
347
|
+
* FALLBACK. This is the fix for the failure-not-just-absence case: an
|
|
348
|
+
* openai-OAuth session's fast tier can 401 on the api-key path — previously
|
|
349
|
+
* the chain stopped there and fail-opened to "standard" (so /ideal
|
|
350
|
+
* over-councilled trivial tasks). Appending the cross-provider models lets a
|
|
351
|
+
* working alternative (a keyed deepseek/opencode fast model) rescue it.
|
|
352
|
+
* - No same-provider fast tier (e.g. xai, whose session model is agentic and
|
|
353
|
+
* ignores the terse contract): try keyed cross-provider fast models FIRST and
|
|
354
|
+
* keep the session model as the last-resort candidate.
|
|
355
|
+
*/
|
|
356
|
+
export function orderClassifyCandidates(args) {
|
|
357
|
+
const candidates = [];
|
|
358
|
+
if (args.hasSameFast) {
|
|
359
|
+
candidates.push({ modelId: args.primaryModelId, providerId: null });
|
|
360
|
+
for (const m of args.crossModels)
|
|
361
|
+
candidates.push({ modelId: m.modelId, providerId: m.providerId });
|
|
362
|
+
}
|
|
363
|
+
else {
|
|
364
|
+
for (const m of args.crossModels)
|
|
365
|
+
candidates.push({ modelId: m.modelId, providerId: m.providerId });
|
|
366
|
+
candidates.push({ modelId: args.primaryModelId, providerId: null });
|
|
367
|
+
}
|
|
368
|
+
return candidates;
|
|
369
|
+
}
|
|
370
|
+
export function createLlmClassifier(modelId, classifyOpts) {
|
|
371
|
+
return async function classify(prompt, opts) {
|
|
372
|
+
const signal = opts?.signal;
|
|
373
|
+
const recentTurns = opts?.recentTurns;
|
|
374
|
+
const debug = process.env.MUONROI_DEBUG_PIL_CLASSIFY === "1";
|
|
375
|
+
// Bounded recent-conversation block so the classifier can resolve back-
|
|
376
|
+
// references in a terse follow-up ("từ các phần đó", "làm tiếp", "this")
|
|
377
|
+
// instead of scoring the isolated sentence. Same for every candidate.
|
|
378
|
+
const trimmedRecent = recentTurns?.trim();
|
|
379
|
+
const promptWithContext = trimmedRecent
|
|
380
|
+
? `[RECENT CONVERSATION — reference only, do NOT classify this]\n${trimmedRecent.slice(0, 800)}\n\n` +
|
|
381
|
+
`[NEW USER MESSAGE — classify THIS; if it refers back to the conversation above, judge the depth of the work it actually entails]\n${prompt.slice(0, 600)}`
|
|
382
|
+
: prompt.slice(0, 600);
|
|
383
|
+
const fullRecent = recentTurns?.trim();
|
|
384
|
+
const repairPrompt = (fullRecent
|
|
385
|
+
? `[RECENT CONVERSATION — reference only, do NOT classify this]\n${fullRecent.slice(0, 1500)}\n\n`
|
|
386
|
+
: "") + `[NEW USER MESSAGE — classify THIS]\n${prompt.slice(0, 1500)}`;
|
|
387
|
+
// One classify attempt against a single resolved model, including the
|
|
388
|
+
// self-repair retry on that same model. Returns the parsed verdict, or null
|
|
389
|
+
// (unparseable OR provider/stream error) so the caller can fall through to
|
|
390
|
+
// the next candidate. Each attempt owns its AbortController/timer.
|
|
391
|
+
const attemptClassify = async (runtime, cmId, deadlineMs) => {
|
|
392
|
+
// Max-output scales with reasoning; each streamText timeout is bounded by
|
|
393
|
+
// the shared deadline (min of the flat ceiling and the time left), so the
|
|
394
|
+
// whole classify — this attempt AND its repair — never overruns the budget.
|
|
395
|
+
const { isReasoning } = classifierBudget(runtime.modelInfo);
|
|
396
|
+
const timeoutMs = Math.max(CLASSIFY_MIN_ATTEMPT_MS, Math.min(CLASSIFY_TIMEOUT_MS, deadlineMs - Date.now()));
|
|
219
397
|
const dropMaxTokens = runtime.unsupportedParams?.includes("maxOutputTokens") === true;
|
|
220
398
|
const maxOut = isReasoning ? REASONING_MAX_OUTPUT_TOKENS : NONREASONING_MAX_OUTPUT_TOKENS;
|
|
221
|
-
// Minimize reasoning cost: force the lowest effort the provider exposes
|
|
222
|
-
// this throwaway 2-word classification. Only providers with
|
|
223
|
-
// `supportsReasoningEffort` (openai, xai) honor it; deepseek has no per-call
|
|
224
|
-
// knob (disable via MUONROI_DEEPSEEK_DISABLE_THINKING at the factory).
|
|
399
|
+
// Minimize reasoning cost: force the lowest effort the provider exposes.
|
|
225
400
|
let providerOptions = runtime.providerOptions;
|
|
226
401
|
if (isReasoning && runtime.modelInfo?.supportsReasoningEffort && runtime.modelInfo.provider) {
|
|
227
402
|
const lowEffort = getProviderCapabilities(runtime.modelInfo.provider).buildProviderOptions({
|
|
@@ -230,34 +405,186 @@ export function createLlmClassifier(factory, modelId) {
|
|
|
230
405
|
});
|
|
231
406
|
providerOptions = mergeProviderOptions(runtime.providerOptions, lowEffort);
|
|
232
407
|
}
|
|
233
|
-
const
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
408
|
+
const controller = new AbortController();
|
|
409
|
+
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
410
|
+
const combinedSignal = signal
|
|
411
|
+
? (AbortSignal.any?.([signal, controller.signal]) ?? controller.signal)
|
|
412
|
+
: controller.signal;
|
|
413
|
+
try {
|
|
414
|
+
const t0 = Date.now();
|
|
415
|
+
const result = streamText({
|
|
416
|
+
model: runtime.model,
|
|
417
|
+
abortSignal: combinedSignal,
|
|
418
|
+
system: SYSTEM_PROMPT,
|
|
419
|
+
prompt: promptWithContext,
|
|
420
|
+
...(dropMaxTokens ? {} : { maxOutputTokens: maxOut }),
|
|
421
|
+
...(providerOptions ? { providerOptions } : {}),
|
|
422
|
+
});
|
|
423
|
+
let text = "";
|
|
424
|
+
let reasoningText = "";
|
|
425
|
+
let streamError = "";
|
|
426
|
+
const partCounts = {};
|
|
427
|
+
for await (const part of result.fullStream) {
|
|
428
|
+
if (debug)
|
|
429
|
+
partCounts[part.type] = (partCounts[part.type] ?? 0) + 1;
|
|
430
|
+
if (part.type === "text-delta")
|
|
431
|
+
text += part.textDelta ?? part.text ?? "";
|
|
432
|
+
else if (part.type === "reasoning-delta")
|
|
433
|
+
reasoningText += part.textDelta ?? part.text ?? "";
|
|
434
|
+
else if (part.type === "error") {
|
|
435
|
+
const e = part.error;
|
|
436
|
+
streamError = e instanceof Error ? e.message : String(e ?? "unknown");
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
const elapsedMs = Date.now() - t0;
|
|
440
|
+
if (debug) {
|
|
441
|
+
console.error(`[pil.llm-classify] raw(${cmId}${cmId !== modelId ? `←${modelId}` : ""}) ` +
|
|
442
|
+
`maxOut=${dropMaxTokens ? "dropped" : maxOut} streamError=${JSON.stringify(streamError)} ` +
|
|
443
|
+
`parts=${JSON.stringify(partCounts)} text<<<${text}>>> reasoning<<<${reasoningText.slice(0, 200)}>>>`);
|
|
444
|
+
}
|
|
445
|
+
// Reasoning models occasionally route the entire answer into reasoning
|
|
446
|
+
// parts (no committed text). Fall back to the reasoning channel.
|
|
447
|
+
let parsed = parseResponse(text) ?? (reasoningText ? parseResponse(reasoningText) : null);
|
|
448
|
+
// Metrics-only probe sink (env-gated): latency + parsed depth + any
|
|
449
|
+
// stream error. NO prompt/response content — safe to leave wired.
|
|
450
|
+
const probeLog = process.env.MUONROI_CLASSIFY_LATENCY_LOG;
|
|
451
|
+
if (probeLog) {
|
|
452
|
+
try {
|
|
453
|
+
appendFileSync(probeLog, `${JSON.stringify({
|
|
454
|
+
model: cmId,
|
|
455
|
+
from: modelId === cmId ? undefined : modelId,
|
|
456
|
+
tier: runtime.modelInfo?.tier,
|
|
457
|
+
reasoning: isReasoning,
|
|
458
|
+
timeoutMs,
|
|
459
|
+
elapsedMs,
|
|
460
|
+
textLen: text.length,
|
|
461
|
+
reasoningLen: reasoningText.length,
|
|
462
|
+
parsed: parsed != null,
|
|
463
|
+
depth: parsed?.depthTier ?? null,
|
|
464
|
+
streamError: streamError || undefined,
|
|
465
|
+
})}\n`);
|
|
466
|
+
}
|
|
467
|
+
catch (e) {
|
|
468
|
+
console.error(`[pil.llm-classify] probe-log write failed: ${e?.message}`);
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
if (parsed)
|
|
472
|
+
return parsed;
|
|
473
|
+
// Surface a swallowed provider/transport error (No-Silent-Catch): a
|
|
474
|
+
// stream `error` part means the call FAILED (auth/key/rate-limit), which
|
|
475
|
+
// is categorically different from unparseable text. Before this fix such
|
|
476
|
+
// errors vanished into a silent null → fail-open "standard". On error we
|
|
477
|
+
// skip the same-model self-repair (it would fail identically) and let the
|
|
478
|
+
// caller fall through to the next provider candidate.
|
|
479
|
+
if (streamError) {
|
|
480
|
+
console.error(`[pil.llm-classify] stream error on ${cmId} (from ${modelId}): ${streamError} — trying next candidate`);
|
|
481
|
+
return null;
|
|
482
|
+
}
|
|
483
|
+
// Self-repair (agent-first recovery — NOT a regex fallback): the reply
|
|
484
|
+
// did not parse. Call the SAME model once more with the full prompt + an
|
|
485
|
+
// explicit format-repair instruction on a doubled budget. Skip it when
|
|
486
|
+
// too little of the shared deadline remains (the caller then falls
|
|
487
|
+
// through to the next candidate / fails open).
|
|
488
|
+
clearTimeout(timer);
|
|
489
|
+
const repairBudget = deadlineMs - Date.now();
|
|
490
|
+
if (repairBudget < 1500)
|
|
491
|
+
return null;
|
|
492
|
+
const repairTimeout = Math.max(CLASSIFY_MIN_ATTEMPT_MS, Math.min(CLASSIFY_TIMEOUT_MS, repairBudget));
|
|
493
|
+
const repairController = new AbortController();
|
|
494
|
+
const repairTimer = setTimeout(() => repairController.abort(), repairTimeout);
|
|
495
|
+
const repairSignal = signal
|
|
496
|
+
? (AbortSignal.any?.([signal, repairController.signal]) ?? repairController.signal)
|
|
497
|
+
: repairController.signal;
|
|
498
|
+
try {
|
|
499
|
+
const repairRun = streamText({
|
|
500
|
+
model: runtime.model,
|
|
501
|
+
abortSignal: repairSignal,
|
|
502
|
+
system: `${SYSTEM_PROMPT}\n\n${CLASSIFY_REPAIR_INSTRUCTION}`,
|
|
503
|
+
prompt: repairPrompt,
|
|
504
|
+
...(dropMaxTokens ? {} : { maxOutputTokens: maxOut * 2 }),
|
|
505
|
+
...(providerOptions ? { providerOptions } : {}),
|
|
506
|
+
});
|
|
507
|
+
let rt = "";
|
|
508
|
+
let rr = "";
|
|
509
|
+
for await (const part of repairRun.fullStream) {
|
|
510
|
+
if (part.type === "text-delta")
|
|
511
|
+
rt += part.textDelta ?? part.text ?? "";
|
|
512
|
+
else if (part.type === "reasoning-delta")
|
|
513
|
+
rr += part.textDelta ?? part.text ?? "";
|
|
514
|
+
}
|
|
515
|
+
parsed = parseResponse(rt) ?? (rr ? parseResponse(rr) : null);
|
|
516
|
+
if (parsed)
|
|
517
|
+
console.error(`[pil.llm-classify] self-repair recovered classification (${cmId})`);
|
|
518
|
+
return parsed;
|
|
519
|
+
}
|
|
520
|
+
finally {
|
|
521
|
+
clearTimeout(repairTimer);
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
catch (err) {
|
|
525
|
+
console.error(`[pil.llm-classify] classify attempt failed on ${cmId}: ${err?.message}`, {
|
|
526
|
+
stack: err?.stack?.split("\n").slice(0, 3),
|
|
527
|
+
});
|
|
528
|
+
return null;
|
|
529
|
+
}
|
|
530
|
+
finally {
|
|
531
|
+
clearTimeout(timer);
|
|
532
|
+
}
|
|
533
|
+
};
|
|
534
|
+
try {
|
|
535
|
+
// Build the ordered candidate list. Primary = same-provider fast tier (or
|
|
536
|
+
// the session model); on NO same-provider fast tier (e.g. xai) append keyed
|
|
537
|
+
// cross-provider fast models. Measured 2026-07-15: an agentic session model
|
|
538
|
+
// (grok-composer) ignores the terse contract and emits task-planning prose
|
|
539
|
+
// → null → fail-open "standard" (the /ideal over-engineering root cause);
|
|
540
|
+
// and a configured-but-dead key (expired deepseek) errors. Trying
|
|
541
|
+
// candidates in order until one parses fixes both.
|
|
542
|
+
let candidates = [];
|
|
543
|
+
const provider = getModelInfo(modelId)?.provider;
|
|
544
|
+
let primaryModelId = modelId;
|
|
545
|
+
if (classifyOpts?.routeFastTier) {
|
|
546
|
+
const sameFast = provider ? getRoutedModelByTier("fast", provider) : undefined;
|
|
547
|
+
if (sameFast && sameFast.id !== modelId)
|
|
548
|
+
primaryModelId = sameFast.id;
|
|
549
|
+
// Cross-provider models are a FALLBACK appended whenever crossProviderFallback
|
|
550
|
+
// is set — NOT only when the session provider lacks a fast tier. Measured
|
|
551
|
+
// 2026-07-16: an openai-OAuth session's fast tier (gpt-5.4-mini) 401s on the
|
|
552
|
+
// api-key path; with the old absence-only gate the chain had no fallback and
|
|
553
|
+
// fail-opened to "standard", so /ideal over-councilled trivial tasks. Ordering
|
|
554
|
+
// (same-fast-first vs cross-first) is decided by orderClassifyCandidates.
|
|
555
|
+
const crossModels = classifyOpts?.crossProviderFallback && provider ? await pickCrossProviderClassifyModels(provider) : [];
|
|
556
|
+
candidates = orderClassifyCandidates({ primaryModelId, hasSameFast: !!sameFast, crossModels });
|
|
252
557
|
}
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
`parts=${JSON.stringify(partCounts)} text<<<${text}>>> reasoning<<<${reasoningText.slice(0, 200)}>>>`);
|
|
558
|
+
else {
|
|
559
|
+
candidates.push({ modelId: primaryModelId, providerId: null });
|
|
256
560
|
}
|
|
257
|
-
//
|
|
258
|
-
//
|
|
259
|
-
//
|
|
260
|
-
|
|
561
|
+
// Shared absolute deadline bounds the whole chain (each attempt + its
|
|
562
|
+
// repair). A lone same-provider candidate (the common case) simply gets
|
|
563
|
+
// the full flat ceiling within it.
|
|
564
|
+
const chainDeadline = Date.now() + CLASSIFY_TOTAL_BUDGET_MS;
|
|
565
|
+
for (const cand of candidates) {
|
|
566
|
+
if (chainDeadline - Date.now() < 750)
|
|
567
|
+
break; // too little left → fail-open
|
|
568
|
+
// A cross-provider candidate's factory must exist in the registry before
|
|
569
|
+
// resolveModelRuntime can derive it — the session only ever warmed its
|
|
570
|
+
// own. A provider we cannot build (no key, no OAuth) simply drops out.
|
|
571
|
+
if (cand.providerId && !(await resolveCrossProviderClassifyFactory(cand.providerId)))
|
|
572
|
+
continue;
|
|
573
|
+
let runtime;
|
|
574
|
+
try {
|
|
575
|
+
runtime = resolveModelRuntime(cand.modelId);
|
|
576
|
+
}
|
|
577
|
+
catch (e) {
|
|
578
|
+
console.error(`[pil.llm-classify] resolveModelRuntime failed for ${cand.modelId}: ${e?.message}`);
|
|
579
|
+
continue;
|
|
580
|
+
}
|
|
581
|
+
const res = await attemptClassify(runtime, cand.modelId, chainDeadline);
|
|
582
|
+
if (res)
|
|
583
|
+
return res;
|
|
584
|
+
}
|
|
585
|
+
console.error(`[pil.llm-classify] all ${candidates.length} candidate(s) failed — surfacing UNKNOWN, NO regex fallback. ` +
|
|
586
|
+
`rawPreview=${JSON.stringify(prompt.slice(0, 120))}`);
|
|
587
|
+
return null;
|
|
261
588
|
}
|
|
262
589
|
catch (err) {
|
|
263
590
|
console.error(`[pil.llm-classify] classify failed: ${err?.message}`, {
|
|
@@ -266,112 +593,8 @@ export function createLlmClassifier(factory, modelId) {
|
|
|
266
593
|
});
|
|
267
594
|
return null;
|
|
268
595
|
}
|
|
269
|
-
finally {
|
|
270
|
-
if (timer)
|
|
271
|
-
clearTimeout(timer);
|
|
272
|
-
}
|
|
273
596
|
};
|
|
274
597
|
}
|
|
275
|
-
export function classifySubSessionActionHeuristic(prompt) {
|
|
276
|
-
const trimmed = prompt.trim().toLowerCase();
|
|
277
|
-
if (!trimmed)
|
|
278
|
-
return null;
|
|
279
|
-
// Strip trailing punctuation for list-based matching so "hello!" and "cảm ơn!"
|
|
280
|
-
// still hit the static lists instead of falling through to the LLM classifier.
|
|
281
|
-
const stripped = trimmed.replace(/[!?.…,;:]+$/g, "").trim();
|
|
282
|
-
// 1. Simple math equations (exact matches like "2+2", "1 + 1")
|
|
283
|
-
if (/^\d+\s*[+\-*/]\s*\d+$/.test(trimmed)) {
|
|
284
|
-
return {
|
|
285
|
-
action: "DIRECT_ANSWER",
|
|
286
|
-
confidence: 0.99,
|
|
287
|
-
reason: "Obvious input classified via heuristic (simple math)",
|
|
288
|
-
};
|
|
289
|
-
}
|
|
290
|
-
// 2. Greetings (exact matches only, trailing punctuation stripped)
|
|
291
|
-
const greetings = [
|
|
292
|
-
"hi",
|
|
293
|
-
"hello",
|
|
294
|
-
"hey",
|
|
295
|
-
"chào",
|
|
296
|
-
"xin chào",
|
|
297
|
-
"hi there",
|
|
298
|
-
"hello there",
|
|
299
|
-
"chào bạn",
|
|
300
|
-
"halo",
|
|
301
|
-
"hola",
|
|
302
|
-
"bạn ơi",
|
|
303
|
-
];
|
|
304
|
-
if (greetings.includes(stripped)) {
|
|
305
|
-
return {
|
|
306
|
-
action: "DIRECT_ANSWER",
|
|
307
|
-
confidence: 0.99,
|
|
308
|
-
reason: "Obvious input classified via heuristic (greeting)",
|
|
309
|
-
};
|
|
310
|
-
}
|
|
311
|
-
// 3. Thanks (exact matches only, trailing punctuation stripped)
|
|
312
|
-
const thanks = [
|
|
313
|
-
"thanks",
|
|
314
|
-
"thank you",
|
|
315
|
-
"cảm ơn",
|
|
316
|
-
"cám ơn",
|
|
317
|
-
"thank",
|
|
318
|
-
"thx",
|
|
319
|
-
"ty",
|
|
320
|
-
"cảm ơn bạn",
|
|
321
|
-
"cám ơn bạn",
|
|
322
|
-
"cảm ơn nhé",
|
|
323
|
-
"cám ơn nhé",
|
|
324
|
-
];
|
|
325
|
-
if (thanks.includes(stripped)) {
|
|
326
|
-
return {
|
|
327
|
-
action: "DIRECT_ANSWER",
|
|
328
|
-
confidence: 0.99,
|
|
329
|
-
reason: "Obvious input classified via heuristic (thanks)",
|
|
330
|
-
};
|
|
331
|
-
}
|
|
332
|
-
// 4. Help (exact matches only, trailing punctuation stripped)
|
|
333
|
-
const help = ["help", "hướng dẫn", "cứu", "help me"];
|
|
334
|
-
if (help.includes(stripped)) {
|
|
335
|
-
return {
|
|
336
|
-
action: "DIRECT_ANSWER",
|
|
337
|
-
confidence: 0.99,
|
|
338
|
-
reason: "Obvious input classified via heuristic (help)",
|
|
339
|
-
};
|
|
340
|
-
}
|
|
341
|
-
// 5. Short conversational words / acknowledgements (exact matches only, trailing punctuation stripped)
|
|
342
|
-
const conversation = [
|
|
343
|
-
"ok",
|
|
344
|
-
"okay",
|
|
345
|
-
"yes",
|
|
346
|
-
"no",
|
|
347
|
-
"vâng",
|
|
348
|
-
"dạ",
|
|
349
|
-
"ừ",
|
|
350
|
-
"chắc thế",
|
|
351
|
-
"ừm",
|
|
352
|
-
"umm",
|
|
353
|
-
"cool",
|
|
354
|
-
"nice",
|
|
355
|
-
"perfect",
|
|
356
|
-
"done",
|
|
357
|
-
"xong",
|
|
358
|
-
"yep",
|
|
359
|
-
"yup",
|
|
360
|
-
"nah",
|
|
361
|
-
"fine",
|
|
362
|
-
"tốt",
|
|
363
|
-
"được",
|
|
364
|
-
"okie",
|
|
365
|
-
];
|
|
366
|
-
if (conversation.includes(stripped)) {
|
|
367
|
-
return {
|
|
368
|
-
action: "DIRECT_ANSWER",
|
|
369
|
-
confidence: 0.99,
|
|
370
|
-
reason: "Obvious input classified via heuristic (acknowledgement)",
|
|
371
|
-
};
|
|
372
|
-
}
|
|
373
|
-
return null;
|
|
374
|
-
}
|
|
375
598
|
const ROUTER_SYSTEM_PROMPT = "You are a routing controller for an AI coding agent. Your goal is to decide the execution strategy for the user's prompt based on the conversation history and metadata.\n\n" +
|
|
376
599
|
"Analyze the user's prompt and select one of the following ACTIONS:\n" +
|
|
377
600
|
'- "DIRECT_ANSWER": The prompt is informational, a quick question, a code review, an explanation, greeting, or thanks. No file creation/modification, test execution, or multi-turn tool runs are needed.\n' +
|
|
@@ -385,23 +608,27 @@ const ROUTER_SYSTEM_PROMPT = "You are a routing controller for an AI coding agen
|
|
|
385
608
|
'- "ROTATE_SESSION,0.95,Session size exceeds threshold and current request starts a new task."\n' +
|
|
386
609
|
'- "SPAWN_SUB_SESSION,0.98,Requires writing a test suite and fixing multiple files to get it green."\n' +
|
|
387
610
|
"No other text, only the comma-separated line.";
|
|
388
|
-
export async function classifySubSessionAction(
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
611
|
+
export async function classifySubSessionAction(modelId, prompt, contextInfo, signal) {
|
|
612
|
+
// No regex pre-filter: the model decides the route for EVERY prompt, including
|
|
613
|
+
// greetings/acks (which it routes to DIRECT_ANSWER). The old keyword/list
|
|
614
|
+
// heuristic was removed (2026-07-07, no-regex rule) — a hardcoded whitelist
|
|
615
|
+
// mis-handles the long tail of natural-language inputs the whole design moved
|
|
616
|
+
// off of. On a null/failed model result the caller keeps the conservative
|
|
617
|
+
// DIRECT_ANSWER default (a semantic default, not a regex guess).
|
|
394
618
|
const controller = new AbortController();
|
|
395
619
|
let timer;
|
|
396
620
|
try {
|
|
397
621
|
// Zero-hardcode: query models catalog for a cheap fast-tier model under the same provider.
|
|
398
622
|
const info = getModelInfo(modelId);
|
|
399
623
|
const provider = info?.provider;
|
|
400
|
-
const fastModel = provider
|
|
624
|
+
const fastModel = provider
|
|
625
|
+
? getRoutedModelByTier("fast", provider) || getRoutedModelByTier("balanced", provider)
|
|
626
|
+
: undefined;
|
|
401
627
|
const classificationModelId = fastModel?.id ?? modelId;
|
|
402
|
-
const runtime = resolveModelRuntime(
|
|
628
|
+
const runtime = resolveModelRuntime(classificationModelId);
|
|
403
629
|
const isReasoning = runtime.modelInfo?.reasoning === true;
|
|
404
|
-
|
|
630
|
+
// Same flat safety-net ceiling as the main classifier (see CLASSIFY_TIMEOUT_MS).
|
|
631
|
+
timer = setTimeout(() => controller.abort(), CLASSIFY_TIMEOUT_MS);
|
|
405
632
|
const combinedSignal = signal
|
|
406
633
|
? (AbortSignal.any?.([signal, controller.signal]) ?? controller.signal)
|
|
407
634
|
: controller.signal;
|
|
@@ -417,10 +644,15 @@ export async function classifySubSessionAction(factory, modelId, prompt, context
|
|
|
417
644
|
}
|
|
418
645
|
let promptWithContext = prompt.slice(0, 1000);
|
|
419
646
|
if (contextInfo) {
|
|
647
|
+
const recent = contextInfo.recentTurns?.trim();
|
|
648
|
+
const historyBlock = recent
|
|
649
|
+
? `[CONVERSATION HISTORY — for context; the prompt may continue or reference it]\n${recent.slice(0, 800)}\n\n`
|
|
650
|
+
: "";
|
|
420
651
|
promptWithContext =
|
|
421
652
|
`[SESSION METADATA]\n` +
|
|
422
653
|
`Current session size: ${contextInfo.currentChars} characters.\n` +
|
|
423
654
|
`Rotation threshold: ${contextInfo.threshold} characters.\n\n` +
|
|
655
|
+
`${historyBlock}` +
|
|
424
656
|
`[USER PROMPT]\n${promptWithContext}`;
|
|
425
657
|
}
|
|
426
658
|
const result = streamText({
|