muonroi-cli 1.7.2 → 1.8.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 +21 -21
- package/README.md +122 -122
- package/dist/packages/agent-harness-core/src/event-filter.js +1 -0
- package/dist/packages/agent-harness-core/src/event-redact.js +8 -0
- package/dist/packages/agent-harness-core/src/mcp-server.d.ts +16 -0
- package/dist/packages/agent-harness-core/src/mcp-server.js +88 -10
- package/dist/packages/agent-harness-core/src/protocol.d.ts +15 -0
- package/dist/packages/agent-harness-core/src/selector.js +2 -2
- package/dist/packages/agent-harness-core/src/transports/sidechannel.js +1 -1
- package/dist/packages/agent-harness-opentui/src/agent-mode.js +1 -1
- package/dist/packages/agent-harness-opentui/src/install.js +1 -1
- package/dist/src/__test-stubs__/vitest-setup.js +4 -0
- package/dist/src/agent-harness/__tests__/mock-model.spec.js +54 -0
- package/dist/src/agent-harness/mock-model.d.ts +1 -1
- package/dist/src/agent-harness/mock-model.js +32 -3
- package/dist/src/agent-harness/test-spawn.js +3 -1
- package/dist/src/chat/chat-keychain.js +9 -1
- package/dist/src/cli/cost-forensics.js +12 -12
- package/dist/src/cli/keys.d.ts +2 -2
- package/dist/src/cli/keys.js +107 -28
- package/dist/src/cli/usage-report.js +2 -2
- package/dist/src/council/__tests__/abort-threading.test.d.ts +1 -0
- package/dist/src/council/__tests__/abort-threading.test.js +193 -0
- package/dist/src/council/__tests__/clarification-prompt.test.js +21 -1
- package/dist/src/council/__tests__/council-turn-length-emit.test.d.ts +1 -0
- package/dist/src/council/__tests__/council-turn-length-emit.test.js +130 -0
- package/dist/src/council/__tests__/debate-round-budget.test.d.ts +1 -0
- package/dist/src/council/__tests__/debate-round-budget.test.js +45 -0
- package/dist/src/council/__tests__/evaluator-metrics.test.js +3 -0
- package/dist/src/council/__tests__/post-debate-recommendation.test.d.ts +1 -0
- package/dist/src/council/__tests__/post-debate-recommendation.test.js +58 -0
- package/dist/src/council/__tests__/research-tools.test.js +4 -0
- package/dist/src/council/__tests__/round-tools.test.js +6 -0
- package/dist/src/council/clarifier-question-cap.test.d.ts +12 -0
- package/dist/src/council/clarifier-question-cap.test.js +73 -0
- package/dist/src/council/clarifier.d.ts +29 -1
- package/dist/src/council/clarifier.js +64 -1
- package/dist/src/council/debate-planner.d.ts +2 -1
- package/dist/src/council/debate-planner.js +5 -3
- package/dist/src/council/debate.d.ts +9 -0
- package/dist/src/council/debate.js +88 -8
- package/dist/src/council/index.d.ts +46 -1
- package/dist/src/council/index.js +125 -19
- package/dist/src/council/llm.js +41 -13
- package/dist/src/council/prompts.js +56 -21
- package/dist/src/council/types.d.ts +14 -1
- package/dist/src/ee/__tests__/export-transcripts.test.js +1 -1
- package/dist/src/ee/bridge.d.ts +3 -1
- package/dist/src/ee/bridge.js +9 -3
- package/dist/src/ee/bridge.test.js +1 -2
- package/dist/src/ee/client.js +15 -2
- package/dist/src/ee/recall-ledger.d.ts +20 -0
- package/dist/src/ee/recall-ledger.js +40 -1
- package/dist/src/ee/search.d.ts +25 -0
- package/dist/src/ee/search.js +73 -0
- package/dist/src/ee/types.d.ts +15 -1
- package/dist/src/ee/who-am-i.d.ts +41 -0
- package/dist/src/ee/who-am-i.js +143 -0
- package/dist/src/ee/who-am-i.test.d.ts +1 -0
- package/dist/src/ee/who-am-i.test.js +101 -0
- package/dist/src/generated/version.d.ts +1 -1
- package/dist/src/generated/version.js +1 -1
- package/dist/src/headless/council-answers.js +1 -0
- package/dist/src/headless/output.d.ts +30 -1
- package/dist/src/headless/output.js +180 -0
- package/dist/src/headless/output.test.js +61 -1
- package/dist/src/hooks/index.js +48 -2
- package/dist/src/index.d.ts +2 -0
- package/dist/src/index.js +55 -60
- package/dist/src/lsp/client-normalize.test.d.ts +12 -0
- package/dist/src/lsp/client-normalize.test.js +34 -0
- package/dist/src/lsp/client.d.ts +1 -0
- package/dist/src/lsp/client.js +10 -2
- package/dist/src/lsp/manager.d.ts +1 -1
- package/dist/src/lsp/manager.js +2 -2
- package/dist/src/lsp/manager.test.js +2 -2
- package/dist/src/lsp/runtime.d.ts +1 -1
- package/dist/src/lsp/runtime.js +2 -2
- package/dist/src/maintain/__tests__/gh-create-pr.test.js +7 -3
- package/dist/src/maintain/__tests__/pr-builder.test.js +7 -3
- package/dist/src/mcp/__tests__/client-pool.spec.js +3 -3
- package/dist/src/mcp/__tests__/ee-tools.test.js +42 -14
- package/dist/src/mcp/__tests__/forensics-tools.test.js +3 -3
- package/dist/src/mcp/__tests__/lsp-tools.test.js +4 -4
- package/dist/src/mcp/client-pool.d.ts +1 -1
- package/dist/src/mcp/client-pool.js +25 -9
- package/dist/src/mcp/ee-tools.d.ts +10 -0
- package/dist/src/mcp/ee-tools.js +34 -0
- package/dist/src/mcp/oauth-callback.js +2 -2
- package/dist/src/mcp/parse-headers.test.js +14 -14
- package/dist/src/mcp/self-verify-jobs.js +1 -3
- package/dist/src/mcp/setup-guide-text.js +74 -74
- package/dist/src/mcp/smoke.test.js +43 -43
- package/dist/src/models/catalog-gemini.test.js +12 -10
- package/dist/src/models/catalog.json +106 -38
- package/dist/src/ops/doctor.js +7 -7
- package/dist/src/orchestrator/__tests__/auto-commit.test.d.ts +1 -0
- package/dist/src/orchestrator/__tests__/auto-commit.test.js +142 -0
- package/dist/src/orchestrator/__tests__/batch-turn-runner.test.js +2 -0
- package/dist/src/orchestrator/__tests__/delegations.test.d.ts +1 -0
- package/dist/src/orchestrator/__tests__/delegations.test.js +96 -0
- package/dist/src/orchestrator/__tests__/message-processor.test.js +1 -0
- package/dist/src/orchestrator/__tests__/stream-runner.test.js +29 -0
- package/dist/src/orchestrator/agent.test.js +5 -2
- package/dist/src/orchestrator/auto-commit.d.ts +110 -0
- package/dist/src/orchestrator/auto-commit.js +364 -0
- package/dist/src/orchestrator/batch-turn-runner.d.ts +3 -2
- package/dist/src/orchestrator/batch-turn-runner.js +2 -1
- package/dist/src/orchestrator/compaction-proposer-prompt.d.ts +8 -0
- package/dist/src/orchestrator/compaction-proposer-prompt.js +43 -0
- package/dist/src/orchestrator/compaction.d.ts +22 -0
- package/dist/src/orchestrator/compaction.js +42 -0
- package/dist/src/orchestrator/delegations.d.ts +1 -0
- package/dist/src/orchestrator/delegations.js +41 -0
- package/dist/src/orchestrator/error-utils.js +27 -2
- package/dist/src/orchestrator/message-processor.d.ts +21 -5
- package/dist/src/orchestrator/message-processor.js +219 -2573
- package/dist/src/orchestrator/orchestrator.d.ts +8 -2
- package/dist/src/orchestrator/orchestrator.js +187 -34
- package/dist/src/orchestrator/preprocessor.d.ts +13 -0
- package/dist/src/orchestrator/preprocessor.js +145 -0
- package/dist/src/orchestrator/prompts.d.ts +8 -0
- package/dist/src/orchestrator/prompts.js +76 -60
- package/dist/src/orchestrator/safety-askcard.d.ts +66 -0
- package/dist/src/orchestrator/safety-askcard.js +74 -0
- package/dist/src/orchestrator/sandbox.test.js +8 -5
- package/dist/src/orchestrator/scope-ceiling.js +1 -1
- package/dist/src/orchestrator/stall-watchdog.d.ts +47 -0
- package/dist/src/orchestrator/stall-watchdog.js +23 -0
- package/dist/src/orchestrator/stall-watchdog.test.js +36 -1
- package/dist/src/orchestrator/stream-runner.js +52 -30
- package/dist/src/orchestrator/sub-agent-model-tier.js +1 -1
- package/dist/src/orchestrator/subagent-compactor.d.ts +14 -0
- package/dist/src/orchestrator/subagent-compactor.js +83 -10
- package/dist/src/orchestrator/subagent-compactor.spec.js +132 -0
- package/dist/src/orchestrator/text-tool-call-detector.d.ts +9 -5
- package/dist/src/orchestrator/text-tool-call-detector.js +16 -12
- package/dist/src/orchestrator/text-tool-call-detector.test.js +66 -8
- package/dist/src/orchestrator/tool-args-repair.js +1 -1
- package/dist/src/orchestrator/tool-engine.d.ts +151 -0
- package/dist/src/orchestrator/tool-engine.js +3079 -0
- package/dist/src/orchestrator/turn-runner-deps.d.ts +1 -0
- package/dist/src/pil/__tests__/discovery-types.test.js +15 -4
- package/dist/src/pil/__tests__/discovery.test.js +89 -145
- package/dist/src/pil/__tests__/dual-run.test.js +2 -0
- package/dist/src/pil/__tests__/layer1-intent-trace.test.js +3 -0
- package/dist/src/pil/__tests__/layer1-intent.test.js +3 -0
- package/dist/src/pil/__tests__/layer16-clarity.test.js +35 -32
- package/dist/src/pil/__tests__/layer18-acceptance.test.js +1 -78
- package/dist/src/pil/__tests__/layer2_5-ponytail.test.d.ts +1 -0
- package/dist/src/pil/__tests__/layer2_5-ponytail.test.js +53 -0
- package/dist/src/pil/__tests__/layer3-ee-injection.test.js +67 -4
- package/dist/src/pil/__tests__/layer3-injected-chunk.test.js +14 -2
- package/dist/src/pil/__tests__/layer4-gsd.test.js +30 -0
- package/dist/src/pil/__tests__/layer5-context.test.js +39 -1
- package/dist/src/pil/__tests__/layer6-output.test.js +30 -2
- package/dist/src/pil/__tests__/orchestrator-integration.test.js +3 -1
- package/dist/src/pil/__tests__/pipeline.test.js +11 -8
- package/dist/src/pil/__tests__/response-tools.test.js +52 -0
- package/dist/src/pil/agent-operating-contract.d.ts +1 -1
- package/dist/src/pil/agent-operating-contract.js +5 -3
- package/dist/src/pil/agent-operating-contract.test.js +2 -2
- package/dist/src/pil/cheap-model-playbook.d.ts +1 -1
- package/dist/src/pil/cheap-model-playbook.js +1 -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/config.d.ts +20 -0
- package/dist/src/pil/config.js +35 -0
- package/dist/src/pil/discovery-types.d.ts +32 -9
- package/dist/src/pil/discovery.d.ts +3 -3
- package/dist/src/pil/discovery.js +138 -244
- package/dist/src/pil/index.d.ts +1 -1
- package/dist/src/pil/index.js +1 -1
- package/dist/src/pil/layer1-intent.d.ts +9 -1
- package/dist/src/pil/layer1-intent.js +99 -44
- package/dist/src/pil/layer1-intent.test.js +112 -2
- package/dist/src/pil/layer15-context-scan.js +1 -1
- package/dist/src/pil/layer16-clarity.d.ts +21 -25
- package/dist/src/pil/layer16-clarity.js +52 -88
- package/dist/src/pil/layer18-acceptance.d.ts +13 -3
- package/dist/src/pil/layer18-acceptance.js +1 -75
- package/dist/src/pil/layer2-personality.js +7 -8
- package/dist/src/pil/layer2_5-ponytail.d.ts +2 -0
- package/dist/src/pil/layer2_5-ponytail.js +41 -0
- package/dist/src/pil/layer3-ee-injection.js +180 -14
- package/dist/src/pil/layer4-gsd.js +22 -8
- package/dist/src/pil/layer5-context.d.ts +1 -0
- package/dist/src/pil/layer5-context.js +26 -11
- package/dist/src/pil/layer6-output.js +23 -5
- package/dist/src/pil/llm-classify.js +3 -3
- package/dist/src/pil/native-capabilities-workbook.d.ts +1 -1
- package/dist/src/pil/native-capabilities-workbook.js +4 -3
- package/dist/src/pil/pipeline.js +15 -1
- package/dist/src/pil/response-tools.d.ts +16 -0
- package/dist/src/pil/response-tools.js +41 -0
- package/dist/src/pil/schema.d.ts +8 -0
- package/dist/src/pil/schema.js +24 -3
- package/dist/src/pil/task-tier-map.js +7 -7
- package/dist/src/pil/types.d.ts +9 -3
- package/dist/src/product-loop/__tests__/discovery-interview.test.js +103 -1
- package/dist/src/product-loop/__tests__/gather-selectable-alts.test.js +34 -0
- package/dist/src/product-loop/__tests__/sprint-self-verify.test.js +12 -12
- package/dist/src/product-loop/discovery-interview.d.ts +20 -0
- package/dist/src/product-loop/discovery-interview.js +121 -7
- package/dist/src/product-loop/done-gate.js +3 -3
- package/dist/src/product-loop/gather.js +19 -5
- package/dist/src/product-loop/loop-driver.js +20 -20
- package/dist/src/product-loop/progress-snapshot.js +4 -4
- package/dist/src/product-loop/sprint-self-verify.js +2 -2
- package/dist/src/providers/__tests__/reasoning-roundtrip.test.js +12 -0
- package/dist/src/providers/auth/__tests__/gemini-oauth.test.d.ts +1 -5
- package/dist/src/providers/auth/__tests__/gemini-oauth.test.js +1 -5
- package/dist/src/providers/auth/gcloud.d.ts +28 -0
- package/dist/src/providers/auth/gcloud.js +102 -0
- package/dist/src/providers/auth/gemini-oauth.d.ts +46 -19
- package/dist/src/providers/auth/gemini-oauth.js +292 -105
- package/dist/src/providers/auth/grok-oauth.d.ts +1 -1
- package/dist/src/providers/auth/grok-oauth.js +1 -1
- package/dist/src/providers/auth/registry.js +28 -6
- package/dist/src/providers/gemini.d.ts +1 -1
- package/dist/src/providers/gemini.js +10 -3
- package/dist/src/providers/keychain.d.ts +2 -2
- package/dist/src/providers/keychain.js +20 -2
- package/dist/src/providers/mcp-vision-bridge.js +48 -48
- package/dist/src/providers/openai.js +5 -1
- package/dist/src/providers/runtime.d.ts +1 -1
- package/dist/src/providers/runtime.js +11 -2
- package/dist/src/providers/siliconflow-sse-repair.js +1 -1
- package/dist/src/providers/strategies/google.strategy.d.ts +9 -2
- package/dist/src/providers/strategies/google.strategy.js +159 -9
- package/dist/src/providers/strategies/thinking-mode.js +19 -6
- package/dist/src/providers/types.d.ts +1 -1
- package/dist/src/reporter/index.js +1 -1
- package/dist/src/router/decide.test.js +40 -8
- package/dist/src/router/step-router.d.ts +20 -0
- package/dist/src/router/step-router.js +73 -0
- 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.js +60 -60
- package/dist/src/scaffold/init-new.js +453 -453
- package/dist/src/self-qa/__tests__/scenario-planner.test.js +3 -3
- package/dist/src/self-qa/agentic-loop.js +19 -19
- package/dist/src/self-qa/orchestrator.js +5 -5
- package/dist/src/self-qa/scenario-planner.js +1 -1
- package/dist/src/self-qa/spec-emitter.js +4 -4
- package/dist/src/storage/__tests__/migrations.test.js +2 -2
- package/dist/src/storage/index.d.ts +1 -1
- package/dist/src/storage/index.js +1 -1
- package/dist/src/storage/interaction-log.js +5 -5
- package/dist/src/storage/migrations.js +122 -122
- package/dist/src/storage/session-experience-store.js +4 -4
- package/dist/src/storage/sessions.js +43 -43
- package/dist/src/storage/transcript-view.js +7 -1
- package/dist/src/storage/transcript.d.ts +1 -0
- package/dist/src/storage/transcript.js +55 -0
- package/dist/src/storage/ui-interaction-log.d.ts +17 -0
- package/dist/src/storage/usage.js +14 -14
- package/dist/src/storage/workspaces.js +12 -12
- package/dist/src/tools/__tests__/native-tools.test.js +6 -0
- package/dist/src/tools/bash.d.ts +36 -23
- package/dist/src/tools/bash.js +59 -301
- package/dist/src/tools/bash.test.js +2 -257
- package/dist/src/tools/file-tracker.d.ts +5 -0
- package/dist/src/tools/file-tracker.js +14 -0
- package/dist/src/tools/file.js +2 -0
- package/dist/src/tools/git-safety.d.ts +38 -1
- package/dist/src/tools/git-safety.js +94 -4
- package/dist/src/tools/git-safety.test.js +45 -1
- package/dist/src/tools/native-tools.d.ts +1 -1
- package/dist/src/tools/native-tools.js +56 -0
- package/dist/src/tools/registry-bash-empty-command.test.js +4 -3
- package/dist/src/tools/registry-git-safety.test.js +17 -6
- package/dist/src/tools/registry.d.ts +5 -0
- package/dist/src/tools/registry.js +185 -11
- package/dist/src/types/index.d.ts +7 -1
- package/dist/src/ui/app.d.ts +1 -0
- package/dist/src/ui/app.js +0 -0
- package/dist/src/ui/components/council-question-card.js +1 -0
- package/dist/src/ui/components/message-view.js +37 -3
- package/dist/src/ui/components/structured-response-view.js +7 -0
- package/dist/src/ui/components/task-list-panel.js +3 -12
- package/dist/src/ui/containers/chat-feed.d.ts +40 -0
- package/dist/src/ui/containers/chat-feed.js +66 -0
- package/dist/src/ui/containers/modals-layer.d.ts +87 -0
- package/dist/src/ui/containers/modals-layer.js +18 -0
- package/dist/src/ui/slash/__tests__/menu-parity.test.d.ts +1 -0
- package/dist/src/ui/slash/__tests__/menu-parity.test.js +1 -0
- package/dist/src/ui/slash/__tests__/ponytail.test.d.ts +1 -0
- package/dist/src/ui/slash/__tests__/ponytail.test.js +37 -0
- package/dist/src/ui/slash/council-inspect.js +4 -4
- package/dist/src/ui/slash/export.js +16 -1
- package/dist/src/ui/slash/menu-items.js +1 -0
- package/dist/src/ui/slash/ponytail.d.ts +2 -0
- package/dist/src/ui/slash/ponytail.js +19 -0
- package/dist/src/ui/status-bar/store.js +0 -1
- package/dist/src/ui/status-bar/store.test.js +2 -2
- package/dist/src/ui/use-app-logic.d.ts +26 -0
- package/dist/src/ui/use-app-logic.js +0 -0
- package/dist/src/ui/utils/relaunch.js +1 -1
- package/dist/src/ui/utils/text.d.ts +11 -0
- package/dist/src/ui/utils/text.js +21 -1
- package/dist/src/ui/utils/text.test.js +25 -1
- package/dist/src/ui/utils/tools.js +3 -1
- package/dist/src/usage/cost-log.js +2 -2
- package/dist/src/usage/estimator.d.ts +14 -0
- package/dist/src/usage/estimator.js +22 -0
- package/dist/src/utils/__tests__/compaction-caps.test.d.ts +21 -0
- package/dist/src/utils/__tests__/compaction-caps.test.js +187 -0
- package/dist/src/utils/__tests__/llm-deadline-abort.test.d.ts +1 -0
- package/dist/src/utils/__tests__/llm-deadline-abort.test.js +33 -0
- package/dist/src/utils/clipboard-image.js +23 -23
- package/dist/src/utils/llm-deadline.d.ts +9 -1
- package/dist/src/utils/llm-deadline.js +31 -2
- package/dist/src/utils/permission-mode.d.ts +33 -0
- package/dist/src/utils/permission-mode.js +164 -3
- package/dist/src/utils/permission-mode.test.js +92 -1
- package/dist/src/utils/settings.d.ts +27 -31
- package/dist/src/utils/settings.js +100 -109
- package/dist/src/utils/side-question.js +2 -2
- package/dist/src/utils/skills.js +3 -3
- package/dist/src/verify/__tests__/coverage-parsers.test.js +30 -30
- package/package.json +1 -1
|
@@ -22,6 +22,7 @@
|
|
|
22
22
|
* const calls = handle.calls; // LanguageModelV3CallOptions[]
|
|
23
23
|
* expect(calls[0].maxOutputTokens).toBeUndefined();
|
|
24
24
|
*/
|
|
25
|
+
import { APICallError } from "@ai-sdk/provider";
|
|
25
26
|
import { simulateReadableStream } from "ai";
|
|
26
27
|
import { MockLanguageModelV3 } from "ai/test";
|
|
27
28
|
function isNestedArray(v) {
|
|
@@ -71,7 +72,7 @@ export function createMockModel(fx) {
|
|
|
71
72
|
const text = generates.length > 0 ? generates[Math.min(genIdx, generates.length - 1)] : "{}";
|
|
72
73
|
genIdx += 1;
|
|
73
74
|
if (process.env.MUONROI_DEBUG_MOCK_MODEL === "1") {
|
|
74
|
-
process.stderr.write(`[mock-model] doGenerate #${genIdx} → ${text.length} chars\n`);
|
|
75
|
+
process.stderr.write(`[mock-model] doGenerate #${genIdx} → ${text.length} chars\nCALLER:\n${new Error().stack}\n`);
|
|
75
76
|
}
|
|
76
77
|
return {
|
|
77
78
|
content: [{ type: "text", text }],
|
|
@@ -201,12 +202,40 @@ function normalizeStreamChunks(chunks) {
|
|
|
201
202
|
return chunks.map((c) => {
|
|
202
203
|
if (c && c.type === "error") {
|
|
203
204
|
const raw = c.error;
|
|
204
|
-
|
|
205
|
-
return { ...c, error: err };
|
|
205
|
+
return { ...c, error: buildFixtureError(raw) };
|
|
206
206
|
}
|
|
207
207
|
return c;
|
|
208
208
|
});
|
|
209
209
|
}
|
|
210
|
+
/**
|
|
211
|
+
* Turn a fixture `error` payload into the right runtime shape:
|
|
212
|
+
* - an `APICallError` when the payload is an object carrying a `statusCode`
|
|
213
|
+
* (or `apiCallError: true`), so harness specs can exercise the orchestrator's
|
|
214
|
+
* status-aware paths (humanizeApiError 5xx canned text, summarizeApiErrorForLog
|
|
215
|
+
* forensics, retry classifier). `APICallError.isInstance` duck-types on a
|
|
216
|
+
* shared marker, so constructing it here satisfies the `isInstance` checks in
|
|
217
|
+
* error-utils.ts. A pure JSON fixture cannot otherwise build one (it would
|
|
218
|
+
* arrive as a plain string).
|
|
219
|
+
* - a plain `Error` for string payloads (the common transient-message case).
|
|
220
|
+
*/
|
|
221
|
+
function buildFixtureError(raw) {
|
|
222
|
+
if (raw instanceof Error)
|
|
223
|
+
return raw;
|
|
224
|
+
if (raw && typeof raw === "object") {
|
|
225
|
+
const o = raw;
|
|
226
|
+
if (o.apiCallError === true || typeof o.statusCode === "number") {
|
|
227
|
+
return new APICallError({
|
|
228
|
+
message: o.message ?? `mock API error${o.statusCode ? ` (HTTP ${o.statusCode})` : ""}`,
|
|
229
|
+
url: o.url ?? "https://mock.invalid/v1/chat/completions",
|
|
230
|
+
requestBodyValues: {},
|
|
231
|
+
statusCode: o.statusCode,
|
|
232
|
+
responseBody: o.responseBody,
|
|
233
|
+
isRetryable: o.isRetryable,
|
|
234
|
+
});
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
return new Error(typeof raw === "string" ? raw : "mock LLM error");
|
|
238
|
+
}
|
|
210
239
|
export async function loadMockModelFromDir(dir) {
|
|
211
240
|
// Diagnostic logging path. Without these messages, every failure mode
|
|
212
241
|
// (directory missing, no .json files, malformed JSON, no `model` block)
|
|
@@ -131,7 +131,9 @@ async function spawnWindows(args, opts) {
|
|
|
131
131
|
await waitForHandshake(outSocket, timeoutMs);
|
|
132
132
|
}
|
|
133
133
|
catch (err) {
|
|
134
|
-
proc.kill
|
|
134
|
+
if (proc && typeof proc.kill === "function") {
|
|
135
|
+
proc.kill();
|
|
136
|
+
}
|
|
135
137
|
inServer.close();
|
|
136
138
|
outServer.close();
|
|
137
139
|
throw err;
|
|
@@ -86,7 +86,15 @@ export async function deleteChatSecret(id) {
|
|
|
86
86
|
const kt = await loadKeytar();
|
|
87
87
|
if (!kt?.deletePassword)
|
|
88
88
|
return false;
|
|
89
|
-
|
|
89
|
+
try {
|
|
90
|
+
return await kt.deletePassword(KEYCHAIN_SERVICE, ACCOUNT_BY_CHAT[id]);
|
|
91
|
+
}
|
|
92
|
+
catch (err) {
|
|
93
|
+
if (process.env.DEBUG || process.env.MUONROI_DEBUG_KEYCHAIN) {
|
|
94
|
+
console.error(`[chat-keychain] deletePassword backend error for ${id}:`, err?.message || err);
|
|
95
|
+
}
|
|
96
|
+
return false;
|
|
97
|
+
}
|
|
90
98
|
}
|
|
91
99
|
/**
|
|
92
100
|
* List all stored chat secret IDs.
|
|
@@ -43,21 +43,21 @@ export function collectCostForensics(sessionId) {
|
|
|
43
43
|
if (!session)
|
|
44
44
|
throw new Error(`Session not found: ${sessionId}`);
|
|
45
45
|
const rows = db
|
|
46
|
-
.prepare(`
|
|
47
|
-
SELECT id, source, model, message_seq, input_tokens, output_tokens,
|
|
48
|
-
cache_read_tokens, cache_creation_tokens, cost_micros, created_at,
|
|
49
|
-
provider_options_shape
|
|
50
|
-
FROM usage_events
|
|
51
|
-
WHERE session_id = ?
|
|
52
|
-
ORDER BY id ASC
|
|
46
|
+
.prepare(`
|
|
47
|
+
SELECT id, source, model, message_seq, input_tokens, output_tokens,
|
|
48
|
+
cache_read_tokens, cache_creation_tokens, cost_micros, created_at,
|
|
49
|
+
provider_options_shape
|
|
50
|
+
FROM usage_events
|
|
51
|
+
WHERE session_id = ?
|
|
52
|
+
ORDER BY id ASC
|
|
53
53
|
`)
|
|
54
54
|
.all(sessionId);
|
|
55
55
|
const counts = db
|
|
56
|
-
.prepare(`
|
|
57
|
-
SELECT event_type, COUNT(*) AS c
|
|
58
|
-
FROM interaction_logs
|
|
59
|
-
WHERE session_id = ?
|
|
60
|
-
GROUP BY event_type
|
|
56
|
+
.prepare(`
|
|
57
|
+
SELECT event_type, COUNT(*) AS c
|
|
58
|
+
FROM interaction_logs
|
|
59
|
+
WHERE session_id = ?
|
|
60
|
+
GROUP BY event_type
|
|
61
61
|
`)
|
|
62
62
|
.all(sessionId);
|
|
63
63
|
const countMap = new Map(counts.map((r) => [r.event_type, r.c]));
|
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, google)
|
|
19
|
-
* keys logout <provider> — OAuth logout (openai, google)
|
|
18
|
+
* keys login <provider> — OAuth login (openai, google/agy)
|
|
19
|
+
* keys logout <provider> — OAuth logout (openai, google/agy)
|
|
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, google)
|
|
19
|
-
* keys logout <provider> — OAuth logout (openai, google)
|
|
18
|
+
* keys login <provider> — OAuth login (openai, google/agy)
|
|
19
|
+
* keys logout <provider> — OAuth logout (openai, google/agy)
|
|
20
20
|
*/
|
|
21
21
|
import { spawnSync } from "node:child_process";
|
|
22
22
|
import { promises as fs } from "node:fs";
|
|
@@ -49,8 +49,17 @@ function maskKey(key) {
|
|
|
49
49
|
return "***";
|
|
50
50
|
return `${key.slice(0, 6)}…${key.slice(-4)}`;
|
|
51
51
|
}
|
|
52
|
-
function
|
|
53
|
-
|
|
52
|
+
function normalizeProvider(p) {
|
|
53
|
+
const lower = p.toLowerCase();
|
|
54
|
+
if (lower === "agy")
|
|
55
|
+
return "google"; // agy is alias for google (OAuth + key)
|
|
56
|
+
if (KEYCHAIN_PROVIDER_IDS.includes(lower)) {
|
|
57
|
+
return lower;
|
|
58
|
+
}
|
|
59
|
+
return null;
|
|
60
|
+
}
|
|
61
|
+
function _isValidProvider(p) {
|
|
62
|
+
return normalizeProvider(p) !== null;
|
|
54
63
|
}
|
|
55
64
|
async function promptHidden(question) {
|
|
56
65
|
// ASCII control codes used during raw-mode capture.
|
|
@@ -104,8 +113,9 @@ async function promptHidden(question) {
|
|
|
104
113
|
});
|
|
105
114
|
}
|
|
106
115
|
export async function runKeysSet(provider, options = {}) {
|
|
107
|
-
|
|
108
|
-
|
|
116
|
+
const norm = normalizeProvider(provider);
|
|
117
|
+
if (!norm) {
|
|
118
|
+
console.error(`Unknown provider '${provider}'. Valid: ${KEYCHAIN_PROVIDER_IDS.join(", ")} (agy is alias for google)`);
|
|
109
119
|
process.exit(1);
|
|
110
120
|
}
|
|
111
121
|
const key = (await promptHidden(`Paste ${provider} API key (hidden): `)).trim();
|
|
@@ -124,7 +134,7 @@ export async function runKeysSet(provider, options = {}) {
|
|
|
124
134
|
console.log(`Bitwarden vault: ${res.action} '${itemName}'.`);
|
|
125
135
|
}
|
|
126
136
|
try {
|
|
127
|
-
const ok = await setKeyForProvider(
|
|
137
|
+
const ok = await setKeyForProvider(norm, key);
|
|
128
138
|
if (!ok) {
|
|
129
139
|
console.error("OS keychain unavailable (keytar failed to load or secret service backend unavailable).");
|
|
130
140
|
console.error("Common on Linux: install the runtime library, e.g.");
|
|
@@ -184,13 +194,35 @@ export async function runKeysList() {
|
|
|
184
194
|
const chatStored = await listChatSecrets();
|
|
185
195
|
// Load OAuth tokens for supported providers
|
|
186
196
|
const { loadTokens } = await import("../providers/auth/token-store.js");
|
|
197
|
+
const { getOAuthProviderConfig } = await import("../providers/auth/registry.js");
|
|
187
198
|
const oauthRows = [];
|
|
188
199
|
const oauthIds = await getOAuthProviderIds();
|
|
189
200
|
for (const p of oauthIds) {
|
|
190
201
|
try {
|
|
191
|
-
|
|
202
|
+
let tokens = await loadTokens(p);
|
|
203
|
+
if (!tokens && p === "google") {
|
|
204
|
+
// Import from agy storage only for fresh (non-expired) tokens.
|
|
205
|
+
// Stale agy tokens should be cleaned by running `keys logout google` + removing old .gemini file.
|
|
206
|
+
const { loadAgyTokensWithRefresh } = await import("../providers/auth/gemini-oauth.js");
|
|
207
|
+
const agyTokens = await loadAgyTokensWithRefresh();
|
|
208
|
+
if (agyTokens && Date.now() < agyTokens.expiresAt) {
|
|
209
|
+
tokens = agyTokens;
|
|
210
|
+
}
|
|
211
|
+
}
|
|
192
212
|
if (tokens) {
|
|
193
|
-
|
|
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
|
+
}
|
|
194
226
|
}
|
|
195
227
|
}
|
|
196
228
|
catch {
|
|
@@ -202,7 +234,7 @@ export async function runKeysList() {
|
|
|
202
234
|
console.log("No keys stored in OS keychain.");
|
|
203
235
|
console.log("Run 'muonroi-cli keys set <provider>' or 'muonroi-cli keys import-bw' to add some.");
|
|
204
236
|
console.log("Run 'muonroi-cli keys login openai' to log in with your OpenAI subscription.");
|
|
205
|
-
console.log("Run 'muonroi-cli keys login google' to log in with
|
|
237
|
+
console.log("Run 'muonroi-cli keys login google' or 'keys login agy' to log in with Agy (Google OAuth).");
|
|
206
238
|
console.log("Run 'muonroi-cli keys login xai' to log in with your SuperGrok / X Premium+ subscription.");
|
|
207
239
|
return;
|
|
208
240
|
}
|
|
@@ -215,7 +247,7 @@ export async function runKeysList() {
|
|
|
215
247
|
const account = row.email ?? "(no email)";
|
|
216
248
|
const expiry = new Date(row.expiresAt).toLocaleString();
|
|
217
249
|
const expired = Date.now() > row.expiresAt ? " [EXPIRED]" : "";
|
|
218
|
-
console.log(`${row.
|
|
250
|
+
console.log(`${row.display.padEnd(12)} ${account.padEnd(26)} ${expiry}${expired}`);
|
|
219
251
|
}
|
|
220
252
|
console.log("");
|
|
221
253
|
}
|
|
@@ -254,12 +286,37 @@ export async function runKeysList() {
|
|
|
254
286
|
}
|
|
255
287
|
}
|
|
256
288
|
export async function runKeysDelete(provider) {
|
|
257
|
-
|
|
258
|
-
|
|
289
|
+
const norm = normalizeProvider(provider);
|
|
290
|
+
if (!norm) {
|
|
291
|
+
console.error(`Unknown provider '${provider}'. Valid: ${KEYCHAIN_PROVIDER_IDS.join(", ")} (agy is alias for google)`);
|
|
259
292
|
process.exit(1);
|
|
260
293
|
}
|
|
261
|
-
const ok = await deleteKeyForProvider(
|
|
294
|
+
const ok = await deleteKeyForProvider(norm);
|
|
262
295
|
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
|
+
}
|
|
263
320
|
}
|
|
264
321
|
export async function runKeysExport(filePath) {
|
|
265
322
|
const providers = {};
|
|
@@ -330,13 +387,14 @@ export async function runKeysImport(filePath) {
|
|
|
330
387
|
let imported = 0;
|
|
331
388
|
let skipped = 0;
|
|
332
389
|
for (const [provider, key] of entries) {
|
|
333
|
-
|
|
390
|
+
const norm = normalizeProvider(provider);
|
|
391
|
+
if (!norm) {
|
|
334
392
|
console.warn(`Skipping unknown provider '${provider}' in bundle.`);
|
|
335
393
|
skipped++;
|
|
336
394
|
continue;
|
|
337
395
|
}
|
|
338
396
|
try {
|
|
339
|
-
const ok = await setKeyForProvider(
|
|
397
|
+
const ok = await setKeyForProvider(norm, key);
|
|
340
398
|
if (ok) {
|
|
341
399
|
imported++;
|
|
342
400
|
console.log(`✓ ${provider} → keychain (${maskKey(key)})`);
|
|
@@ -388,7 +446,8 @@ export async function runKeysImportBw(opts = {}) {
|
|
|
388
446
|
let imported = 0;
|
|
389
447
|
let skipped = 0;
|
|
390
448
|
for (const provider of requested) {
|
|
391
|
-
|
|
449
|
+
const norm = normalizeProvider(provider);
|
|
450
|
+
if (!norm) {
|
|
392
451
|
console.warn(`Skip unknown provider: ${provider}`);
|
|
393
452
|
skipped++;
|
|
394
453
|
continue;
|
|
@@ -407,7 +466,7 @@ export async function runKeysImportBw(opts = {}) {
|
|
|
407
466
|
continue;
|
|
408
467
|
}
|
|
409
468
|
try {
|
|
410
|
-
const ok = await setKeyForProvider(
|
|
469
|
+
const ok = await setKeyForProvider(norm, key);
|
|
411
470
|
if (!ok) {
|
|
412
471
|
console.warn(`! ${provider} — OS keychain unavailable (keytar or secret service backend).`);
|
|
413
472
|
console.warn(" Linux fix: sudo dnf install libsecret (Fedora) or sudo apt-get install libsecret-1-0 (Ubuntu)");
|
|
@@ -620,18 +679,36 @@ export async function runChatImportBw(opts = {}) {
|
|
|
620
679
|
*/
|
|
621
680
|
export async function runKeysLogin(provider) {
|
|
622
681
|
const oauthIds = await getOAuthProviderIds();
|
|
623
|
-
|
|
624
|
-
|
|
682
|
+
const norm = provider.toLowerCase() === "agy" ? "google" : provider;
|
|
683
|
+
if (!oauthIds.includes(norm)) {
|
|
684
|
+
const supported = oauthIds.map((id) => (id === "google" ? "google (or agy)" : id)).join(", ");
|
|
685
|
+
console.error(`OAuth login not supported for '${provider}'. Supported: ${supported}`);
|
|
625
686
|
process.exit(1);
|
|
626
687
|
}
|
|
627
688
|
const { getOAuthProviderConfig } = await import("../providers/auth/registry.js");
|
|
628
689
|
const { saveTokens } = await import("../providers/auth/token-store.js");
|
|
629
|
-
const cfg = await getOAuthProviderConfig(
|
|
690
|
+
const cfg = await getOAuthProviderConfig(norm);
|
|
630
691
|
if (!cfg) {
|
|
631
692
|
console.error(`OAuth provider '${provider}' is registered but failed to load.`);
|
|
632
693
|
process.exit(1);
|
|
633
694
|
}
|
|
634
695
|
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
|
+
}
|
|
635
712
|
console.log(`Logging in to ${name} via OAuth...`);
|
|
636
713
|
console.log("A browser window will open. Complete the sign-in to continue.");
|
|
637
714
|
const tokens = await cfg.provider.login({
|
|
@@ -646,12 +723,12 @@ export async function runKeysLogin(provider) {
|
|
|
646
723
|
console.log(` User code: ${codeOrUrl}`);
|
|
647
724
|
}
|
|
648
725
|
console.log("\n Waiting for authorization...");
|
|
649
|
-
if (
|
|
726
|
+
if (norm === "xai") {
|
|
650
727
|
console.log(" (If the xAI page shows a code instead of redirecting, paste it when the terminal prompts.)");
|
|
651
728
|
}
|
|
652
729
|
},
|
|
653
730
|
});
|
|
654
|
-
await saveTokens(
|
|
731
|
+
await saveTokens(norm, tokens);
|
|
655
732
|
const emailDisplay = tokens.email ? ` (${tokens.email})` : "";
|
|
656
733
|
const expiry = new Date(tokens.expiresAt).toLocaleString();
|
|
657
734
|
console.log(`\nLogged in to ${name}${emailDisplay}. Token expires: ${expiry}`);
|
|
@@ -665,22 +742,24 @@ export async function runKeysLogin(provider) {
|
|
|
665
742
|
*/
|
|
666
743
|
export async function runKeysLogout(provider) {
|
|
667
744
|
const oauthIds = await getOAuthProviderIds();
|
|
668
|
-
|
|
669
|
-
|
|
745
|
+
const norm = provider.toLowerCase() === "agy" ? "google" : provider;
|
|
746
|
+
if (!oauthIds.includes(norm)) {
|
|
747
|
+
const supported = oauthIds.map((id) => (id === "google" ? "google (or agy)" : id)).join(", ");
|
|
748
|
+
console.error(`OAuth logout not supported for '${provider}'. Supported: ${supported}`);
|
|
670
749
|
process.exit(1);
|
|
671
750
|
}
|
|
672
751
|
const { getOAuthProviderConfig } = await import("../providers/auth/registry.js");
|
|
673
752
|
const { loadTokens, deleteTokens } = await import("../providers/auth/token-store.js");
|
|
674
|
-
const tokens = await loadTokens(
|
|
753
|
+
const tokens = await loadTokens(norm);
|
|
675
754
|
if (!tokens) {
|
|
676
755
|
console.log(`No OAuth tokens stored for '${provider}'.`);
|
|
677
756
|
return;
|
|
678
757
|
}
|
|
679
|
-
const cfg = await getOAuthProviderConfig(
|
|
758
|
+
const cfg = await getOAuthProviderConfig(norm);
|
|
680
759
|
if (cfg) {
|
|
681
760
|
await cfg.provider.revoke(tokens); // best-effort
|
|
682
761
|
}
|
|
683
|
-
await deleteTokens(
|
|
762
|
+
await deleteTokens(norm);
|
|
684
763
|
const name = cfg?.displayName ?? provider;
|
|
685
764
|
console.log(`Logged out of ${name}. OAuth tokens revoked and deleted.`);
|
|
686
765
|
}
|
|
@@ -8,8 +8,8 @@ import { promises as fs } from "node:fs";
|
|
|
8
8
|
import * as os from "node:os";
|
|
9
9
|
import * as path from "node:path";
|
|
10
10
|
import { readCostLog } from "../usage/cost-log.js";
|
|
11
|
-
import { readProductLedger } from "../usage/product-ledger.js";
|
|
12
11
|
import { listDecisionLogDates, readDecisionLog } from "../usage/decision-log.js";
|
|
12
|
+
import { readProductLedger } from "../usage/product-ledger.js";
|
|
13
13
|
function muonroiHome(homeOverride) {
|
|
14
14
|
return homeOverride ?? process.env.MUONROI_CLI_HOME ?? path.join(os.homedir(), ".muonroi-cli");
|
|
15
15
|
}
|
|
@@ -284,7 +284,7 @@ export async function runSecurityAudit(opts = {}) {
|
|
|
284
284
|
const highRiskCmds = overrides
|
|
285
285
|
.map((d) => {
|
|
286
286
|
const ctx = d.meta?.context || {};
|
|
287
|
-
let cmd = ctx.command ? String(ctx.command) :
|
|
287
|
+
let cmd = ctx.command ? String(ctx.command) : ctx.shuru ? "[shuru]" : "";
|
|
288
288
|
if (cmd && cmd !== "[shuru]") {
|
|
289
289
|
cmd = cmd.replace(/((?:key|token|secret|pwd|pass|auth|AWS_)[^=]*=)[^\s]+/gi, "$1[REDACTED]");
|
|
290
290
|
cmd = cmd.replace(/https?:\/\/\S*?(?:key|token|secret)=\S+/gi, "[REDACTED_URL]");
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Abort-signal threading contract for the council subsystem.
|
|
3
|
+
*
|
|
4
|
+
* Bug this guards: a user-abort signal that reaches `runCouncil(options.signal)`
|
|
5
|
+
* was never forwarded to the `generate`-based call path (clarify, research-need,
|
|
6
|
+
* leader-eval, opening statements, round summary, synthesis, debate planning).
|
|
7
|
+
* `llm.generate` had no `signal` parameter at all and `withTimeoutSignal(undefined,…)`
|
|
8
|
+
* was hard-coded — so pressing Esc/Ctrl-C mid-council did nothing and the run
|
|
9
|
+
* continued making LLM calls to completion.
|
|
10
|
+
*
|
|
11
|
+
* The fix:
|
|
12
|
+
* 1. `CouncilLLM.generate` accepts a trailing `signal?` and threads it into the
|
|
13
|
+
* SDK call via `withTimeoutSignal(signal, …)`.
|
|
14
|
+
* 2. `runCouncil` wraps the injected `llm` with `withCouncilSignal(llm, signal)`
|
|
15
|
+
* so EVERY generate call site (none of which pass a signal) inherits it.
|
|
16
|
+
* 3. `planDebate` forwards the signal to its direct `generateObject` attempt.
|
|
17
|
+
*/
|
|
18
|
+
import { beforeEach, describe, expect, it, vi } from "vitest";
|
|
19
|
+
import { withCouncilSignal } from "../index.js";
|
|
20
|
+
/** Recording fake — captures the `signal` argument each method received. */
|
|
21
|
+
function recordingLlm(generateReply = () => "{}") {
|
|
22
|
+
const calls = [];
|
|
23
|
+
const llm = {
|
|
24
|
+
generate: async (_modelId, _system, prompt, _maxTokens, _onUsage, signal) => {
|
|
25
|
+
calls.push({ kind: "generate", signal });
|
|
26
|
+
return generateReply(prompt);
|
|
27
|
+
},
|
|
28
|
+
debate: async (_modelId, _system, _prompt, signal) => {
|
|
29
|
+
calls.push({ kind: "debate", signal });
|
|
30
|
+
return { text: "ok", toolCalls: [] };
|
|
31
|
+
},
|
|
32
|
+
research: async (_modelId, _topic, _ctx, signal) => {
|
|
33
|
+
calls.push({ kind: "research", signal });
|
|
34
|
+
return "findings";
|
|
35
|
+
},
|
|
36
|
+
};
|
|
37
|
+
return { llm, calls };
|
|
38
|
+
}
|
|
39
|
+
describe("withCouncilSignal — injects the council abort signal into generate", () => {
|
|
40
|
+
it("forwards the wrapper signal when the caller passes none", async () => {
|
|
41
|
+
const { llm, calls } = recordingLlm();
|
|
42
|
+
const ac = new AbortController();
|
|
43
|
+
const wrapped = withCouncilSignal(llm, ac.signal);
|
|
44
|
+
// tracedGenerate / openingWithRetry / judgeReadiness all call generate with
|
|
45
|
+
// <= 4 positional args — i.e. no signal. The wrapper must inject ours.
|
|
46
|
+
await wrapped.generate("m", "sys", "prompt", 256);
|
|
47
|
+
expect(calls).toHaveLength(1);
|
|
48
|
+
expect(calls[0].signal).toBe(ac.signal);
|
|
49
|
+
});
|
|
50
|
+
it("lets an explicit caller signal win over the injected one", async () => {
|
|
51
|
+
const { llm, calls } = recordingLlm();
|
|
52
|
+
const injected = new AbortController().signal;
|
|
53
|
+
const explicit = new AbortController().signal;
|
|
54
|
+
const wrapped = withCouncilSignal(llm, injected);
|
|
55
|
+
await wrapped.generate("m", "sys", "prompt", 256, undefined, explicit);
|
|
56
|
+
expect(calls[0].signal).toBe(explicit);
|
|
57
|
+
});
|
|
58
|
+
it("is a no-op passthrough when no signal is configured (sprint path)", () => {
|
|
59
|
+
const { llm } = recordingLlm();
|
|
60
|
+
expect(withCouncilSignal(llm, undefined)).toBe(llm);
|
|
61
|
+
});
|
|
62
|
+
});
|
|
63
|
+
describe("createCouncilLLM.generate — honours the parent abort signal", () => {
|
|
64
|
+
beforeEach(() => vi.resetModules());
|
|
65
|
+
it("propagates an already-aborted parent into the SDK abortSignal", async () => {
|
|
66
|
+
const captured = [];
|
|
67
|
+
vi.doMock("ai", () => ({
|
|
68
|
+
generateText: vi.fn().mockImplementation(async (args) => {
|
|
69
|
+
captured.push(args);
|
|
70
|
+
return { text: "done", usage: { inputTokens: 1, outputTokens: 1 }, finishReason: "stop" };
|
|
71
|
+
}),
|
|
72
|
+
stepCountIs: vi.fn().mockReturnValue({ __step: 1 }),
|
|
73
|
+
}));
|
|
74
|
+
vi.doMock("../../providers/keychain.js", () => ({
|
|
75
|
+
loadKeyForProvider: vi.fn().mockResolvedValue("test-key"),
|
|
76
|
+
}));
|
|
77
|
+
vi.doMock("../../providers/runtime.js", () => ({
|
|
78
|
+
detectProviderForModel: vi.fn().mockReturnValue("openai"),
|
|
79
|
+
createProviderFactory: vi.fn().mockReturnValue({ factory: {} }),
|
|
80
|
+
createProviderFactoryAsync: vi.fn().mockResolvedValue({ factory: {} }),
|
|
81
|
+
resolveModelRuntime: vi.fn().mockReturnValue({ model: {}, providerOptions: undefined }),
|
|
82
|
+
}));
|
|
83
|
+
const { createCouncilLLM } = await import("../llm.js");
|
|
84
|
+
const stats = { calls: 0, startMs: Date.now(), phases: [] };
|
|
85
|
+
const llm = createCouncilLLM({}, "agent", undefined, stats);
|
|
86
|
+
const ac = new AbortController();
|
|
87
|
+
ac.abort(new Error("user pressed Esc"));
|
|
88
|
+
await llm.generate("gpt-4o", "sys", "prompt", 256, undefined, ac.signal);
|
|
89
|
+
expect(captured).toHaveLength(1);
|
|
90
|
+
const sig = captured[0].abortSignal;
|
|
91
|
+
expect(sig).toBeDefined();
|
|
92
|
+
expect(sig?.aborted).toBe(true);
|
|
93
|
+
});
|
|
94
|
+
it("does NOT abort the SDK call when the parent is live", async () => {
|
|
95
|
+
const captured = [];
|
|
96
|
+
vi.doMock("ai", () => ({
|
|
97
|
+
generateText: vi.fn().mockImplementation(async (args) => {
|
|
98
|
+
captured.push(args);
|
|
99
|
+
return { text: "done", usage: { inputTokens: 1, outputTokens: 1 }, finishReason: "stop" };
|
|
100
|
+
}),
|
|
101
|
+
stepCountIs: vi.fn().mockReturnValue({ __step: 1 }),
|
|
102
|
+
}));
|
|
103
|
+
vi.doMock("../../providers/keychain.js", () => ({
|
|
104
|
+
loadKeyForProvider: vi.fn().mockResolvedValue("test-key"),
|
|
105
|
+
}));
|
|
106
|
+
vi.doMock("../../providers/runtime.js", () => ({
|
|
107
|
+
detectProviderForModel: vi.fn().mockReturnValue("openai"),
|
|
108
|
+
createProviderFactory: vi.fn().mockReturnValue({ factory: {} }),
|
|
109
|
+
createProviderFactoryAsync: vi.fn().mockResolvedValue({ factory: {} }),
|
|
110
|
+
resolveModelRuntime: vi.fn().mockReturnValue({ model: {}, providerOptions: undefined }),
|
|
111
|
+
}));
|
|
112
|
+
const { createCouncilLLM } = await import("../llm.js");
|
|
113
|
+
const stats = { calls: 0, startMs: Date.now(), phases: [] };
|
|
114
|
+
const llm = createCouncilLLM({}, "agent", undefined, stats);
|
|
115
|
+
const ac = new AbortController();
|
|
116
|
+
await llm.generate("gpt-4o", "sys", "prompt", 256, undefined, ac.signal);
|
|
117
|
+
const sig = captured[0].abortSignal;
|
|
118
|
+
expect(sig?.aborted).toBe(false);
|
|
119
|
+
});
|
|
120
|
+
});
|
|
121
|
+
describe("evaluateResearchNeed — generate call inherits the wrapped signal", () => {
|
|
122
|
+
it("records the council signal on the underlying generate call", async () => {
|
|
123
|
+
const { evaluateResearchNeed } = await import("../debate.js");
|
|
124
|
+
const { llm, calls } = recordingLlm(() => '{"needsResearch": false}');
|
|
125
|
+
const ac = new AbortController();
|
|
126
|
+
const wrapped = withCouncilSignal(llm, ac.signal);
|
|
127
|
+
const spec = {
|
|
128
|
+
problemStatement: "x",
|
|
129
|
+
constraints: [],
|
|
130
|
+
successCriteria: [],
|
|
131
|
+
scope: "",
|
|
132
|
+
rawQA: [],
|
|
133
|
+
};
|
|
134
|
+
const gen = evaluateResearchNeed(spec, "leader-model", "ctx", wrapped, false);
|
|
135
|
+
let step = await gen.next();
|
|
136
|
+
while (!step.done)
|
|
137
|
+
step = await gen.next();
|
|
138
|
+
const genCalls = calls.filter((c) => c.kind === "generate");
|
|
139
|
+
expect(genCalls.length).toBeGreaterThan(0);
|
|
140
|
+
for (const c of genCalls)
|
|
141
|
+
expect(c.signal).toBe(ac.signal);
|
|
142
|
+
});
|
|
143
|
+
});
|
|
144
|
+
describe("planDebate — forwards signal to the direct generateObject attempt", () => {
|
|
145
|
+
beforeEach(() => vi.resetModules());
|
|
146
|
+
it("passes an abort-linked signal to generateObject", async () => {
|
|
147
|
+
const captured = [];
|
|
148
|
+
vi.doMock("ai", () => ({
|
|
149
|
+
generateObject: vi.fn().mockImplementation(async (args) => {
|
|
150
|
+
captured.push(args);
|
|
151
|
+
return {
|
|
152
|
+
object: {
|
|
153
|
+
intentSummary: "s",
|
|
154
|
+
stances: [
|
|
155
|
+
{ name: "A", lens: "l1" },
|
|
156
|
+
{ name: "B", lens: "l2" },
|
|
157
|
+
],
|
|
158
|
+
outputShape: { kind: "decision", sections: [{ key: "k", heading: "H", shape: "list" }], guardrails: [] },
|
|
159
|
+
},
|
|
160
|
+
};
|
|
161
|
+
}),
|
|
162
|
+
}));
|
|
163
|
+
vi.doMock("../../providers/keychain.js", () => ({
|
|
164
|
+
loadKeyForProvider: vi.fn().mockResolvedValue("test-key"),
|
|
165
|
+
}));
|
|
166
|
+
vi.doMock("../../providers/runtime.js", () => ({
|
|
167
|
+
detectProviderForModel: vi.fn().mockReturnValue("openai"),
|
|
168
|
+
createProviderFactory: vi.fn().mockReturnValue({ factory: {} }),
|
|
169
|
+
createProviderFactoryAsync: vi.fn().mockResolvedValue({ factory: {} }),
|
|
170
|
+
resolveModelRuntime: vi.fn().mockReturnValue({ model: {}, providerOptions: undefined }),
|
|
171
|
+
}));
|
|
172
|
+
const { planDebate } = await import("../debate-planner.js");
|
|
173
|
+
const spec = {
|
|
174
|
+
problemStatement: "x",
|
|
175
|
+
constraints: [],
|
|
176
|
+
successCriteria: [],
|
|
177
|
+
scope: "",
|
|
178
|
+
rawQA: [],
|
|
179
|
+
};
|
|
180
|
+
const ac = new AbortController();
|
|
181
|
+
ac.abort(new Error("user pressed Esc"));
|
|
182
|
+
const { llm } = recordingLlm();
|
|
183
|
+
const gen = planDebate(spec, "leader-model", llm, undefined, undefined, undefined, undefined, ac.signal);
|
|
184
|
+
let step = await gen.next();
|
|
185
|
+
while (!step.done)
|
|
186
|
+
step = await gen.next();
|
|
187
|
+
expect(captured).toHaveLength(1);
|
|
188
|
+
const sig = captured[0].abortSignal;
|
|
189
|
+
expect(sig).toBeDefined();
|
|
190
|
+
expect(sig?.aborted).toBe(true);
|
|
191
|
+
});
|
|
192
|
+
});
|
|
193
|
+
//# sourceMappingURL=abort-threading.test.js.map
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { describe, expect, it } from "vitest";
|
|
2
|
-
import { buildClarificationPrompt, buildReadinessJudgePrompt } from "../prompts.js";
|
|
2
|
+
import { buildClarificationPrompt, buildReadinessJudgePrompt, buildSpecSynthesisPrompt } from "../prompts.js";
|
|
3
3
|
// Guards the de-robotized askcard contract (2026-06-14). The council clarifier
|
|
4
4
|
// used to (a) force "AT LEAST 2 questions" → rambling/over-asking, (b) tell the
|
|
5
5
|
// model to OMIT a recommendation unless certain → unranked option lists, and
|
|
@@ -34,6 +34,26 @@ describe("buildClarificationPrompt — de-robotized askcard", () => {
|
|
|
34
34
|
expect(system).toMatch(/Output ONLY a JSON array/);
|
|
35
35
|
});
|
|
36
36
|
});
|
|
37
|
+
// Issue A — language drift. buildSpecSynthesisPrompt had NO language rule, so
|
|
38
|
+
// DeepSeek defaulted the brief to English even for a Vietnamese topic. Because
|
|
39
|
+
// the final synthesis detects its output language FROM this problemStatement,
|
|
40
|
+
// an English brief dragged the whole council off the user's language. The fix
|
|
41
|
+
// adds a mandatory "use the user's language" rule. These lock it in.
|
|
42
|
+
describe("buildSpecSynthesisPrompt — user-language rule (issue A)", () => {
|
|
43
|
+
const { system } = buildSpecSynthesisPrompt("Xây dựng REST API quản lý todo", "", [
|
|
44
|
+
{ question: "Auth?", answer: "Không cần" },
|
|
45
|
+
]);
|
|
46
|
+
it("instructs writing every field in the user's language, not defaulting to English", () => {
|
|
47
|
+
expect(system).toMatch(/Language Rule/i);
|
|
48
|
+
expect(system).toMatch(/SAME[\s\S]*language the user used/i);
|
|
49
|
+
expect(system).toMatch(/Do NOT default to[\s\S]*English/i);
|
|
50
|
+
});
|
|
51
|
+
it("keeps the mock-router substring so council-e2e fixtures still match", () => {
|
|
52
|
+
// tests/council/council-e2e.ts routes the spec-synthesis mock on this exact
|
|
53
|
+
// phrase — changing it silently diverges the e2e flow. Guard it.
|
|
54
|
+
expect(system).toContain("synthesizing a discussion brief");
|
|
55
|
+
});
|
|
56
|
+
});
|
|
37
57
|
describe("buildReadinessJudgePrompt — no JS string-concat leak", () => {
|
|
38
58
|
const { system } = buildReadinessJudgePrompt("Optimize queries", [], {
|
|
39
59
|
problemStatement: "p",
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|