muonroi-cli 1.8.3 → 1.8.5
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 +21 -21
- package/README.md +133 -122
- 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-tee.d.ts +48 -0
- package/dist/packages/agent-harness-core/src/event-tee.js +77 -0
- package/dist/packages/agent-harness-core/src/mcp-server.d.ts +11 -0
- package/dist/packages/agent-harness-core/src/mcp-server.js +87 -15
- package/dist/packages/agent-harness-core/src/protocol.d.ts +66 -2
- 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 +28 -0
- package/dist/src/agent-harness/mock-model.js +63 -1
- package/dist/src/agent-harness/test-spawn.js +31 -0
- 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 +30 -15
- 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 +2 -2
- package/dist/src/cli/keys.js +19 -81
- package/dist/src/council/clarifier.d.ts +28 -2
- package/dist/src/council/clarifier.js +81 -15
- 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 +51 -3
- 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 +1210 -134
- package/dist/src/council/index.d.ts +85 -1
- package/dist/src/council/index.js +634 -196
- package/dist/src/council/leader.d.ts +26 -0
- package/dist/src/council/leader.js +150 -9
- package/dist/src/council/llm.d.ts +32 -0
- package/dist/src/council/llm.js +231 -38
- package/dist/src/council/panel-select.d.ts +30 -0
- package/dist/src/council/panel-select.js +72 -0
- package/dist/src/council/planner.js +23 -0
- package/dist/src/council/preflight.d.ts +7 -0
- package/dist/src/council/preflight.js +14 -2
- package/dist/src/council/prompts.d.ts +30 -3
- package/dist/src/council/prompts.js +254 -84
- 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 +128 -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 +1 -0
- package/dist/src/ee/auth.js +15 -2
- package/dist/src/ee/bridge.d.ts +10 -0
- package/dist/src/ee/bridge.js +58 -0
- package/dist/src/ee/client.js +81 -18
- 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 +45 -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 +13 -1
- package/dist/src/flow/compaction/index.js +70 -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/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 +55 -0
- package/dist/src/gsd/flags.js +83 -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 +251 -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/hooks/index.js +1 -1
- package/dist/src/index.js +44 -11
- 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 +1 -1
- package/dist/src/mcp/ee-tools.js +1 -0
- package/dist/src/mcp/oauth-callback.js +2 -2
- 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 +77 -76
- 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 +8 -8
- 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 +10 -0
- package/dist/src/orchestrator/compaction.js +27 -7
- package/dist/src/orchestrator/council-manager.d.ts +12 -3
- package/dist/src/orchestrator/council-manager.js +65 -24
- 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/message-processor.js +242 -33
- package/dist/src/orchestrator/orchestrator.d.ts +39 -3
- package/dist/src/orchestrator/orchestrator.js +651 -102
- package/dist/src/orchestrator/preprocessor.js +1 -1
- 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 +159 -159
- 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-intercept.d.ts +45 -0
- package/dist/src/orchestrator/safety-intercept.js +55 -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 +24 -3
- package/dist/src/orchestrator/stall-watchdog.js +47 -13
- package/dist/src/orchestrator/stream-runner.js +62 -29
- 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 +26 -0
- package/dist/src/orchestrator/tool-engine.js +669 -56
- 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 +37 -0
- package/dist/src/orchestrator/turn-watchdog.js +55 -0
- package/dist/src/pil/agent-operating-contract.d.ts +1 -1
- package/dist/src/pil/agent-operating-contract.js +1 -1
- 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.js +16 -11
- 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/layer2_5-ponytail.js +8 -8
- 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 +33 -2
- package/dist/src/pil/llm-classify.js +123 -131
- package/dist/src/pil/native-capabilities-workbook.d.ts +1 -1
- package/dist/src/pil/native-capabilities-workbook.js +1 -0
- package/dist/src/pil/pipeline.js +34 -2
- 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/backlog-builder.d.ts +14 -1
- package/dist/src/product-loop/backlog-builder.js +30 -6
- 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.js +32 -3
- package/dist/src/product-loop/discovery-schema.js +5 -1
- package/dist/src/product-loop/done-gate.js +3 -3
- 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 +333 -52
- package/dist/src/product-loop/loop-driver.d.ts +7 -0
- package/dist/src/product-loop/loop-driver.js +327 -116
- package/dist/src/product-loop/phase-plan.d.ts +5 -0
- package/dist/src/product-loop/phase-plan.js +39 -2
- package/dist/src/product-loop/phase-runner.js +9 -1
- package/dist/src/product-loop/progress-snapshot.js +4 -4
- package/dist/src/product-loop/sprint-runner.d.ts +111 -0
- package/dist/src/product-loop/sprint-runner.js +559 -16
- package/dist/src/product-loop/types.d.ts +36 -5
- package/dist/src/providers/adapter.d.ts +1 -1
- package/dist/src/providers/adapter.js +3 -4
- 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/openai-oauth.js +1 -1
- package/dist/src/providers/auth/registry.js +0 -34
- package/dist/src/providers/auth/token-store.js +4 -1
- package/dist/src/providers/auth/types.d.ts +1 -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/keychain.d.ts +1 -1
- package/dist/src/providers/keychain.js +7 -9
- package/dist/src/providers/mcp-vision-bridge.js +82 -172
- 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 +27 -2
- package/dist/src/providers/runtime.js +78 -15
- 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 +280 -1
- 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/wire-debug.js +95 -0
- package/dist/src/reporter/index.js +1 -1
- 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/bb-ecosystem-apply.js +47 -47
- package/dist/src/scaffold/bb-quality-gate.js +5 -5
- package/dist/src/scaffold/continuation-prompt.d.ts +11 -0
- package/dist/src/scaffold/continuation-prompt.js +86 -60
- package/dist/src/scaffold/init-new.js +453 -453
- 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 +22 -22
- 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/interaction-log.js +5 -5
- package/dist/src/storage/migrations.js +196 -126
- package/dist/src/storage/session-experience-store.js +4 -4
- package/dist/src/storage/sessions.d.ts +28 -10
- package/dist/src/storage/sessions.js +112 -55
- package/dist/src/storage/transcript-view.js +1 -1
- package/dist/src/storage/transcript.d.ts +51 -0
- package/dist/src/storage/transcript.js +383 -112
- package/dist/src/storage/usage.js +14 -14
- package/dist/src/storage/workspaces.js +12 -12
- package/dist/src/tools/file.d.ts +15 -0
- package/dist/src/tools/file.js +32 -0
- package/dist/src/tools/native-tools.js +5 -0
- package/dist/src/tools/registry.d.ts +3 -0
- package/dist/src/tools/registry.js +460 -22
- package/dist/src/tools/research.d.ts +29 -0
- package/dist/src/tools/research.js +233 -0
- package/dist/src/types/index.d.ts +118 -3
- package/dist/src/ui/app.js +0 -0
- package/dist/src/ui/cards/product-status-card.js +1 -1
- 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/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 +49 -15
- 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 +12 -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/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/containers/modals-layer.d.ts +2 -1
- 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/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-inspect.js +4 -4
- 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/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 +12 -18
- 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/format.d.ts +14 -0
- package/dist/src/ui/utils/format.js +23 -3
- package/dist/src/usage/downgrade.js +2 -2
- package/dist/src/usage/product-ledger.js +2 -2
- package/dist/src/utils/clipboard-image.js +23 -23
- package/dist/src/utils/install-manager.js +14 -11
- package/dist/src/utils/logger.js +2 -2
- package/dist/src/utils/permission-mode.js +5 -3
- package/dist/src/utils/redactor.js +1 -1
- package/dist/src/utils/settings.d.ts +153 -5
- package/dist/src/utils/settings.js +233 -29
- package/dist/src/utils/side-question.js +2 -2
- package/dist/src/utils/skills.js +3 -3
- 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 +1 -1
- package/dist/src/verify/recipes.d.ts +13 -0
- package/dist/src/verify/recipes.js +15 -0
- package/package.json +135 -132
- 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
|
@@ -46,21 +46,21 @@ export function collectCostForensics(sessionId) {
|
|
|
46
46
|
const chain = getSessionChain(sessionId);
|
|
47
47
|
const placeholders = chain.map(() => "?").join(",");
|
|
48
48
|
const rows = db
|
|
49
|
-
.prepare(`
|
|
50
|
-
SELECT id, source, model, message_seq, input_tokens, output_tokens,
|
|
51
|
-
cache_read_tokens, cache_creation_tokens, cost_micros, created_at,
|
|
52
|
-
provider_options_shape
|
|
53
|
-
FROM usage_events
|
|
54
|
-
WHERE session_id IN (${placeholders})
|
|
55
|
-
ORDER BY id ASC
|
|
49
|
+
.prepare(`
|
|
50
|
+
SELECT id, source, model, message_seq, input_tokens, output_tokens,
|
|
51
|
+
cache_read_tokens, cache_creation_tokens, cost_micros, created_at,
|
|
52
|
+
provider_options_shape
|
|
53
|
+
FROM usage_events
|
|
54
|
+
WHERE session_id IN (${placeholders})
|
|
55
|
+
ORDER BY id ASC
|
|
56
56
|
`)
|
|
57
57
|
.all(...chain);
|
|
58
58
|
const counts = db
|
|
59
|
-
.prepare(`
|
|
60
|
-
SELECT event_type, COUNT(*) AS c
|
|
61
|
-
FROM interaction_logs
|
|
62
|
-
WHERE session_id IN (${placeholders})
|
|
63
|
-
GROUP BY event_type
|
|
59
|
+
.prepare(`
|
|
60
|
+
SELECT event_type, COUNT(*) AS c
|
|
61
|
+
FROM interaction_logs
|
|
62
|
+
WHERE session_id IN (${placeholders})
|
|
63
|
+
GROUP BY event_type
|
|
64
64
|
`)
|
|
65
65
|
.all(...chain);
|
|
66
66
|
const countMap = new Map(counts.map((r) => [r.event_type, r.c]));
|
|
@@ -83,6 +83,7 @@ export function collectCostForensics(sessionId) {
|
|
|
83
83
|
let totalCacheCreation = 0;
|
|
84
84
|
let totalCostMicros = 0;
|
|
85
85
|
let peakSingleCallInput = 0;
|
|
86
|
+
let peakSingleCallFresh = 0;
|
|
86
87
|
for (const e of events) {
|
|
87
88
|
totalInput += e.inputTokens;
|
|
88
89
|
totalOutput += e.outputTokens;
|
|
@@ -91,6 +92,9 @@ export function collectCostForensics(sessionId) {
|
|
|
91
92
|
totalCostMicros += e.costMicros;
|
|
92
93
|
if (e.inputTokens > peakSingleCallInput)
|
|
93
94
|
peakSingleCallInput = e.inputTokens;
|
|
95
|
+
const fresh = Math.max(0, e.inputTokens - e.cacheReadTokens);
|
|
96
|
+
if (fresh > peakSingleCallFresh)
|
|
97
|
+
peakSingleCallFresh = fresh;
|
|
94
98
|
}
|
|
95
99
|
const cacheable = totalInput;
|
|
96
100
|
const cacheHitRatio = cacheable > 0 ? totalCacheRead / cacheable : 0;
|
|
@@ -106,6 +110,7 @@ export function collectCostForensics(sessionId) {
|
|
|
106
110
|
totalCostUsd: totalCostMicros / 1_000_000,
|
|
107
111
|
cacheHitRatio,
|
|
108
112
|
peakSingleCallInput,
|
|
113
|
+
peakSingleCallFresh,
|
|
109
114
|
events,
|
|
110
115
|
experience: selectSessionExperience(sessionId),
|
|
111
116
|
};
|
|
@@ -159,7 +164,8 @@ export function printCostForensics(summary, opts = {}) {
|
|
|
159
164
|
w(`Total output tokens: ${formatNum(summary.totalOutput)}`);
|
|
160
165
|
w(`Cache read tokens: ${formatNum(summary.totalCacheRead)} (${formatPct(summary.cacheHitRatio)} of input)`);
|
|
161
166
|
w(`Cache create tokens: ${formatNum(summary.totalCacheCreation)}`);
|
|
162
|
-
w(`Peak single call: ${formatNum(summary.peakSingleCallInput)} input`
|
|
167
|
+
w(`Peak single call: ${formatNum(summary.peakSingleCallInput)} input ` +
|
|
168
|
+
`(${formatNum(summary.peakSingleCallFresh)} fresh)`);
|
|
163
169
|
w(`Estimated cost: $${summary.totalCostUsd.toFixed(4)}`);
|
|
164
170
|
// Cache-cadence diagnostic — surface fast-loop prompt-cache latency loss.
|
|
165
171
|
const cadence = computeCacheCadence(summary.events);
|
|
@@ -199,8 +205,17 @@ export function printCostForensics(summary, opts = {}) {
|
|
|
199
205
|
}
|
|
200
206
|
// Acceptance hints — surface anomalies relative to Phase A/B/C targets.
|
|
201
207
|
const anomalies = [];
|
|
202
|
-
|
|
203
|
-
|
|
208
|
+
// Phase B breach = a single call that sent >80K FRESH (uncached) tokens.
|
|
209
|
+
// Use FRESH, not raw input: `task`/`council` usage rows store `totalUsage`
|
|
210
|
+
// aggregated across a streamText call's internal steps, so a high raw input
|
|
211
|
+
// there is normally cached-prefix reuse across steps (high cache hit), NOT a
|
|
212
|
+
// cold cap breach. Evidence: session 6f17a13c1591 peaked at 378,613 raw input
|
|
213
|
+
// but only 54,389 fresh (324,224 cache-read) — aggregate reuse, no breach.
|
|
214
|
+
if (summary.peakSingleCallFresh > 80_000) {
|
|
215
|
+
anomalies.push(`peak single-call FRESH input ${formatNum(summary.peakSingleCallFresh)} > 80,000 — sub-agent cap may not have kicked in (Phase B target breach)`);
|
|
216
|
+
}
|
|
217
|
+
else if (summary.peakSingleCallInput > 80_000) {
|
|
218
|
+
anomalies.push(`peak single-call input ${formatNum(summary.peakSingleCallInput)} > 80,000 but only ${formatNum(summary.peakSingleCallFresh)} FRESH — aggregate multi-step reuse (task/council totalUsage), NOT a cap breach`);
|
|
204
219
|
}
|
|
205
220
|
if (summary.events.some((e) => e.messageSeq === null && e.source === "message")) {
|
|
206
221
|
anomalies.push(`some 'message' events have NULL message_seq — Phase A5 message write-ahead bypassed (check persistMessageWriteAhead wiring)`);
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* Format (JSON):
|
|
8
8
|
* { v:1, kdf:"scrypt", salt:hex, iv:hex, tag:hex, ct:base64 }
|
|
9
9
|
*
|
|
10
|
-
* Plaintext payload (JSON): { providers: { deepseek: "sk-...",
|
|
10
|
+
* Plaintext payload (JSON): { providers: { deepseek: "sk-...", xai: "xai-..." } }
|
|
11
11
|
*
|
|
12
12
|
* Crypto:
|
|
13
13
|
* - scrypt(passphrase, salt, 32) → 256-bit key
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* Format (JSON):
|
|
8
8
|
* { v:1, kdf:"scrypt", salt:hex, iv:hex, tag:hex, ct:base64 }
|
|
9
9
|
*
|
|
10
|
-
* Plaintext payload (JSON): { providers: { deepseek: "sk-...",
|
|
10
|
+
* Plaintext payload (JSON): { providers: { deepseek: "sk-...", xai: "xai-..." } }
|
|
11
11
|
*
|
|
12
12
|
* Crypto:
|
|
13
13
|
* - scrypt(passphrase, salt, 32) → 256-bit key
|
package/dist/src/cli/keys.d.ts
CHANGED
|
@@ -15,8 +15,8 @@
|
|
|
15
15
|
* keys delete <provider> — remove a stored key
|
|
16
16
|
* keys import-bw [providers] — pull from Bitwarden vault, store in keychain
|
|
17
17
|
* keys cleanup-settings — strip plaintext keys from user-settings.json
|
|
18
|
-
* keys login <provider> — OAuth login (openai,
|
|
19
|
-
* keys logout <provider> — OAuth logout (openai,
|
|
18
|
+
* keys login <provider> — OAuth login (openai, xai)
|
|
19
|
+
* keys logout <provider> — OAuth logout (openai, xai)
|
|
20
20
|
*/
|
|
21
21
|
import { type ChatSecretId } from "../chat/chat-keychain.js";
|
|
22
22
|
export interface KeysSetOptions {
|
package/dist/src/cli/keys.js
CHANGED
|
@@ -15,8 +15,8 @@
|
|
|
15
15
|
* keys delete <provider> — remove a stored key
|
|
16
16
|
* keys import-bw [providers] — pull from Bitwarden vault, store in keychain
|
|
17
17
|
* keys cleanup-settings — strip plaintext keys from user-settings.json
|
|
18
|
-
* keys login <provider> — OAuth login (openai,
|
|
19
|
-
* keys logout <provider> — OAuth logout (openai,
|
|
18
|
+
* keys login <provider> — OAuth login (openai, xai)
|
|
19
|
+
* keys logout <provider> — OAuth logout (openai, xai)
|
|
20
20
|
*/
|
|
21
21
|
import { spawnSync } from "node:child_process";
|
|
22
22
|
import { promises as fs } from "node:fs";
|
|
@@ -43,7 +43,7 @@ const CHAT_SECRET_IDS = ["discord-token", "discord-guild-id", "slack-token", "sl
|
|
|
43
43
|
function isChatSecretId(value) {
|
|
44
44
|
return CHAT_SECRET_IDS.includes(value);
|
|
45
45
|
}
|
|
46
|
-
const
|
|
46
|
+
const getSettingsPath = () => path.join(os.homedir(), ".muonroi-cli", "user-settings.json");
|
|
47
47
|
function maskKey(key) {
|
|
48
48
|
if (key.length <= 10)
|
|
49
49
|
return "***";
|
|
@@ -51,8 +51,6 @@ function maskKey(key) {
|
|
|
51
51
|
}
|
|
52
52
|
function normalizeProvider(p) {
|
|
53
53
|
const lower = p.toLowerCase();
|
|
54
|
-
if (lower === "agy")
|
|
55
|
-
return "google"; // agy is alias for google (OAuth + key)
|
|
56
54
|
if (KEYCHAIN_PROVIDER_IDS.includes(lower)) {
|
|
57
55
|
return lower;
|
|
58
56
|
}
|
|
@@ -115,7 +113,7 @@ async function promptHidden(question) {
|
|
|
115
113
|
export async function runKeysSet(provider, options = {}) {
|
|
116
114
|
const norm = normalizeProvider(provider);
|
|
117
115
|
if (!norm) {
|
|
118
|
-
console.error(`Unknown provider '${provider}'. Valid: ${KEYCHAIN_PROVIDER_IDS.join(", ")}
|
|
116
|
+
console.error(`Unknown provider '${provider}'. Valid: ${KEYCHAIN_PROVIDER_IDS.join(", ")}`);
|
|
119
117
|
process.exit(1);
|
|
120
118
|
}
|
|
121
119
|
const key = (await promptHidden(`Paste ${provider} API key (hidden): `)).trim();
|
|
@@ -199,30 +197,11 @@ export async function runKeysList() {
|
|
|
199
197
|
const oauthIds = await getOAuthProviderIds();
|
|
200
198
|
for (const p of oauthIds) {
|
|
201
199
|
try {
|
|
202
|
-
|
|
203
|
-
if (
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
const agyTokens = await loadAgyTokensWithRefresh();
|
|
208
|
-
if (agyTokens && Date.now() < agyTokens.expiresAt) {
|
|
209
|
-
tokens = agyTokens;
|
|
210
|
-
}
|
|
211
|
-
}
|
|
212
|
-
if (tokens) {
|
|
213
|
-
if (Date.now() < tokens.expiresAt) {
|
|
214
|
-
const cfg = await getOAuthProviderConfig(p);
|
|
215
|
-
const display = cfg?.displayName ?? p;
|
|
216
|
-
oauthRows.push({ provider: p, display, email: tokens.email, expiresAt: tokens.expiresAt });
|
|
217
|
-
}
|
|
218
|
-
else if (p === "google") {
|
|
219
|
-
// auto-clean stale Agy token from our store (imported from old ~/.gemini)
|
|
220
|
-
try {
|
|
221
|
-
const { deleteTokens } = await import("../providers/auth/token-store.js");
|
|
222
|
-
await deleteTokens("google");
|
|
223
|
-
}
|
|
224
|
-
catch { }
|
|
225
|
-
}
|
|
200
|
+
const tokens = await loadTokens(p);
|
|
201
|
+
if (tokens && Date.now() < tokens.expiresAt) {
|
|
202
|
+
const cfg = await getOAuthProviderConfig(p);
|
|
203
|
+
const display = cfg?.displayName ?? p;
|
|
204
|
+
oauthRows.push({ provider: p, display, email: tokens.email, expiresAt: tokens.expiresAt });
|
|
226
205
|
}
|
|
227
206
|
}
|
|
228
207
|
catch {
|
|
@@ -234,7 +213,6 @@ export async function runKeysList() {
|
|
|
234
213
|
console.log("No keys stored in OS keychain.");
|
|
235
214
|
console.log("Run 'muonroi-cli keys set <provider>' or 'muonroi-cli keys import-bw' to add some.");
|
|
236
215
|
console.log("Run 'muonroi-cli keys login openai' to log in with your OpenAI subscription.");
|
|
237
|
-
console.log("Run 'muonroi-cli keys login google' or 'keys login agy' to log in with Agy (Google OAuth).");
|
|
238
216
|
console.log("Run 'muonroi-cli keys login xai' to log in with your SuperGrok / X Premium+ subscription.");
|
|
239
217
|
return;
|
|
240
218
|
}
|
|
@@ -288,35 +266,11 @@ export async function runKeysList() {
|
|
|
288
266
|
export async function runKeysDelete(provider) {
|
|
289
267
|
const norm = normalizeProvider(provider);
|
|
290
268
|
if (!norm) {
|
|
291
|
-
console.error(`Unknown provider '${provider}'. Valid: ${KEYCHAIN_PROVIDER_IDS.join(", ")}
|
|
269
|
+
console.error(`Unknown provider '${provider}'. Valid: ${KEYCHAIN_PROVIDER_IDS.join(", ")}`);
|
|
292
270
|
process.exit(1);
|
|
293
271
|
}
|
|
294
272
|
const ok = await deleteKeyForProvider(norm);
|
|
295
273
|
console.log(ok ? `Deleted ${provider} key from keychain.` : `No ${provider} key was stored.`);
|
|
296
|
-
// If deleting google/agy, also clean the OAuth token (Agy uses the google slot for its OAuth)
|
|
297
|
-
if (norm === "google") {
|
|
298
|
-
try {
|
|
299
|
-
const { loadTokens, deleteTokens } = await import("../providers/auth/token-store.js");
|
|
300
|
-
const { getOAuthProviderConfig } = await import("../providers/auth/registry.js");
|
|
301
|
-
const tokens = await loadTokens("google");
|
|
302
|
-
if (tokens) {
|
|
303
|
-
const cfg = await getOAuthProviderConfig("google");
|
|
304
|
-
if (cfg?.provider) {
|
|
305
|
-
try {
|
|
306
|
-
await cfg.provider.revoke(tokens);
|
|
307
|
-
}
|
|
308
|
-
catch {
|
|
309
|
-
// best effort
|
|
310
|
-
}
|
|
311
|
-
}
|
|
312
|
-
await deleteTokens("google");
|
|
313
|
-
console.log(`Also cleared Agy/Google OAuth token.`);
|
|
314
|
-
}
|
|
315
|
-
}
|
|
316
|
-
catch {
|
|
317
|
-
// ignore secondary errors
|
|
318
|
-
}
|
|
319
|
-
}
|
|
320
274
|
}
|
|
321
275
|
export async function runKeysExport(filePath) {
|
|
322
276
|
const providers = {};
|
|
@@ -679,9 +633,9 @@ export async function runChatImportBw(opts = {}) {
|
|
|
679
633
|
*/
|
|
680
634
|
export async function runKeysLogin(provider) {
|
|
681
635
|
const oauthIds = await getOAuthProviderIds();
|
|
682
|
-
const norm = provider
|
|
636
|
+
const norm = provider;
|
|
683
637
|
if (!oauthIds.includes(norm)) {
|
|
684
|
-
const supported = oauthIds.
|
|
638
|
+
const supported = oauthIds.join(", ");
|
|
685
639
|
console.error(`OAuth login not supported for '${provider}'. Supported: ${supported}`);
|
|
686
640
|
process.exit(1);
|
|
687
641
|
}
|
|
@@ -693,22 +647,6 @@ export async function runKeysLogin(provider) {
|
|
|
693
647
|
process.exit(1);
|
|
694
648
|
}
|
|
695
649
|
const name = cfg.displayName;
|
|
696
|
-
// Special handling for google: prefer importing/reusing token from agy local config
|
|
697
|
-
// (~/.gemini/oauth_creds.json) so existing agy login "just works" without browser.
|
|
698
|
-
if (provider === "google") {
|
|
699
|
-
const { loadAgyTokensWithRefresh } = await import("../providers/auth/gemini-oauth.js");
|
|
700
|
-
const existing = await loadAgyTokensWithRefresh();
|
|
701
|
-
if (existing?.accessToken) {
|
|
702
|
-
console.log(`Found existing agy token for ${existing.email || "(no email)"}.`);
|
|
703
|
-
console.log("Reusing it (refreshed if near expiry). No browser needed.");
|
|
704
|
-
const expiry = new Date(existing.expiresAt).toLocaleString();
|
|
705
|
-
const isExpired = Date.now() > existing.expiresAt;
|
|
706
|
-
console.log(`Token expires: ${expiry}${isExpired ? " (was expired, refresh attempted)" : ""}`);
|
|
707
|
-
console.log("Run 'muonroi-cli keys list' to verify.");
|
|
708
|
-
return;
|
|
709
|
-
}
|
|
710
|
-
// No existing agy token — fall through to normal browser flow (using agy client)
|
|
711
|
-
}
|
|
712
650
|
console.log(`Logging in to ${name} via OAuth...`);
|
|
713
651
|
console.log("A browser window will open. Complete the sign-in to continue.");
|
|
714
652
|
const tokens = await cfg.provider.login({
|
|
@@ -742,9 +680,9 @@ export async function runKeysLogin(provider) {
|
|
|
742
680
|
*/
|
|
743
681
|
export async function runKeysLogout(provider) {
|
|
744
682
|
const oauthIds = await getOAuthProviderIds();
|
|
745
|
-
const norm = provider
|
|
683
|
+
const norm = provider;
|
|
746
684
|
if (!oauthIds.includes(norm)) {
|
|
747
|
-
const supported = oauthIds.
|
|
685
|
+
const supported = oauthIds.join(", ");
|
|
748
686
|
console.error(`OAuth logout not supported for '${provider}'. Supported: ${supported}`);
|
|
749
687
|
process.exit(1);
|
|
750
688
|
}
|
|
@@ -766,7 +704,7 @@ export async function runKeysLogout(provider) {
|
|
|
766
704
|
export async function runKeysCleanupSettings() {
|
|
767
705
|
let raw;
|
|
768
706
|
try {
|
|
769
|
-
raw = await fs.readFile(
|
|
707
|
+
raw = await fs.readFile(getSettingsPath(), "utf8");
|
|
770
708
|
}
|
|
771
709
|
catch (e) {
|
|
772
710
|
if (e.code === "ENOENT") {
|
|
@@ -780,11 +718,11 @@ export async function runKeysCleanupSettings() {
|
|
|
780
718
|
json = JSON.parse(raw);
|
|
781
719
|
}
|
|
782
720
|
catch {
|
|
783
|
-
console.error(`Settings file is not valid JSON: ${
|
|
721
|
+
console.error(`Settings file is not valid JSON: ${getSettingsPath()}`);
|
|
784
722
|
process.exit(1);
|
|
785
723
|
}
|
|
786
724
|
const ts = new Date().toISOString().replace(/[:.]/g, "-");
|
|
787
|
-
const backup = `${
|
|
725
|
+
const backup = `${getSettingsPath()}.bak.${ts}`;
|
|
788
726
|
await fs.writeFile(backup, raw, "utf8");
|
|
789
727
|
let removed = 0;
|
|
790
728
|
if ("apiKey" in json) {
|
|
@@ -806,9 +744,9 @@ export async function runKeysCleanupSettings() {
|
|
|
806
744
|
delete json.providers;
|
|
807
745
|
}
|
|
808
746
|
}
|
|
809
|
-
await fs.writeFile(
|
|
747
|
+
await fs.writeFile(getSettingsPath(), `${JSON.stringify(json, null, 2)}\n`, "utf8");
|
|
810
748
|
console.log(`Backed up to: ${backup}`);
|
|
811
|
-
console.log(`Removed ${removed} plaintext key field(s) from: ${
|
|
749
|
+
console.log(`Removed ${removed} plaintext key field(s) from: ${getSettingsPath()}`);
|
|
812
750
|
if (removed === 0) {
|
|
813
751
|
console.log("(File was already clean.)");
|
|
814
752
|
}
|
|
@@ -69,6 +69,26 @@ export interface ClarifyOptionsResult {
|
|
|
69
69
|
* so the UI knows to suppress the "(Recommended)" tag.
|
|
70
70
|
*/
|
|
71
71
|
export declare function buildClarifyOptions(suggestions: string[] | undefined, recommended?: string): ClarifyOptionsResult;
|
|
72
|
+
/**
|
|
73
|
+
* Model-designed option object: a choice the user picks between, carrying its
|
|
74
|
+
* own explanation. `recommended:true` marks the pre-selected default. This is
|
|
75
|
+
* the richer shape the clarifier prompt now asks for so every choice shows a
|
|
76
|
+
* per-option "why" (not just the question-level `why`).
|
|
77
|
+
*/
|
|
78
|
+
export interface ClarifyOptionSpec {
|
|
79
|
+
label: string;
|
|
80
|
+
description?: string;
|
|
81
|
+
recommended?: boolean;
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Build council options from the model's richer `{label, description,
|
|
85
|
+
* recommended}` objects, preserving each choice's explanation as `description`
|
|
86
|
+
* and pointing `defaultIndex` at the option the model flagged `recommended`
|
|
87
|
+
* (falling back to the first when none is flagged). The same "Type something" /
|
|
88
|
+
* "Chat about this" escape-hatches are appended as in `buildClarifyOptions`.
|
|
89
|
+
* Pure + exported for unit testing.
|
|
90
|
+
*/
|
|
91
|
+
export declare function buildClarifyOptionsRich(specs: ClarifyOptionSpec[] | undefined): ClarifyOptionsResult;
|
|
72
92
|
export declare function runClarification(topic: string, leaderModelId: string, conversationContext: string, respondToQuestion: QuestionResponder, llm: CouncilLLM, signal?: AbortSignal, seedQuestions?: GrayAreaQuestion[], maxRounds?: number,
|
|
73
93
|
/**
|
|
74
94
|
* Optional pre-filled answers keyed by seed-question id. When the loop's
|
|
@@ -83,5 +103,11 @@ prefillAnswers?: Map<string, string>,
|
|
|
83
103
|
* a cheaper tier model on the same provider as the leader. Falls back to
|
|
84
104
|
* the leader model if no cheaper model is cataloged. Default false.
|
|
85
105
|
*/
|
|
86
|
-
costAware?: boolean
|
|
87
|
-
|
|
106
|
+
costAware?: boolean,
|
|
107
|
+
/**
|
|
108
|
+
* Healthy panel models to fall back to when the leader's cost-tier spec-synth
|
|
109
|
+
* model is on a failing proxy — keeps the spec's ≥3 observable criteria (and
|
|
110
|
+
* thus a meaningful leader eval) instead of degrading to one generic criterion.
|
|
111
|
+
*/
|
|
112
|
+
fallbackModels?: string[]): AsyncGenerator<StreamChunk, ClarifiedSpec, unknown>;
|
|
113
|
+
export declare function buildSpecFromTopic(topic: string, conversationContext: string): ClarifiedSpec;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { pickCouncilTaskModel } from "./leader.js";
|
|
2
|
-
import { tracedGenerate } from "./llm.js";
|
|
2
|
+
import { tracedGenerate, tracedGenerateWithFallback } from "./llm.js";
|
|
3
3
|
import { phaseDone, phaseError, phaseStart } from "./phase-events.js";
|
|
4
4
|
import { buildClarificationPrompt, buildReadinessJudgePrompt, buildSpecSynthesisPrompt } from "./prompts.js";
|
|
5
5
|
/** P5: Hard cap on clarification rounds regardless of judge verdict. */
|
|
@@ -138,6 +138,45 @@ export function buildClarifyOptions(suggestions, recommended) {
|
|
|
138
138
|
}
|
|
139
139
|
return { options, defaultIndex };
|
|
140
140
|
}
|
|
141
|
+
/**
|
|
142
|
+
* Build council options from the model's richer `{label, description,
|
|
143
|
+
* recommended}` objects, preserving each choice's explanation as `description`
|
|
144
|
+
* and pointing `defaultIndex` at the option the model flagged `recommended`
|
|
145
|
+
* (falling back to the first when none is flagged). The same "Type something" /
|
|
146
|
+
* "Chat about this" escape-hatches are appended as in `buildClarifyOptions`.
|
|
147
|
+
* Pure + exported for unit testing.
|
|
148
|
+
*/
|
|
149
|
+
export function buildClarifyOptionsRich(specs) {
|
|
150
|
+
const cleaned = (specs ?? []).filter((s) => !!s && typeof s.label === "string" && s.label.trim().length > 0);
|
|
151
|
+
const choices = cleaned.map((s) => ({
|
|
152
|
+
label: s.label.trim(),
|
|
153
|
+
value: s.label.trim(),
|
|
154
|
+
kind: "choice",
|
|
155
|
+
...(typeof s.description === "string" && s.description.trim().length > 0
|
|
156
|
+
? { description: s.description.trim() }
|
|
157
|
+
: {}),
|
|
158
|
+
}));
|
|
159
|
+
const options = [
|
|
160
|
+
...choices,
|
|
161
|
+
{
|
|
162
|
+
label: "Type something",
|
|
163
|
+
description: "Nhập câu trả lời tự do",
|
|
164
|
+
value: "",
|
|
165
|
+
kind: "freetext",
|
|
166
|
+
},
|
|
167
|
+
{
|
|
168
|
+
label: "Chat about this",
|
|
169
|
+
description: "Thảo luận thêm trước khi trả lời",
|
|
170
|
+
value: "",
|
|
171
|
+
kind: "chat",
|
|
172
|
+
},
|
|
173
|
+
];
|
|
174
|
+
let defaultIndex;
|
|
175
|
+
const recIdx = cleaned.findIndex((s) => s.recommended === true);
|
|
176
|
+
if (recIdx !== -1)
|
|
177
|
+
defaultIndex = recIdx;
|
|
178
|
+
return { options, defaultIndex };
|
|
179
|
+
}
|
|
141
180
|
export async function* runClarification(topic, leaderModelId, conversationContext, respondToQuestion, llm, signal, seedQuestions, maxRounds,
|
|
142
181
|
/**
|
|
143
182
|
* Optional pre-filled answers keyed by seed-question id. When the loop's
|
|
@@ -152,7 +191,13 @@ prefillAnswers,
|
|
|
152
191
|
* a cheaper tier model on the same provider as the leader. Falls back to
|
|
153
192
|
* the leader model if no cheaper model is cataloged. Default false.
|
|
154
193
|
*/
|
|
155
|
-
costAware = false
|
|
194
|
+
costAware = false,
|
|
195
|
+
/**
|
|
196
|
+
* Healthy panel models to fall back to when the leader's cost-tier spec-synth
|
|
197
|
+
* model is on a failing proxy — keeps the spec's ≥3 observable criteria (and
|
|
198
|
+
* thus a meaningful leader eval) instead of degrading to one generic criterion.
|
|
199
|
+
*/
|
|
200
|
+
fallbackModels = []) {
|
|
156
201
|
// P5: use MAX_CLARIFY_ROUNDS (12) as the hard cap; respect explicit override
|
|
157
202
|
// from callers that pass maxRounds (e.g. tests that want old 3-round behavior).
|
|
158
203
|
const max = typeof maxRounds === "number" && maxRounds > 0 ? maxRounds : MAX_CLARIFY_ROUNDS;
|
|
@@ -284,7 +329,16 @@ costAware = false) {
|
|
|
284
329
|
continue;
|
|
285
330
|
}
|
|
286
331
|
const questionId = crypto.randomUUID();
|
|
287
|
-
|
|
332
|
+
// Prefer the model's richer {label, description, recommended} options so
|
|
333
|
+
// every choice shows its own explanation + the recommended default is
|
|
334
|
+
// pre-selected. Fall back to the legacy string `suggestions` shape (PIL
|
|
335
|
+
// gray-area seeds and any model still emitting the old format).
|
|
336
|
+
const { options, defaultIndex } = q.options && q.options.length > 0
|
|
337
|
+
? buildClarifyOptionsRich(q.options)
|
|
338
|
+
: buildClarifyOptions(q.suggestions, q.recommended);
|
|
339
|
+
// Keep the deprecated `suggestions` mirror populated for consumers that
|
|
340
|
+
// still read it (audit/replay), deriving labels from rich options.
|
|
341
|
+
const suggestionLabels = q.suggestions ?? (q.options ? q.options.map((o) => o.label).filter((l) => typeof l === "string") : undefined);
|
|
288
342
|
yield {
|
|
289
343
|
type: "council_question",
|
|
290
344
|
content: `**${q.question}**\n${q.why ? `> ${q.why}` : ""}`,
|
|
@@ -293,7 +347,7 @@ costAware = false) {
|
|
|
293
347
|
phase: "clarify",
|
|
294
348
|
question: q.question,
|
|
295
349
|
context: q.why,
|
|
296
|
-
suggestions:
|
|
350
|
+
suggestions: suggestionLabels,
|
|
297
351
|
options,
|
|
298
352
|
isRequired: q.isRequired,
|
|
299
353
|
defaultIndex,
|
|
@@ -329,7 +383,7 @@ costAware = false) {
|
|
|
329
383
|
startedAt: phaseStartedAt,
|
|
330
384
|
detail: allQA.length > 0 ? `${allQA.length} Q&A` : "no clarification needed",
|
|
331
385
|
});
|
|
332
|
-
const spec = yield* synthesizeSpec(topic, conversationContext, allQA, leaderModelId, llm, costAware);
|
|
386
|
+
const spec = yield* synthesizeSpec(topic, conversationContext, allQA, leaderModelId, llm, costAware, fallbackModels);
|
|
333
387
|
// P5: attach ready-gate metadata to the returned spec
|
|
334
388
|
spec.confidenceScore = gateConfidence;
|
|
335
389
|
spec.remainingGaps = gateGaps;
|
|
@@ -355,13 +409,18 @@ costAware = false) {
|
|
|
355
409
|
};
|
|
356
410
|
return spec;
|
|
357
411
|
}
|
|
358
|
-
export function buildSpecFromTopic(topic,
|
|
412
|
+
export function buildSpecFromTopic(topic, conversationContext) {
|
|
359
413
|
return {
|
|
360
414
|
problemStatement: topic,
|
|
361
415
|
constraints: [],
|
|
362
416
|
successCriteria: [`Address the topic: ${topic.slice(0, 100)}`],
|
|
363
417
|
scope: "Determined by conversation context",
|
|
364
418
|
rawQA: [],
|
|
419
|
+
// Carry the session/task context onto the spec so every debate stage (not
|
|
420
|
+
// just the opening) can stay anchored to the parent task. Previously this
|
|
421
|
+
// argument was dropped (prefixed `_`), which is why auto-council debates saw
|
|
422
|
+
// only the isolated sub-task and drifted off-topic.
|
|
423
|
+
parentContext: conversationContext?.trim() || undefined,
|
|
365
424
|
};
|
|
366
425
|
}
|
|
367
426
|
/**
|
|
@@ -374,7 +433,7 @@ export function buildSpecFromTopic(topic, _conversationContext) {
|
|
|
374
433
|
* We LLM-extract criteria/constraints/scope from the topic when QA is empty.
|
|
375
434
|
* Falls back to the original buildSpecFromTopic if the LLM call fails.
|
|
376
435
|
*/
|
|
377
|
-
async function* inferSpecFromTopicOnly(topic, conversationContext, leaderModelId, llm, costAware) {
|
|
436
|
+
async function* inferSpecFromTopicOnly(topic, conversationContext, leaderModelId, llm, costAware, fallbackModels = []) {
|
|
378
437
|
const system = `You are extracting an implicit specification from a short topic statement. ` +
|
|
379
438
|
`The user did not answer clarification questions, but the topic itself often ` +
|
|
380
439
|
`implies scope, criteria, and constraints. Tease them out.\n\n` +
|
|
@@ -396,16 +455,21 @@ async function* inferSpecFromTopicOnly(topic, conversationContext, leaderModelId
|
|
|
396
455
|
`- Write all fields in the user's language (detected from the topic).`;
|
|
397
456
|
const prompt = `## Topic\n${topic}\n\n${conversationContext ? `## Context\n${conversationContext}\n` : ""}`;
|
|
398
457
|
try {
|
|
458
|
+
// Model-fallback: the cost-tier synth model can sit on a flaky proxy
|
|
459
|
+
// (Console Go glm/kimi → "Upstream request failed"). Falling back to
|
|
460
|
+
// buildSpecFromTopic yields a single generic criterion, which makes the
|
|
461
|
+
// leader eval read a vague "1/1 criteria met". Retry on healthy panel models
|
|
462
|
+
// first so the spec keeps its ≥3 observable criteria.
|
|
399
463
|
const synthModel = pickCouncilTaskModel("spec_synthesis", leaderModelId, costAware);
|
|
400
|
-
const raw = yield*
|
|
464
|
+
const raw = yield* tracedGenerateWithFallback(llm, {
|
|
401
465
|
phase: "synthesis",
|
|
402
466
|
label: "Inferring spec from topic (no clarification answers)",
|
|
403
|
-
|
|
467
|
+
models: [synthModel, ...fallbackModels],
|
|
404
468
|
system,
|
|
405
469
|
prompt,
|
|
406
470
|
maxTokens: 1024,
|
|
407
471
|
});
|
|
408
|
-
const match = raw
|
|
472
|
+
const match = raw?.match(/\{[\s\S]*\}/);
|
|
409
473
|
if (match) {
|
|
410
474
|
const parsed = JSON.parse(match[0]);
|
|
411
475
|
const criteria = Array.isArray(parsed.successCriteria) && parsed.successCriteria.length >= 1
|
|
@@ -429,12 +493,12 @@ async function* inferSpecFromTopicOnly(topic, conversationContext, leaderModelId
|
|
|
429
493
|
}
|
|
430
494
|
return buildSpecFromTopic(topic, conversationContext);
|
|
431
495
|
}
|
|
432
|
-
async function* synthesizeSpec(topic, conversationContext, qa, leaderModelId, llm, costAware = false) {
|
|
496
|
+
async function* synthesizeSpec(topic, conversationContext, qa, leaderModelId, llm, costAware = false, fallbackModels = []) {
|
|
433
497
|
if (qa.length === 0) {
|
|
434
498
|
// Clarifier asked 0 questions OR user skipped all of them. Use the LLM to
|
|
435
499
|
// pull implicit criteria/constraints/scope from the topic alone instead of
|
|
436
500
|
// returning the single-criterion fallback that makes leader-eval trivial.
|
|
437
|
-
return yield* inferSpecFromTopicOnly(topic, conversationContext, leaderModelId, llm, costAware);
|
|
501
|
+
return yield* inferSpecFromTopicOnly(topic, conversationContext, leaderModelId, llm, costAware, fallbackModels);
|
|
438
502
|
}
|
|
439
503
|
const { system, prompt } = buildSpecSynthesisPrompt(topic, conversationContext, qa);
|
|
440
504
|
const resolved = {};
|
|
@@ -444,16 +508,18 @@ async function* synthesizeSpec(topic, conversationContext, qa, leaderModelId, ll
|
|
|
444
508
|
}
|
|
445
509
|
}
|
|
446
510
|
try {
|
|
511
|
+
// Model-fallback (see inferSpecFromTopicOnly): keep the ≥3-criteria spec even
|
|
512
|
+
// when the leader's cost-tier synth model is on a failing proxy.
|
|
447
513
|
const synthModel = pickCouncilTaskModel("spec_synthesis", leaderModelId, costAware);
|
|
448
|
-
const raw = yield*
|
|
514
|
+
const raw = yield* tracedGenerateWithFallback(llm, {
|
|
449
515
|
phase: "synthesis",
|
|
450
516
|
label: "Synthesizing clarified spec",
|
|
451
|
-
|
|
517
|
+
models: [synthModel, ...fallbackModels],
|
|
452
518
|
system,
|
|
453
519
|
prompt,
|
|
454
520
|
maxTokens: 2048,
|
|
455
521
|
});
|
|
456
|
-
const match = raw
|
|
522
|
+
const match = raw?.match(/\{[\s\S]*\}/);
|
|
457
523
|
if (match) {
|
|
458
524
|
const parsed = JSON.parse(match[0]);
|
|
459
525
|
return {
|
|
@@ -98,7 +98,7 @@ function getCompactionText(msg) {
|
|
|
98
98
|
return null;
|
|
99
99
|
return msg.content;
|
|
100
100
|
}
|
|
101
|
-
function
|
|
101
|
+
function extractText(content) {
|
|
102
102
|
if (typeof content === "string")
|
|
103
103
|
return content;
|
|
104
104
|
if (Array.isArray(content)) {
|
|
@@ -109,40 +109,74 @@ function extractUserText(content) {
|
|
|
109
109
|
}
|
|
110
110
|
return "";
|
|
111
111
|
}
|
|
112
|
+
/** Filter assistant messages to exclude tool-call-only turns (tool_use + tool_result
|
|
113
|
+
* pairs that carry no analytical text). An assistant turn with ≥80 chars of text
|
|
114
|
+
* content is considered "analytical" (contains decisions, analysis, code, or plans). */
|
|
115
|
+
function isAnalyticalAssistant(content) {
|
|
116
|
+
const text = extractText(content);
|
|
117
|
+
return text.trim().length >= 80;
|
|
118
|
+
}
|
|
119
|
+
/** Maximum total characters for the built context string. Raised from 12K to 32K
|
|
120
|
+
* (~8K tokens) so council participants get meaningful session history rather than
|
|
121
|
+
* only 5 user-message snippets. */
|
|
122
|
+
const MAX_CONTEXT_CHARS = 32_000;
|
|
112
123
|
export function buildCouncilContext(messages) {
|
|
113
124
|
const parts = [];
|
|
125
|
+
// 1. Compaction summary (first message if it's a system compaction)
|
|
114
126
|
if (messages.length > 0 && isCompactionSummary(messages[0])) {
|
|
115
127
|
const summary = getCompactionText(messages[0]);
|
|
116
128
|
if (summary) {
|
|
117
129
|
parts.push(`## Session Context (from compaction summary)\n${summary}`);
|
|
118
130
|
}
|
|
119
131
|
}
|
|
120
|
-
|
|
121
|
-
|
|
132
|
+
// 2. Recent conversation turns — scan BOTH user + analytical assistant messages
|
|
133
|
+
// from the tail, capturing at most 8 meaningful entries.
|
|
134
|
+
// Previously only captured 5 user messages — lost ALL assistant analysis,
|
|
135
|
+
// code changes, and decisions made during the session.
|
|
136
|
+
const conversationTurns = [];
|
|
137
|
+
for (let i = messages.length - 1; i >= 0 && conversationTurns.length < 8; i--) {
|
|
122
138
|
const msg = messages[i];
|
|
123
139
|
if (msg.role === "user") {
|
|
124
|
-
const text =
|
|
140
|
+
const text = extractText(msg.content);
|
|
141
|
+
if (text.trim()) {
|
|
142
|
+
conversationTurns.unshift(`[User] ${text.slice(0, 3000).trim()}`);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
else if (msg.role === "assistant" && isAnalyticalAssistant(msg.content)) {
|
|
146
|
+
const text = extractText(msg.content);
|
|
125
147
|
if (text.trim()) {
|
|
126
|
-
|
|
148
|
+
// Cap assistant text to 1500 chars — enough to convey analysis/decisions
|
|
149
|
+
// without blowing the context budget on long code outputs.
|
|
150
|
+
conversationTurns.unshift(`[Assistant] ${text.slice(0, 1500).trim()}`);
|
|
127
151
|
}
|
|
128
152
|
}
|
|
129
153
|
}
|
|
130
|
-
if (
|
|
131
|
-
parts.push(`## Recent
|
|
154
|
+
if (conversationTurns.length > 0) {
|
|
155
|
+
parts.push(`## Recent Conversation\n${conversationTurns.join("\n\n")}`);
|
|
132
156
|
}
|
|
133
|
-
|
|
157
|
+
// 3. Key decisions from system messages (task outcomes, decisions, plans)
|
|
158
|
+
const decisionMsgs = [];
|
|
134
159
|
for (const msg of messages) {
|
|
135
|
-
if (msg.role
|
|
136
|
-
|
|
160
|
+
if (msg.role !== "system" || typeof msg.content !== "string")
|
|
161
|
+
continue;
|
|
162
|
+
const c = msg.content;
|
|
163
|
+
if (c.includes("[Council Memory]") ||
|
|
164
|
+
c.includes("[Council Decision]") ||
|
|
165
|
+
c.includes("[Council Action Items]") ||
|
|
166
|
+
c.includes("[Council Plan Update]") ||
|
|
167
|
+
c.includes("[Decision]") ||
|
|
168
|
+
c.includes("[Task Completed]") ||
|
|
169
|
+
c.includes("[NEEDS HUMAN REVIEW]")) {
|
|
170
|
+
decisionMsgs.push(c);
|
|
137
171
|
}
|
|
138
172
|
}
|
|
139
|
-
if (
|
|
140
|
-
const digests =
|
|
141
|
-
parts.push(`##
|
|
173
|
+
if (decisionMsgs.length > 0) {
|
|
174
|
+
const digests = decisionMsgs.slice(-4).map(formatCouncilMemoryDigest);
|
|
175
|
+
parts.push(`## Key Decisions\n${digests.join("\n\n")}`);
|
|
142
176
|
}
|
|
143
177
|
const combined = parts.join("\n\n---\n\n");
|
|
144
|
-
if (combined.length >
|
|
145
|
-
return `${combined.slice(0,
|
|
178
|
+
if (combined.length > MAX_CONTEXT_CHARS) {
|
|
179
|
+
return `${combined.slice(0, MAX_CONTEXT_CHARS)}\n\n[... context truncated to fit token budget]`;
|
|
146
180
|
}
|
|
147
181
|
return combined;
|
|
148
182
|
}
|