muonroi-cli 1.7.2 → 1.8.1
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/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 +143 -6
- 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 +7 -4
- 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 +36 -1
- 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/flow/compaction/index.d.ts +1 -0
- package/dist/src/flow/compaction/index.js +4 -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 +59 -62
- 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 +13 -5
- package/dist/src/lsp/manager.test.js +41 -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/auto-setup.js +0 -8
- 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/self-verify-jobs.d.ts +1 -0
- package/dist/src/mcp/self-verify-jobs.js +1 -3
- package/dist/src/mcp/self-verify-runner.js +1 -1
- package/dist/src/models/catalog-gemini.test.js +12 -10
- package/dist/src/models/catalog.json +106 -38
- 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 +3 -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 +2 -0
- package/dist/src/orchestrator/__tests__/prompts.test.d.ts +1 -0
- package/dist/src/orchestrator/__tests__/prompts.test.js +88 -0
- package/dist/src/orchestrator/__tests__/stream-runner.test.js +29 -0
- package/dist/src/orchestrator/__tests__/sub-session-delegation.test.d.ts +1 -0
- package/dist/src/orchestrator/__tests__/sub-session-delegation.test.js +507 -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 +366 -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 +23 -5
- package/dist/src/orchestrator/message-processor.js +226 -2573
- package/dist/src/orchestrator/orchestrator.d.ts +11 -2
- package/dist/src/orchestrator/orchestrator.js +530 -43
- package/dist/src/orchestrator/pending-calls.js +2 -1
- package/dist/src/orchestrator/preprocessor.d.ts +13 -0
- package/dist/src/orchestrator/preprocessor.js +146 -0
- package/dist/src/orchestrator/prompts.d.ts +16 -0
- package/dist/src/orchestrator/prompts.js +130 -67
- 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-cap.d.ts +2 -0
- package/dist/src/orchestrator/sub-agent-cap.js +12 -4
- package/dist/src/orchestrator/sub-agent-model-tier.js +1 -1
- package/dist/src/orchestrator/subagent-compactor.d.ts +21 -0
- package/dist/src/orchestrator/subagent-compactor.js +148 -24
- package/dist/src/orchestrator/subagent-compactor.spec.js +249 -1
- 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 +153 -0
- package/dist/src/orchestrator/tool-engine.js +3138 -0
- package/dist/src/orchestrator/turn-runner-deps.d.ts +3 -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__/llm-classify.test.js +79 -1
- 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 +62 -7
- 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.d.ts +11 -0
- package/dist/src/pil/llm-classify.js +196 -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/gather.js +19 -5
- package/dist/src/product-loop/loop-driver.js +2 -2
- 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/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/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/self-qa/agentic-loop.js +30 -21
- 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__/hierarchical-session.test.d.ts +1 -0
- package/dist/src/storage/__tests__/hierarchical-session.test.js +161 -0
- package/dist/src/storage/__tests__/sweep-stale-pending.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/migrations.js +13 -1
- package/dist/src/storage/transcript-response-entry.test.js +18 -5
- package/dist/src/storage/transcript-view.js +7 -1
- package/dist/src/storage/transcript.d.ts +3 -0
- package/dist/src/storage/transcript.js +254 -73
- package/dist/src/storage/ui-interaction-log.d.ts +17 -0
- package/dist/src/storage/usage.js +22 -5
- 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 +66 -1
- 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 +6 -0
- package/dist/src/tools/registry.js +215 -13
- package/dist/src/types/index.d.ts +8 -2
- 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/expand.js +14 -1
- 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/__tests__/llm-deadline.test.js +16 -4
- package/dist/src/utils/__tests__/logger.test.d.ts +1 -0
- package/dist/src/utils/__tests__/logger.test.js +115 -0
- package/dist/src/utils/__tests__/settings-agent-first.test.d.ts +1 -0
- package/dist/src/utils/__tests__/settings-agent-first.test.js +48 -0
- package/dist/src/utils/install-manager.d.ts +1 -0
- package/dist/src/utils/install-manager.js +76 -7
- package/dist/src/utils/install-manager.test.js +43 -1
- package/dist/src/utils/llm-deadline.d.ts +9 -1
- package/dist/src/utils/llm-deadline.js +31 -2
- package/dist/src/utils/logger.d.ts +28 -0
- package/dist/src/utils/logger.js +171 -0
- 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 +36 -32
- package/dist/src/utils/settings.js +151 -123
- package/dist/src/utils/update-checker.test.js +5 -1
- package/package.json +1 -1
|
@@ -39,6 +39,11 @@ export function renderHeadlessChunk(chunk) {
|
|
|
39
39
|
const stderr = [`${color}${icon} ${label}\x1b[0m`, ...mediaLines].join("\n");
|
|
40
40
|
return { stderr: `${stderr}\n` };
|
|
41
41
|
}
|
|
42
|
+
case "structured_response":
|
|
43
|
+
// A respond_* terminal answer arrives ONLY as this chunk (never as
|
|
44
|
+
// `content`). Without this case it hit the no-op default below and the
|
|
45
|
+
// answer was silently dropped from `--format text` stdout.
|
|
46
|
+
return chunk.structuredResponse ? { stdout: `${formatStructuredResponseText(chunk.structuredResponse)}\n` } : {};
|
|
42
47
|
case "error":
|
|
43
48
|
return chunk.content ? { stderr: `\x1b[31m${chunk.content}\x1b[0m\n` } : {};
|
|
44
49
|
case "done":
|
|
@@ -52,6 +57,119 @@ export function renderHeadlessChunk(chunk) {
|
|
|
52
57
|
function truncate(text, max) {
|
|
53
58
|
return text.length <= max ? text : `${text.slice(0, max - 1)}…`;
|
|
54
59
|
}
|
|
60
|
+
/**
|
|
61
|
+
* Plain-text rendering of a respond_* terminal answer for headless `--format
|
|
62
|
+
* text`. Mirrors the per-taskType layout of {@link StructuredResponseView}
|
|
63
|
+
* (src/ui/components/structured-response-view.tsx) but emits flat text (no ANSI
|
|
64
|
+
* box-drawing) so the answer pipes cleanly. Falls back to the primary text
|
|
65
|
+
* field, then raw JSON, for taskTypes without a dedicated layout.
|
|
66
|
+
*/
|
|
67
|
+
export function formatStructuredResponseText(sr) {
|
|
68
|
+
const d = (sr.data ?? {});
|
|
69
|
+
const str = (v) => (typeof v === "string" ? v : "");
|
|
70
|
+
const arr = (v) => (Array.isArray(v) ? v : []);
|
|
71
|
+
switch (sr.taskType) {
|
|
72
|
+
case "general":
|
|
73
|
+
return str(d.response) || JSON.stringify(d, null, 2);
|
|
74
|
+
case "documentation": {
|
|
75
|
+
const examples = arr(d.examples);
|
|
76
|
+
const parts = [str(d.content)];
|
|
77
|
+
for (const ex of examples) {
|
|
78
|
+
if (ex.description)
|
|
79
|
+
parts.push(`\n${ex.description}`);
|
|
80
|
+
if (ex.code)
|
|
81
|
+
parts.push(ex.code);
|
|
82
|
+
}
|
|
83
|
+
const out = parts.filter(Boolean).join("\n");
|
|
84
|
+
return out || JSON.stringify(d, null, 2);
|
|
85
|
+
}
|
|
86
|
+
case "analyze": {
|
|
87
|
+
const findings = arr(d.findings);
|
|
88
|
+
if (findings.length === 0)
|
|
89
|
+
return JSON.stringify(d, null, 2);
|
|
90
|
+
return findings
|
|
91
|
+
.map((f) => {
|
|
92
|
+
const sev = (f.severity ?? "").toUpperCase();
|
|
93
|
+
const head = sev ? `[${sev}] ${f.text ?? ""}` : (f.text ?? "");
|
|
94
|
+
return f.evidence ? `${head}\n evidence: ${f.evidence}` : head;
|
|
95
|
+
})
|
|
96
|
+
.join("\n");
|
|
97
|
+
}
|
|
98
|
+
case "plan": {
|
|
99
|
+
const steps = arr(d.steps);
|
|
100
|
+
const lines = [];
|
|
101
|
+
steps.forEach((s, i) => {
|
|
102
|
+
lines.push(`${i + 1}. ${s.action ?? ""}`);
|
|
103
|
+
if (s.criterion)
|
|
104
|
+
lines.push(` done when: ${s.criterion}`);
|
|
105
|
+
if (s.rationale)
|
|
106
|
+
lines.push(` why: ${s.rationale}`);
|
|
107
|
+
});
|
|
108
|
+
const assumptions = arr(d.assumptions);
|
|
109
|
+
const risks = arr(d.risks);
|
|
110
|
+
if (assumptions.length > 0) {
|
|
111
|
+
lines.push("", "assumptions:");
|
|
112
|
+
for (const a of assumptions)
|
|
113
|
+
lines.push(` - ${a}`);
|
|
114
|
+
}
|
|
115
|
+
if (risks.length > 0) {
|
|
116
|
+
lines.push("", "risks:");
|
|
117
|
+
for (const rk of risks)
|
|
118
|
+
lines.push(` - ${rk}`);
|
|
119
|
+
}
|
|
120
|
+
return lines.length > 0 ? lines.join("\n") : JSON.stringify(d, null, 2);
|
|
121
|
+
}
|
|
122
|
+
case "debug": {
|
|
123
|
+
const r = d;
|
|
124
|
+
const lines = [];
|
|
125
|
+
if (r.hypothesis)
|
|
126
|
+
lines.push(`hypothesis: ${r.hypothesis}`);
|
|
127
|
+
if (r.root_cause)
|
|
128
|
+
lines.push(`root cause: ${r.root_cause}`);
|
|
129
|
+
if (r.fix?.file)
|
|
130
|
+
lines.push(`fix: ${r.fix.file}`);
|
|
131
|
+
if (r.fix?.diff)
|
|
132
|
+
lines.push(r.fix.diff);
|
|
133
|
+
if (r.verify_command)
|
|
134
|
+
lines.push(`verify: ${r.verify_command}`);
|
|
135
|
+
return lines.length > 0 ? lines.join("\n") : JSON.stringify(d, null, 2);
|
|
136
|
+
}
|
|
137
|
+
case "refactor": {
|
|
138
|
+
const r = d;
|
|
139
|
+
const lines = [];
|
|
140
|
+
if (r.summary)
|
|
141
|
+
lines.push(r.summary);
|
|
142
|
+
for (const c of r.changes ?? []) {
|
|
143
|
+
if (c.file)
|
|
144
|
+
lines.push(`\n── ${c.file} ──`);
|
|
145
|
+
if (c.diff)
|
|
146
|
+
lines.push(c.diff);
|
|
147
|
+
}
|
|
148
|
+
if (r.verify_command)
|
|
149
|
+
lines.push(`verify: ${r.verify_command}`);
|
|
150
|
+
return lines.length > 0 ? lines.join("\n") : JSON.stringify(d, null, 2);
|
|
151
|
+
}
|
|
152
|
+
case "generate": {
|
|
153
|
+
const r = d;
|
|
154
|
+
const lines = [];
|
|
155
|
+
if (r.explanation)
|
|
156
|
+
lines.push(r.explanation);
|
|
157
|
+
for (const f of r.files ?? []) {
|
|
158
|
+
const lang = f.language ? ` (${f.language})` : "";
|
|
159
|
+
if (f.path)
|
|
160
|
+
lines.push(`\n── ${f.path}${lang} ──`);
|
|
161
|
+
if (f.content)
|
|
162
|
+
lines.push(f.content);
|
|
163
|
+
}
|
|
164
|
+
return lines.length > 0 ? lines.join("\n") : JSON.stringify(d, null, 2);
|
|
165
|
+
}
|
|
166
|
+
default: {
|
|
167
|
+
// Renderer-missing taskType: probe common text-bearing fields, then JSON.
|
|
168
|
+
const primary = str(d.response) || str(d.summary) || str(d.content) || str(d.text);
|
|
169
|
+
return primary || JSON.stringify(d, null, 2);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
}
|
|
55
173
|
function formatToolCallLabel(tc) {
|
|
56
174
|
const name = tc.function.name;
|
|
57
175
|
try {
|
|
@@ -74,6 +192,45 @@ function formatToolCallLabel(tc) {
|
|
|
74
192
|
catch { }
|
|
75
193
|
return name;
|
|
76
194
|
}
|
|
195
|
+
/**
|
|
196
|
+
* Stateful headless TEXT consumer. Streams tool/error progress to stderr (via
|
|
197
|
+
* {@link renderHeadlessChunk}) but BUFFERS assistant `content` so that a
|
|
198
|
+
* terminal `respond_*` answer ({@link StructuredResponse}) supersedes any
|
|
199
|
+
* preamble the model leaked before calling the response tool — otherwise the
|
|
200
|
+
* answer would print twice (once as raw leaked content, once formatted). For a
|
|
201
|
+
* normal chat turn with no structured answer, the buffered content is flushed
|
|
202
|
+
* verbatim at the end. Mirrors the buffer-and-supersede design of
|
|
203
|
+
* {@link createHeadlessJsonlEmitter}.
|
|
204
|
+
*/
|
|
205
|
+
export function createHeadlessTextEmitter() {
|
|
206
|
+
let pendingContent = "";
|
|
207
|
+
let structuredEmitted = false;
|
|
208
|
+
function consumeChunk(chunk) {
|
|
209
|
+
switch (chunk.type) {
|
|
210
|
+
case "content":
|
|
211
|
+
pendingContent += chunk.content ?? "";
|
|
212
|
+
return {};
|
|
213
|
+
case "structured_response":
|
|
214
|
+
if (!chunk.structuredResponse)
|
|
215
|
+
return {};
|
|
216
|
+
// Terminal answer is authoritative — drop any buffered preamble.
|
|
217
|
+
pendingContent = "";
|
|
218
|
+
structuredEmitted = true;
|
|
219
|
+
return { stdout: `${formatStructuredResponseText(chunk.structuredResponse)}\n` };
|
|
220
|
+
case "done":
|
|
221
|
+
// Trailing newline is emitted by flush() alongside the final answer.
|
|
222
|
+
return {};
|
|
223
|
+
default:
|
|
224
|
+
return renderHeadlessChunk(chunk);
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
function flush() {
|
|
228
|
+
if (structuredEmitted || pendingContent.length === 0)
|
|
229
|
+
return {};
|
|
230
|
+
return { stdout: `${pendingContent}\n` };
|
|
231
|
+
}
|
|
232
|
+
return { consumeChunk, flush };
|
|
233
|
+
}
|
|
77
234
|
function jsonLine(event) {
|
|
78
235
|
return `${JSON.stringify(event)}\n`;
|
|
79
236
|
}
|
|
@@ -188,6 +345,29 @@ export function createHeadlessJsonlEmitter(sessionId) {
|
|
|
188
345
|
}
|
|
189
346
|
break;
|
|
190
347
|
}
|
|
348
|
+
case "structured_response": {
|
|
349
|
+
if (chunk.structuredResponse) {
|
|
350
|
+
// Flush any buffered preamble text first so ordering is preserved,
|
|
351
|
+
// then emit the typed terminal answer (previously dropped entirely).
|
|
352
|
+
if (textBuffer.length > 0) {
|
|
353
|
+
stdout += jsonLine(withSession({
|
|
354
|
+
type: "text",
|
|
355
|
+
stepNumber: currentStep,
|
|
356
|
+
text: textBuffer,
|
|
357
|
+
timestamp: Date.now(),
|
|
358
|
+
}));
|
|
359
|
+
textBuffer = "";
|
|
360
|
+
}
|
|
361
|
+
stdout += jsonLine(withSession({
|
|
362
|
+
type: "structured_response",
|
|
363
|
+
stepNumber: currentStep,
|
|
364
|
+
timestamp: Date.now(),
|
|
365
|
+
taskType: chunk.structuredResponse.taskType,
|
|
366
|
+
data: chunk.structuredResponse.data,
|
|
367
|
+
}));
|
|
368
|
+
}
|
|
369
|
+
break;
|
|
370
|
+
}
|
|
191
371
|
case "error":
|
|
192
372
|
stdout += jsonLine(withSession({
|
|
193
373
|
type: "error",
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { describe, expect, it } from "vitest";
|
|
2
|
-
import { createHeadlessJsonlEmitter, isHeadlessOutputFormat, renderHeadlessChunk, renderHeadlessPrelude, } from "./output.js";
|
|
2
|
+
import { createHeadlessJsonlEmitter, createHeadlessTextEmitter, formatStructuredResponseText, isHeadlessOutputFormat, renderHeadlessChunk, renderHeadlessPrelude, } from "./output.js";
|
|
3
3
|
function toolCall(name) {
|
|
4
4
|
return {
|
|
5
5
|
id: "tc-1",
|
|
@@ -175,5 +175,65 @@ describe("headless output helpers", () => {
|
|
|
175
175
|
});
|
|
176
176
|
expectSessionAndTimestamp(parsed, sessionId);
|
|
177
177
|
});
|
|
178
|
+
// Regression backstop for G5: a respond_* terminal answer arrives ONLY as a
|
|
179
|
+
// `structured_response` chunk. Both headless renderers used to drop it
|
|
180
|
+
// (no-op default branch), so `--format text` produced empty stdout and
|
|
181
|
+
// `--format json` emitted no answer payload on a preamble-free turn.
|
|
182
|
+
describe("structured_response (respond_* terminal answer)", () => {
|
|
183
|
+
const generalChunk = {
|
|
184
|
+
type: "structured_response",
|
|
185
|
+
structuredResponse: { taskType: "general", data: { response: "The answer." } },
|
|
186
|
+
};
|
|
187
|
+
it("renderHeadlessChunk renders the answer to stdout (no longer dropped)", () => {
|
|
188
|
+
const w = renderHeadlessChunk(generalChunk);
|
|
189
|
+
expect(w.stdout).toBe("The answer.\n");
|
|
190
|
+
expect(w.stderr).toBeUndefined();
|
|
191
|
+
});
|
|
192
|
+
it("formatStructuredResponseText lays out each taskType as flat text", () => {
|
|
193
|
+
expect(formatStructuredResponseText({
|
|
194
|
+
taskType: "analyze",
|
|
195
|
+
data: { findings: [{ text: "Bug X", evidence: "file:1", severity: "high" }] },
|
|
196
|
+
})).toBe("[HIGH] Bug X\n evidence: file:1");
|
|
197
|
+
expect(formatStructuredResponseText({
|
|
198
|
+
taskType: "plan",
|
|
199
|
+
data: { steps: [{ action: "Do A", criterion: "A done", rationale: "because" }] },
|
|
200
|
+
})).toBe("1. Do A\n done when: A done\n why: because");
|
|
201
|
+
// Unknown taskType falls back to a primary text field, then JSON.
|
|
202
|
+
expect(formatStructuredResponseText({ taskType: "mystery", data: { summary: "hi" } })).toBe("hi");
|
|
203
|
+
expect(formatStructuredResponseText({ taskType: "mystery", data: { n: 1 } })).toBe('{\n "n": 1\n}');
|
|
204
|
+
});
|
|
205
|
+
it("text emitter: a structured answer SUPERSEDES leaked preamble content (no duplicate)", () => {
|
|
206
|
+
const e = createHeadlessTextEmitter();
|
|
207
|
+
// Model leaks the raw answer as content, then delivers it via respond_*.
|
|
208
|
+
expect(e.consumeChunk({ type: "content", content: '```json\n{"x":1}\n```' }).stdout).toBeUndefined();
|
|
209
|
+
const out = e.consumeChunk(generalChunk).stdout;
|
|
210
|
+
expect(out).toBe("The answer.\n");
|
|
211
|
+
// Nothing buffered remains to flush — the answer is not printed twice.
|
|
212
|
+
expect(e.flush().stdout).toBeUndefined();
|
|
213
|
+
});
|
|
214
|
+
it("text emitter: a normal content-only turn flushes its buffered answer", () => {
|
|
215
|
+
const e = createHeadlessTextEmitter();
|
|
216
|
+
expect(e.consumeChunk({ type: "content", content: "PONG" }).stdout).toBeUndefined();
|
|
217
|
+
expect(e.consumeChunk({ type: "done" }).stdout).toBeUndefined();
|
|
218
|
+
expect(e.flush().stdout).toBe("PONG\n");
|
|
219
|
+
});
|
|
220
|
+
it("text emitter: tool/error progress still streams to stderr immediately", () => {
|
|
221
|
+
const e = createHeadlessTextEmitter();
|
|
222
|
+
const w = e.consumeChunk({ type: "tool_calls", toolCalls: [toolCall("bash")] });
|
|
223
|
+
expect(w.stderr).toContain("bash");
|
|
224
|
+
expect(w.stdout).toBeUndefined();
|
|
225
|
+
});
|
|
226
|
+
it("json emitter: emits a typed structured_response event carrying the payload", () => {
|
|
227
|
+
const { consumeChunk } = createHeadlessJsonlEmitter("sess-sr");
|
|
228
|
+
const out = consumeChunk(generalChunk).stdout ?? "";
|
|
229
|
+
const events = out
|
|
230
|
+
.trim()
|
|
231
|
+
.split("\n")
|
|
232
|
+
.map((l) => JSON.parse(l));
|
|
233
|
+
const sr = events.find((ev) => ev.type === "structured_response");
|
|
234
|
+
expect(sr).toMatchObject({ type: "structured_response", taskType: "general", data: { response: "The answer." } });
|
|
235
|
+
expectSessionAndTimestamp(sr, "sess-sr");
|
|
236
|
+
});
|
|
237
|
+
});
|
|
178
238
|
});
|
|
179
239
|
//# sourceMappingURL=output.test.js.map
|
package/dist/src/hooks/index.js
CHANGED
|
@@ -33,10 +33,14 @@ let _cachedScope = null;
|
|
|
33
33
|
// Latch: stores the last PreToolUse warning response so PostToolUse can build judgeCtx.
|
|
34
34
|
// Follows the _cachedScope module-level variable pattern.
|
|
35
35
|
let _lastWarningResponse = null;
|
|
36
|
+
// Fix 2: throttle recall-feedback reminder to once per unique pending-set.
|
|
37
|
+
// Tracks the sha of pending IDs so identical reminders don't repeat on every tool call.
|
|
38
|
+
let _lastRecallReminderSha = null;
|
|
36
39
|
/** Reset hook module state — for test teardown only. */
|
|
37
40
|
export function resetHookState() {
|
|
38
41
|
_lastWarningResponse = null;
|
|
39
42
|
_cachedScope = null;
|
|
43
|
+
_lastRecallReminderSha = null;
|
|
40
44
|
}
|
|
41
45
|
function emptyResult() {
|
|
42
46
|
return {
|
|
@@ -200,13 +204,51 @@ export async function executeEventHooks(input, cwd, _signal) {
|
|
|
200
204
|
why: m.why,
|
|
201
205
|
confidence: m.confidence,
|
|
202
206
|
}));
|
|
207
|
+
// Hard EE recall-feedback gate: inject mandatory reminder when there are
|
|
208
|
+
// pending unrated hints. The agent MUST clear all pending ee_feedback before
|
|
209
|
+
// proceeding with the task — system prompt in message-processor.ts enforces
|
|
210
|
+
// this as a first-before-anything directive.
|
|
211
|
+
//
|
|
212
|
+
// Fix 2: throttle to once per unique pending-set. The same 9 hints repeating
|
|
213
|
+
// after each of 10+ tool calls is pure UI noise — the model can't even see
|
|
214
|
+
// yield-content, and the user must scroll past identical blocks. Show the
|
|
215
|
+
// reminder ONCE when the set first appears or changes (e.g. after ee_feedback
|
|
216
|
+
// clears some entries), then stay silent until the set changes again.
|
|
217
|
+
let recallReminder = null;
|
|
218
|
+
try {
|
|
219
|
+
const { sessionRecallLedger, isRecallLedgerEnabled, formatPendingReminder } = await import("../ee/recall-ledger.js");
|
|
220
|
+
if (isRecallLedgerEnabled()) {
|
|
221
|
+
const pending = sessionRecallLedger.pending();
|
|
222
|
+
if (pending.length > 0) {
|
|
223
|
+
const pendingSha = pending
|
|
224
|
+
.map((p) => p.id)
|
|
225
|
+
.sort()
|
|
226
|
+
.join(",");
|
|
227
|
+
if (pendingSha !== _lastRecallReminderSha) {
|
|
228
|
+
_lastRecallReminderSha = pendingSha;
|
|
229
|
+
recallReminder = `⚠️ MANDATORY: ${pending.length} unrated EE recall(s) — call ee_feedback NOW before continuing.\n${formatPendingReminder(pending, { max: 5 })}`;
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
else {
|
|
233
|
+
// All cleared — reset so next batch shows fresh
|
|
234
|
+
_lastRecallReminderSha = null;
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
catch {
|
|
239
|
+
/* fail-open */
|
|
240
|
+
}
|
|
203
241
|
if (r.decision === "block") {
|
|
204
242
|
return {
|
|
205
243
|
blocked: true,
|
|
206
244
|
blockingErrors: [{ command: "ee:intercept", stderr: r.reason ?? "ee-blocked" }],
|
|
207
245
|
preventContinuation: true,
|
|
208
246
|
stopReason: r.reason ?? "ee-blocked",
|
|
209
|
-
additionalContexts: [
|
|
247
|
+
additionalContexts: [
|
|
248
|
+
...capturedWarnings,
|
|
249
|
+
...(r.suggestions ?? []),
|
|
250
|
+
...(recallReminder ? [recallReminder] : []),
|
|
251
|
+
],
|
|
210
252
|
decision: "block",
|
|
211
253
|
results: [],
|
|
212
254
|
eeMatches,
|
|
@@ -216,7 +258,11 @@ export async function executeEventHooks(input, cwd, _signal) {
|
|
|
216
258
|
blocked: false,
|
|
217
259
|
blockingErrors: [],
|
|
218
260
|
preventContinuation: false,
|
|
219
|
-
additionalContexts: [
|
|
261
|
+
additionalContexts: [
|
|
262
|
+
...capturedWarnings,
|
|
263
|
+
...(r.suggestions ?? []),
|
|
264
|
+
...(recallReminder ? [recallReminder] : []),
|
|
265
|
+
],
|
|
220
266
|
decision: "approve",
|
|
221
267
|
results: [],
|
|
222
268
|
eeMatches,
|
package/dist/src/index.d.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env bun
|
|
2
2
|
import type { StreamChunk } from "./types/index.js";
|
|
3
|
+
export declare function setTuiActive(active: boolean): void;
|
|
4
|
+
export declare function appendCrashLog(label: string, msg: string): void;
|
|
3
5
|
export declare function setActiveEeYield(fn: ((chunk: StreamChunk) => void) | null): void;
|
package/dist/src/index.js
CHANGED
|
@@ -17,7 +17,7 @@ const packageJson = { version: PACKAGE_VERSION, description: PACKAGE_DESCRIPTION
|
|
|
17
17
|
import { hydrateChatEnvFromKeychain } from "./chat/chat-keychain.js";
|
|
18
18
|
import { setRenderSink } from "./ee/render.js";
|
|
19
19
|
import { createHeadlessCouncilAutoAnswerer, handleCouncilChunk, parseCouncilAnswersFile, } from "./headless/council-answers.js";
|
|
20
|
-
import { createHeadlessJsonlEmitter,
|
|
20
|
+
import { createHeadlessJsonlEmitter, createHeadlessTextEmitter, isHeadlessOutputFormat, renderHeadlessPrelude, } from "./headless/output.js";
|
|
21
21
|
import { loadCatalog, normalizeModelId } from "./models/registry.js";
|
|
22
22
|
// Plan 00-07: boot-order modules — AbortContext + PendingCallsLog (TUI-01, TUI-03, TUI-04).
|
|
23
23
|
import { createAbortContext } from "./orchestrator/abort.js";
|
|
@@ -32,7 +32,7 @@ import { loadUsage } from "./storage/usage-cap.js";
|
|
|
32
32
|
import { startScheduleDaemon } from "./tools/schedule.js";
|
|
33
33
|
import { processAtMentions } from "./utils/at-mentions.js";
|
|
34
34
|
import { runScriptManagedUninstall } from "./utils/install-manager.js";
|
|
35
|
-
import { getApiKey, getBaseURL, getCurrentModel,
|
|
35
|
+
import { getApiKey, getBaseURL, getCurrentModel, saveUserSettings } from "./utils/settings.js";
|
|
36
36
|
import { runUpdate } from "./utils/update-checker.js";
|
|
37
37
|
import { buildVerifyPrompt, getVerifyCliError } from "./verify/entrypoint.js";
|
|
38
38
|
// Hydrate chat secrets from OS keychain before CLI bootstrap
|
|
@@ -41,20 +41,41 @@ const exitCleanlyOnSigterm = () => {
|
|
|
41
41
|
process.exit(0);
|
|
42
42
|
};
|
|
43
43
|
process.on("SIGTERM", exitCleanlyOnSigterm);
|
|
44
|
-
|
|
44
|
+
// Set true while the interactive TUI owns the terminal (raw mode + alternate
|
|
45
|
+
// screen). When it is mounted, writing to stdout/stderr corrupts OpenTUI's
|
|
46
|
+
// framebuffer and a process.exit(1) tears the whole UI down — surfacing to the
|
|
47
|
+
// user as "kicked out of the TUI". A single stray unhandledRejection (e.g. a
|
|
48
|
+
// slash handler whose DB read throws and rejects with no .catch) must not be
|
|
49
|
+
// allowed to do that; while _tuiActive is set we log to crash.log and keep the
|
|
50
|
+
// process alive. Headless / CLI paths keep the original fail-fast behaviour.
|
|
51
|
+
let _tuiActive = false;
|
|
52
|
+
export function setTuiActive(active) {
|
|
53
|
+
_tuiActive = active;
|
|
54
|
+
globalThis.__muonroiTuiActive = active;
|
|
55
|
+
}
|
|
56
|
+
export function appendCrashLog(label, msg) {
|
|
45
57
|
try {
|
|
46
|
-
require("fs").appendFileSync(require("path").join(require("os").homedir(), ".muonroi-cli", "crash.log"), `[${new Date().toISOString()}]
|
|
58
|
+
require("fs").appendFileSync(require("path").join(require("os").homedir(), ".muonroi-cli", "crash.log"), `[${new Date().toISOString()}] ${label}: ${msg}\n`);
|
|
59
|
+
}
|
|
60
|
+
catch {
|
|
61
|
+
/* crash.log is best-effort diagnostics; the logger itself must never throw */
|
|
47
62
|
}
|
|
48
|
-
|
|
63
|
+
}
|
|
64
|
+
process.on("uncaughtException", (err) => {
|
|
65
|
+
appendCrashLog("UNCAUGHT", err.stack || err.message);
|
|
49
66
|
console.error("Fatal:", err.message);
|
|
50
67
|
process.exit(1);
|
|
51
68
|
});
|
|
52
69
|
process.on("unhandledRejection", (reason) => {
|
|
53
70
|
const msg = reason instanceof Error ? reason.stack || reason.message : String(reason);
|
|
54
|
-
|
|
55
|
-
|
|
71
|
+
appendCrashLog("REJECTION", msg);
|
|
72
|
+
// TUI mounted → do NOT corrupt the framebuffer with console.error and do NOT
|
|
73
|
+
// exit. The rejection is logged; the renderer stays up so the user keeps
|
|
74
|
+
// their session (see slash-dispatch .catch handlers in app.tsx, which also
|
|
75
|
+
// surface the failure in-band).
|
|
76
|
+
if (_tuiActive) {
|
|
77
|
+
return;
|
|
56
78
|
}
|
|
57
|
-
catch { }
|
|
58
79
|
if (reason instanceof Error) {
|
|
59
80
|
console.error("Unhandled rejection:", reason.stack || reason.message);
|
|
60
81
|
}
|
|
@@ -314,7 +335,7 @@ async function firstRunWizard(currentModel) {
|
|
|
314
335
|
return null;
|
|
315
336
|
}
|
|
316
337
|
}
|
|
317
|
-
async function startInteractive(apiKey, baseURL, model, maxToolRounds, batchApi,
|
|
338
|
+
async function startInteractive(apiKey, baseURL, model, maxToolRounds, batchApi, session, initialMessage, permissionMode = "safe", injectHalt = false) {
|
|
318
339
|
// ── Plan 00-07 boot order ──────────────────────────────────────────────────
|
|
319
340
|
// 1. redactor.installGlobalPatches() — already at top of file (line 6).
|
|
320
341
|
// 2. loadConfig + loadUsage (validates storage paths, logs usage cap state).
|
|
@@ -377,10 +398,11 @@ async function startInteractive(apiKey, baseURL, model, maxToolRounds, batchApi,
|
|
|
377
398
|
if (reconciled.abandoned > 0) {
|
|
378
399
|
console.warn(`[muonroi-cli] reconciled ${reconciled.abandoned} abandoned tool calls from prior session`);
|
|
379
400
|
}
|
|
401
|
+
const { warmMcpClients } = await import("./mcp/client-pool.js");
|
|
402
|
+
const { loadMcpServers } = await import("./utils/settings.js");
|
|
403
|
+
await warmMcpClients(loadMcpServers(), !session);
|
|
380
404
|
const agent = new Agent(apiKey, baseURL, model, maxToolRounds, {
|
|
381
405
|
session,
|
|
382
|
-
sandboxMode,
|
|
383
|
-
sandboxSettings,
|
|
384
406
|
batchApi,
|
|
385
407
|
abortContext: orchestratorAbort,
|
|
386
408
|
pendingCalls,
|
|
@@ -404,6 +426,10 @@ async function startInteractive(apiKey, baseURL, model, maxToolRounds, batchApi,
|
|
|
404
426
|
alternateKeys: true,
|
|
405
427
|
},
|
|
406
428
|
});
|
|
429
|
+
// The TUI now owns the terminal — route fatal-handler behaviour away from
|
|
430
|
+
// process.exit(1) (see the unhandledRejection handler above). Cleared in
|
|
431
|
+
// restoreTerminalSync() the moment we begin tearing the terminal back down.
|
|
432
|
+
setTuiActive(true);
|
|
407
433
|
/**
|
|
408
434
|
* Restore terminal to main-screen mode before the process exits.
|
|
409
435
|
*
|
|
@@ -418,6 +444,9 @@ async function startInteractive(apiKey, baseURL, model, maxToolRounds, batchApi,
|
|
|
418
444
|
* flush delay before process.exit().
|
|
419
445
|
*/
|
|
420
446
|
function restoreTerminalSync() {
|
|
447
|
+
// Terminal is being handed back — restore fail-fast crash behaviour so a
|
|
448
|
+
// throw during/after teardown still exits cleanly instead of hanging.
|
|
449
|
+
setTuiActive(false);
|
|
421
450
|
try {
|
|
422
451
|
// 1. Disable Kitty keyboard protocol if enabled.
|
|
423
452
|
if (typeof renderer.disableKittyKeyboard === "function") {
|
|
@@ -582,9 +611,9 @@ async function startInteractive(apiKey, baseURL, model, maxToolRounds, batchApi,
|
|
|
582
611
|
apiKey,
|
|
583
612
|
baseURL,
|
|
584
613
|
model: agent.getModel(),
|
|
614
|
+
sandboxMode: "off",
|
|
615
|
+
sandboxSettings: {},
|
|
585
616
|
maxToolRounds,
|
|
586
|
-
sandboxMode,
|
|
587
|
-
sandboxSettings,
|
|
588
617
|
version: packageJson.version,
|
|
589
618
|
injectHalt,
|
|
590
619
|
},
|
|
@@ -593,11 +622,9 @@ async function startInteractive(apiKey, baseURL, model, maxToolRounds, batchApi,
|
|
|
593
622
|
onRelaunch,
|
|
594
623
|
}));
|
|
595
624
|
}
|
|
596
|
-
async function runHeadless(prompt, apiKey, baseURL, model, maxToolRounds, batchApi,
|
|
625
|
+
async function runHeadless(prompt, apiKey, baseURL, model, maxToolRounds, batchApi, format, session, permissionMode = "safe", councilAutoAnswer) {
|
|
597
626
|
const agent = new Agent(apiKey, baseURL, model, maxToolRounds, {
|
|
598
627
|
session,
|
|
599
|
-
sandboxMode,
|
|
600
|
-
sandboxSettings,
|
|
601
628
|
batchApi,
|
|
602
629
|
permissionMode,
|
|
603
630
|
});
|
|
@@ -647,14 +674,20 @@ async function runHeadless(prompt, apiKey, baseURL, model, maxToolRounds, batchA
|
|
|
647
674
|
writeSafe(process.stderr, tail.stderr ?? "");
|
|
648
675
|
return;
|
|
649
676
|
}
|
|
677
|
+
const textEmitter = createHeadlessTextEmitter();
|
|
650
678
|
for await (const chunk of agent.processMessage(enhancedMessage)) {
|
|
651
679
|
maybeAutoAnswer(chunk);
|
|
652
|
-
const writes =
|
|
680
|
+
const writes = textEmitter.consumeChunk(chunk);
|
|
653
681
|
if (writes.stdout)
|
|
654
682
|
writeSafe(process.stdout, writes.stdout);
|
|
655
683
|
if (writes.stderr)
|
|
656
684
|
writeSafe(process.stderr, writes.stderr);
|
|
657
685
|
}
|
|
686
|
+
const textTail = textEmitter.flush();
|
|
687
|
+
if (textTail.stdout)
|
|
688
|
+
writeSafe(process.stdout, textTail.stdout);
|
|
689
|
+
if (textTail.stderr)
|
|
690
|
+
writeSafe(process.stderr, textTail.stderr ?? "");
|
|
658
691
|
}
|
|
659
692
|
finally {
|
|
660
693
|
await agent.cleanup();
|
|
@@ -690,13 +723,6 @@ function stringOption(value) {
|
|
|
690
723
|
function collect(value, prev) {
|
|
691
724
|
return [...prev, value];
|
|
692
725
|
}
|
|
693
|
-
function resolveCliSandboxMode(value) {
|
|
694
|
-
if (value === true)
|
|
695
|
-
return "shuru";
|
|
696
|
-
if (value === false)
|
|
697
|
-
return "off";
|
|
698
|
-
return undefined;
|
|
699
|
-
}
|
|
700
726
|
async function runBackgroundDelegation(jobPath, options) {
|
|
701
727
|
let output = "";
|
|
702
728
|
let agent;
|
|
@@ -724,12 +750,8 @@ async function runBackgroundDelegation(jobPath, options) {
|
|
|
724
750
|
"or run 'muonroi-cli keys login <provider>' for subscription OAuth.");
|
|
725
751
|
}
|
|
726
752
|
const maxToolRounds = parseInt(stringOption(options.maxToolRounds) || String(delegation.maxToolRounds), 10) || delegation.maxToolRounds;
|
|
727
|
-
const sandboxMode = resolveCliSandboxMode(options.sandbox) || delegation.sandboxMode || getCurrentSandboxMode();
|
|
728
|
-
const sandboxSettings = mergeSandboxSettings(getCurrentSandboxSettings(), delegation.sandboxSettings);
|
|
729
753
|
agent = new Agent(apiKey, baseURL, model, maxToolRounds, {
|
|
730
754
|
persistSession: false,
|
|
731
|
-
sandboxMode,
|
|
732
|
-
sandboxSettings,
|
|
733
755
|
batchApi: Boolean(delegation.batchApi ?? options.batchApi === true),
|
|
734
756
|
});
|
|
735
757
|
const result = await agent.runTaskRequest({
|
|
@@ -778,22 +800,7 @@ function resolveConfig(options) {
|
|
|
778
800
|
// work; runaway loops abort with the "Reached max tool rounds" error
|
|
779
801
|
// surface so the user can adjust scope. Override via --max-tool-rounds CLI
|
|
780
802
|
// flag or MUONROI_MAX_TOOL_ROUNDS env.
|
|
781
|
-
const maxToolRounds = parseInt(stringOption(options.maxToolRounds) || "
|
|
782
|
-
const sandboxMode = resolveCliSandboxMode(options.sandbox) || getCurrentSandboxMode();
|
|
783
|
-
const cliOverrides = {};
|
|
784
|
-
if (options.allowNet === true)
|
|
785
|
-
cliOverrides.allowNet = true;
|
|
786
|
-
const allowHostValue = options.allowHost;
|
|
787
|
-
if (Array.isArray(allowHostValue) && allowHostValue.length > 0) {
|
|
788
|
-
cliOverrides.allowedHosts = allowHostValue;
|
|
789
|
-
if (!cliOverrides.allowNet)
|
|
790
|
-
cliOverrides.allowNet = true;
|
|
791
|
-
}
|
|
792
|
-
const portValue = options.port;
|
|
793
|
-
if (Array.isArray(portValue) && portValue.length > 0) {
|
|
794
|
-
cliOverrides.ports = portValue;
|
|
795
|
-
}
|
|
796
|
-
const sandboxSettings = mergeSandboxSettings(getCurrentSandboxSettings(), cliOverrides);
|
|
803
|
+
const maxToolRounds = parseInt(stringOption(options.maxToolRounds) || "100", 10) || 100;
|
|
797
804
|
if (typeof options.apiKey === "string" && process.env.MUONROI_TEST_NO_PERSIST !== "1") {
|
|
798
805
|
// Persist to OS keychain (per-provider) instead of plaintext settings.json.
|
|
799
806
|
// Fire-and-forget: keychain write is async; if it fails (no keytar), the key still
|
|
@@ -802,13 +809,7 @@ function resolveConfig(options) {
|
|
|
802
809
|
}
|
|
803
810
|
if (typeof options.model === "string")
|
|
804
811
|
saveUserSettings({ defaultModel: normalizeModelId(options.model) });
|
|
805
|
-
|
|
806
|
-
const resolved = resolveCliSandboxMode(options.sandbox);
|
|
807
|
-
if (resolved) {
|
|
808
|
-
saveUserSettings({ sandboxMode: resolved });
|
|
809
|
-
}
|
|
810
|
-
}
|
|
811
|
-
return { apiKey, baseURL, model, maxToolRounds, sandboxMode, sandboxSettings };
|
|
812
|
+
return { apiKey, baseURL, model, maxToolRounds };
|
|
812
813
|
}
|
|
813
814
|
function requireApiKey(apiKey) {
|
|
814
815
|
if (!apiKey) {
|
|
@@ -835,11 +836,6 @@ program
|
|
|
835
836
|
.option("-p, --prompt <prompt>", "Run a single prompt headlessly")
|
|
836
837
|
.option("--verify", "Run the built-in verify flow headlessly")
|
|
837
838
|
.option("--format <format>", "Headless output format: text or json", parseHeadlessOutputFormat, "text")
|
|
838
|
-
.option("--sandbox", "Run agent shell commands inside a Shuru sandbox")
|
|
839
|
-
.option("--no-sandbox", "Run agent shell commands directly on the host")
|
|
840
|
-
.option("--allow-net", "Enable network access inside the Shuru sandbox")
|
|
841
|
-
.option("--allow-host <pattern>", "Restrict sandbox network to specific hosts (repeatable)", collect, [])
|
|
842
|
-
.option("--port <mapping>", "Forward a host port to sandbox guest (HOST:GUEST, repeatable)", collect, [])
|
|
843
839
|
.option("-s, --session <id>", "Continue a saved session by id, or use 'latest'")
|
|
844
840
|
.option("--background-task-file <path>", "Run a persisted background delegation")
|
|
845
841
|
.option("--max-tool-rounds <n>", "Max tool execution rounds (ultimate runaway safety net)", "120")
|
|
@@ -977,7 +973,8 @@ program
|
|
|
977
973
|
console.log("Checking for updates...");
|
|
978
974
|
const result = await runUpdate(packageJson.version);
|
|
979
975
|
console.log(result.output);
|
|
980
|
-
process.
|
|
976
|
+
process.exitCode = result.success ? 0 : 1;
|
|
977
|
+
return;
|
|
981
978
|
}
|
|
982
979
|
changeDirectoryOrExit(options.directory);
|
|
983
980
|
// Boot model registry BEFORE any key resolution path runs —
|
|
@@ -1125,15 +1122,15 @@ program
|
|
|
1125
1122
|
console.error(verifyError);
|
|
1126
1123
|
process.exit(1);
|
|
1127
1124
|
}
|
|
1128
|
-
await runHeadless(buildVerifyPrompt(process.cwd()
|
|
1125
|
+
await runHeadless(buildVerifyPrompt(process.cwd()), requireApiKey(config.apiKey), config.baseURL, config.model, config.maxToolRounds, options.batchApi === true, options.format, options.session, options.permission, councilAutoAnswer);
|
|
1129
1126
|
return;
|
|
1130
1127
|
}
|
|
1131
1128
|
if (options.prompt) {
|
|
1132
|
-
await runHeadless(options.prompt, requireApiKey(config.apiKey), config.baseURL, config.model, config.maxToolRounds, options.batchApi === true,
|
|
1129
|
+
await runHeadless(options.prompt, requireApiKey(config.apiKey), config.baseURL, config.model, config.maxToolRounds, options.batchApi === true, options.format, options.session, options.permission, councilAutoAnswer);
|
|
1133
1130
|
return;
|
|
1134
1131
|
}
|
|
1135
1132
|
const initialMessage = message.length > 0 ? message.join(" ") : undefined;
|
|
1136
|
-
await startInteractive(config.apiKey, config.baseURL, config.model, config.maxToolRounds, options.batchApi === true,
|
|
1133
|
+
await startInteractive(config.apiKey, config.baseURL, config.model, config.maxToolRounds, options.batchApi === true, options.session, initialMessage, options.permission, options.injectHalt === true);
|
|
1137
1134
|
});
|
|
1138
1135
|
program
|
|
1139
1136
|
.command("models")
|
|
@@ -1164,7 +1161,7 @@ program
|
|
|
1164
1161
|
console.log("Checking for updates...");
|
|
1165
1162
|
const result = await runUpdate(packageJson.version);
|
|
1166
1163
|
console.log(result.output);
|
|
1167
|
-
process.
|
|
1164
|
+
process.exitCode = result.success ? 0 : 1;
|
|
1168
1165
|
});
|
|
1169
1166
|
program
|
|
1170
1167
|
.command("uninstall")
|
|
@@ -1257,7 +1254,7 @@ keys
|
|
|
1257
1254
|
});
|
|
1258
1255
|
keys
|
|
1259
1256
|
.command("login <provider>")
|
|
1260
|
-
.description("Log in to a provider via OAuth subscription (supported: openai, google, xai)")
|
|
1257
|
+
.description("Log in to a provider via OAuth subscription (supported: openai, google/agy, xai). 'google'/'agy' now uses agy client creds.")
|
|
1261
1258
|
.action(async (provider) => {
|
|
1262
1259
|
const { runKeysLogin } = await import("./cli/keys.js");
|
|
1263
1260
|
await runKeysLogin(provider);
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* src/lsp/client-normalize.test.ts
|
|
3
|
+
*
|
|
4
|
+
* Regression guard for the Windows diagnostics-cache key mismatch: the LSP
|
|
5
|
+
* client stores publishDiagnostics under normalizeUriPath(uri) (a LOWERCASE
|
|
6
|
+
* drive, decoded from `file:///c%3A/…`) but retrieves under
|
|
7
|
+
* normalizeFsPath(path.resolve(...)) (the cwd's UPPERCASE drive). Without
|
|
8
|
+
* canonicalizing the drive letter the keys never matched, so diagnostics were
|
|
9
|
+
* permanently [] on Windows — silently breaking write-time LSP feedback AND the
|
|
10
|
+
* commit gate. normalizeFsPath must produce the SAME key for both casings.
|
|
11
|
+
*/
|
|
12
|
+
export {};
|