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
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { spawn } from "child_process";
|
|
1
|
+
import { exec, spawn } from "child_process";
|
|
2
2
|
import { createHash } from "crypto";
|
|
3
3
|
import fs from "fs";
|
|
4
4
|
import os from "os";
|
|
@@ -83,6 +83,34 @@ export function getScriptInstallContext(homeDir = os.homedir()) {
|
|
|
83
83
|
}
|
|
84
84
|
return null;
|
|
85
85
|
}
|
|
86
|
+
export function fetchLatestGitTag(gitDir) {
|
|
87
|
+
return new Promise((resolve) => {
|
|
88
|
+
exec(`git -C "${gitDir}" ls-remote --tags origin`, (error, stdout) => {
|
|
89
|
+
if (error) {
|
|
90
|
+
resolve(null);
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
const lines = stdout.split(/\r?\n/);
|
|
94
|
+
let maxVersion = null;
|
|
95
|
+
for (const line of lines) {
|
|
96
|
+
const match = line.match(/refs\/tags\/(v?[0-9]+\.[0-9]+\.[0-9]+[^\s]*)$/);
|
|
97
|
+
if (!match)
|
|
98
|
+
continue;
|
|
99
|
+
let tag = match[1];
|
|
100
|
+
if (tag.endsWith("^{}")) {
|
|
101
|
+
tag = tag.slice(0, -3);
|
|
102
|
+
}
|
|
103
|
+
const version = normalizeReleaseVersion(tag);
|
|
104
|
+
if (!version)
|
|
105
|
+
continue;
|
|
106
|
+
if (!maxVersion || semverGt(version, maxVersion)) {
|
|
107
|
+
maxVersion = version;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
resolve(maxVersion);
|
|
111
|
+
});
|
|
112
|
+
});
|
|
113
|
+
}
|
|
86
114
|
export async function fetchLatestReleaseVersion() {
|
|
87
115
|
const release = await fetchReleaseJson(`${RELEASES_API}/latest`);
|
|
88
116
|
return release ? normalizeReleaseVersion(release.tag_name) : null;
|
|
@@ -179,31 +207,66 @@ export async function runManagedUpdate(currentVersion) {
|
|
|
179
207
|
const method = detectInstallMethod();
|
|
180
208
|
if (method === "script")
|
|
181
209
|
return runScriptManagedUpdate(currentVersion);
|
|
210
|
+
const root = findGitRoot(path.dirname(runningModulePath()));
|
|
211
|
+
let latestVersion = null;
|
|
212
|
+
if (method === "dev-link" && root) {
|
|
213
|
+
latestVersion = await fetchLatestGitTag(root);
|
|
214
|
+
}
|
|
215
|
+
else {
|
|
216
|
+
latestVersion = await fetchLatestReleaseVersion();
|
|
217
|
+
}
|
|
218
|
+
const normalizedCurrent = semverValid(currentVersion);
|
|
219
|
+
let statusHeader = "";
|
|
220
|
+
let hasUpdate = false;
|
|
221
|
+
if (latestVersion && normalizedCurrent) {
|
|
222
|
+
hasUpdate = semverGt(latestVersion, normalizedCurrent);
|
|
223
|
+
if (hasUpdate) {
|
|
224
|
+
statusHeader = `A new version of muonroi-cli is available!\n Current version: v${normalizedCurrent}\n Latest version: v${latestVersion}\n\n`;
|
|
225
|
+
}
|
|
226
|
+
else {
|
|
227
|
+
statusHeader = `You are already up to date!\n Current version: v${normalizedCurrent}\n Latest version: v${latestVersion}\n\n`;
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
else if (normalizedCurrent) {
|
|
231
|
+
statusHeader = `Current version: v${normalizedCurrent}\nUnable to check the latest version from GitHub.\n\n`;
|
|
232
|
+
}
|
|
182
233
|
const cmd = getUpdateCommandForMethod(method);
|
|
183
234
|
if (cmd) {
|
|
184
235
|
const pm = method === "bun-global" ? "bun" : "npm";
|
|
236
|
+
const instruction = hasUpdate
|
|
237
|
+
? `To update, run this in a fresh terminal:\n\n ${cmd}\n\nThen restart muonroi-cli.`
|
|
238
|
+
: `If you want to reinstall, run this in a fresh terminal:\n\n ${cmd}`;
|
|
185
239
|
return {
|
|
186
240
|
success: true,
|
|
187
|
-
output:
|
|
241
|
+
output: `${statusHeader}${instruction}`,
|
|
188
242
|
};
|
|
189
243
|
}
|
|
190
244
|
if (method === "compiled") {
|
|
191
245
|
const target = getReleaseTargetForPlatform();
|
|
192
246
|
const asset = target?.assetName ?? "the release asset for your platform";
|
|
247
|
+
const instruction = hasUpdate
|
|
248
|
+
? `Download the latest ${asset} from https://github.com/${GITHUB_REPO}/releases/latest and replace the current binary, or rebuild from source.`
|
|
249
|
+
: `If you want to reinstall, download the latest ${asset} from https://github.com/${GITHUB_REPO}/releases/latest and replace the current binary.`;
|
|
193
250
|
return {
|
|
194
251
|
success: true,
|
|
195
|
-
output:
|
|
252
|
+
output: `${statusHeader}${instruction}`,
|
|
196
253
|
};
|
|
197
254
|
}
|
|
198
255
|
if (method === "dev-link") {
|
|
199
|
-
const root = findGitRoot(path.dirname(runningModulePath()));
|
|
200
256
|
const target = root ?? "the muonroi-cli checkout";
|
|
257
|
+
const instruction = hasUpdate
|
|
258
|
+
? `To update, pull the latest changes and rebuild:\n\n git -C "${target}" pull && bun install && bun run build\n\nThen restart muonroi-cli. (If you also use the compiled muonroi-cli-dev binary, rebuild that separately.)`
|
|
259
|
+
: `To rebuild your local installation:\n\n git -C "${target}" pull && bun install && bun run build\n\nThen restart muonroi-cli.`;
|
|
201
260
|
return {
|
|
202
261
|
success: true,
|
|
203
|
-
output:
|
|
262
|
+
output: `${statusHeader}${instruction}`,
|
|
204
263
|
};
|
|
205
264
|
}
|
|
206
|
-
|
|
265
|
+
const fallback = notScriptManaged("update");
|
|
266
|
+
return {
|
|
267
|
+
success: fallback.success,
|
|
268
|
+
output: `${statusHeader}${fallback.output}`,
|
|
269
|
+
};
|
|
207
270
|
}
|
|
208
271
|
export async function runScriptManagedUpdate(currentVersion) {
|
|
209
272
|
const context = getScriptInstallContext();
|
|
@@ -348,7 +411,13 @@ async function fetchReleaseJson(url) {
|
|
|
348
411
|
try {
|
|
349
412
|
const controller = new AbortController();
|
|
350
413
|
const timer = setTimeout(() => controller.abort(), FETCH_TIMEOUT_MS);
|
|
351
|
-
const res = await fetch(url, {
|
|
414
|
+
const res = await fetch(url, {
|
|
415
|
+
signal: controller.signal,
|
|
416
|
+
headers: {
|
|
417
|
+
Accept: "application/vnd.github+json",
|
|
418
|
+
"User-Agent": "muonroi-cli",
|
|
419
|
+
},
|
|
420
|
+
});
|
|
352
421
|
clearTimeout(timer);
|
|
353
422
|
return res.ok ? (await res.json()) : null;
|
|
354
423
|
}
|
|
@@ -1,7 +1,15 @@
|
|
|
1
|
+
import { exec } from "child_process";
|
|
1
2
|
import fs from "fs";
|
|
2
3
|
import os from "os";
|
|
3
4
|
import path from "path";
|
|
4
|
-
import { afterEach, describe, expect, it } from "vitest";
|
|
5
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
6
|
+
vi.mock("child_process", async () => {
|
|
7
|
+
const actual = await vi.importActual("child_process");
|
|
8
|
+
return {
|
|
9
|
+
...actual,
|
|
10
|
+
exec: vi.fn(),
|
|
11
|
+
};
|
|
12
|
+
});
|
|
5
13
|
import { buildScriptUninstallPlan, getInstallMetadataPath, getReleaseTargetForPlatform, getScriptInstallContext, getScriptInstallDir, loadScriptInstallMetadata, parseChecksumsFile, saveScriptInstallMetadata, } from "./install-manager.js";
|
|
6
14
|
let tempDirs = [];
|
|
7
15
|
afterEach(() => {
|
|
@@ -124,4 +132,38 @@ describe("buildScriptUninstallPlan", () => {
|
|
|
124
132
|
expect(plan?.removePaths).toContain(path.join(installDir, currentTarget.binaryName));
|
|
125
133
|
});
|
|
126
134
|
});
|
|
135
|
+
describe("runManagedUpdate", () => {
|
|
136
|
+
beforeEach(() => {
|
|
137
|
+
vi.stubGlobal("fetch", vi.fn());
|
|
138
|
+
});
|
|
139
|
+
afterEach(() => {
|
|
140
|
+
vi.restoreAllMocks();
|
|
141
|
+
});
|
|
142
|
+
it("handles dev-link update when newer version is available", async () => {
|
|
143
|
+
vi.mocked(exec).mockImplementation(((cmd, callback) => {
|
|
144
|
+
callback(null, "hash\trefs/tags/v2.0.0\n", "");
|
|
145
|
+
return {};
|
|
146
|
+
}));
|
|
147
|
+
const { runManagedUpdate } = await import("./install-manager.js");
|
|
148
|
+
const result = await runManagedUpdate("1.0.0");
|
|
149
|
+
expect(result.success).toBe(true);
|
|
150
|
+
expect(result.output).toContain("A new version of muonroi-cli is available!");
|
|
151
|
+
expect(result.output).toContain("Current version: v1.0.0");
|
|
152
|
+
expect(result.output).toContain("Latest version: v2.0.0");
|
|
153
|
+
expect(result.output).toContain("git -C");
|
|
154
|
+
expect(result.output).toContain("pull && bun install && bun run build");
|
|
155
|
+
});
|
|
156
|
+
it("handles dev-link when already up to date", async () => {
|
|
157
|
+
vi.mocked(exec).mockImplementation(((cmd, callback) => {
|
|
158
|
+
callback(null, "hash\trefs/tags/v1.0.0\n", "");
|
|
159
|
+
return {};
|
|
160
|
+
}));
|
|
161
|
+
const { runManagedUpdate } = await import("./install-manager.js");
|
|
162
|
+
const result = await runManagedUpdate("1.0.0");
|
|
163
|
+
expect(result.success).toBe(true);
|
|
164
|
+
expect(result.output).toContain("You are already up to date!");
|
|
165
|
+
expect(result.output).toContain("Current version: v1.0.0");
|
|
166
|
+
expect(result.output).toContain("Latest version: v1.0.0");
|
|
167
|
+
});
|
|
168
|
+
});
|
|
127
169
|
//# sourceMappingURL=install-manager.test.js.map
|
|
@@ -41,4 +41,12 @@ export declare function withTimeoutSignal(parent: AbortSignal | undefined, timeo
|
|
|
41
41
|
* still be aborted via `withTimeoutSignal` for cleanup; this layer just ensures
|
|
42
42
|
* the caller is never blocked past the deadline.
|
|
43
43
|
*/
|
|
44
|
-
export declare function withDeadlineRace<T>(fn: () => Promise<T>, deadlineMs: number, label: string
|
|
44
|
+
export declare function withDeadlineRace<T>(fn: () => Promise<T>, deadlineMs: number, label: string,
|
|
45
|
+
/**
|
|
46
|
+
* User-abort signal. When it fires, the race rejects within `abortGraceMs`
|
|
47
|
+
* even if `fn()` hasn't settled — so a provider that ignores its abortSignal
|
|
48
|
+
* mid-call (observed with DeepSeek/grok socket stalls) can't keep the caller
|
|
49
|
+
* (and, for council, the locked composer) blocked until the full `deadlineMs`.
|
|
50
|
+
* The short grace lets the normal fetch-level abort win first when it's quick.
|
|
51
|
+
*/
|
|
52
|
+
abortSignal?: AbortSignal, abortGraceMs?: number): Promise<T>;
|
|
@@ -62,19 +62,48 @@ export function withTimeoutSignal(parent, timeoutMs) {
|
|
|
62
62
|
* still be aborted via `withTimeoutSignal` for cleanup; this layer just ensures
|
|
63
63
|
* the caller is never blocked past the deadline.
|
|
64
64
|
*/
|
|
65
|
-
export async function withDeadlineRace(fn, deadlineMs, label
|
|
65
|
+
export async function withDeadlineRace(fn, deadlineMs, label,
|
|
66
|
+
/**
|
|
67
|
+
* User-abort signal. When it fires, the race rejects within `abortGraceMs`
|
|
68
|
+
* even if `fn()` hasn't settled — so a provider that ignores its abortSignal
|
|
69
|
+
* mid-call (observed with DeepSeek/grok socket stalls) can't keep the caller
|
|
70
|
+
* (and, for council, the locked composer) blocked until the full `deadlineMs`.
|
|
71
|
+
* The short grace lets the normal fetch-level abort win first when it's quick.
|
|
72
|
+
*/
|
|
73
|
+
abortSignal, abortGraceMs = 1500) {
|
|
66
74
|
let timer = null;
|
|
75
|
+
let abortTimer = null;
|
|
76
|
+
let abortListener = null;
|
|
67
77
|
const deadline = new Promise((_, reject) => {
|
|
68
78
|
timer = setTimeout(() => {
|
|
69
79
|
reject(new Error(`${label} exceeded ${deadlineMs}ms deadline (timeout)`));
|
|
70
80
|
}, deadlineMs);
|
|
71
81
|
});
|
|
82
|
+
const racers = [fn(), deadline];
|
|
83
|
+
if (abortSignal) {
|
|
84
|
+
const abortRace = new Promise((_, reject) => {
|
|
85
|
+
const arm = () => {
|
|
86
|
+
abortTimer = setTimeout(() => reject(new Error(`${label} aborted by user`)), abortGraceMs);
|
|
87
|
+
};
|
|
88
|
+
if (abortSignal.aborted)
|
|
89
|
+
arm();
|
|
90
|
+
else {
|
|
91
|
+
abortListener = arm;
|
|
92
|
+
abortSignal.addEventListener("abort", arm, { once: true });
|
|
93
|
+
}
|
|
94
|
+
});
|
|
95
|
+
racers.push(abortRace);
|
|
96
|
+
}
|
|
72
97
|
try {
|
|
73
|
-
return await Promise.race(
|
|
98
|
+
return await Promise.race(racers);
|
|
74
99
|
}
|
|
75
100
|
finally {
|
|
76
101
|
if (timer)
|
|
77
102
|
clearTimeout(timer);
|
|
103
|
+
if (abortTimer)
|
|
104
|
+
clearTimeout(abortTimer);
|
|
105
|
+
if (abortListener && abortSignal)
|
|
106
|
+
abortSignal.removeEventListener("abort", abortListener);
|
|
78
107
|
}
|
|
79
108
|
}
|
|
80
109
|
//# sourceMappingURL=llm-deadline.js.map
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
export type LogLevel = "debug" | "info" | "warn" | "error";
|
|
2
|
+
export type LogNamespace = "cli" | "ui" | "orchestrator" | "storage" | "ee" | "mcp" | "pil" | "router";
|
|
3
|
+
export interface LogContext {
|
|
4
|
+
elapsedMs?: number;
|
|
5
|
+
error?: Error | unknown;
|
|
6
|
+
[key: string]: unknown;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Checks if the given log level is enabled based on the current process level weight.
|
|
10
|
+
*/
|
|
11
|
+
export declare function isLogLevelEnabled(level: LogLevel): boolean;
|
|
12
|
+
/**
|
|
13
|
+
* Redacts common patterns of API keys and credential strings from log messages.
|
|
14
|
+
*/
|
|
15
|
+
export declare function redactSecrets(str: string): string;
|
|
16
|
+
/**
|
|
17
|
+
* Recursively redacts sensitive fields from context objects.
|
|
18
|
+
*/
|
|
19
|
+
export declare function redactObject(obj: unknown): unknown;
|
|
20
|
+
/**
|
|
21
|
+
* Structured unified logging system.
|
|
22
|
+
*/
|
|
23
|
+
export declare const logger: {
|
|
24
|
+
debug(ns: LogNamespace, msg: string, ctx?: LogContext): void;
|
|
25
|
+
info(ns: LogNamespace, msg: string, ctx?: LogContext): void;
|
|
26
|
+
warn(ns: LogNamespace, msg: string, ctx?: LogContext): void;
|
|
27
|
+
error(ns: LogNamespace, msg: string, ctx?: LogContext): void;
|
|
28
|
+
};
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
import * as fs from "fs";
|
|
2
|
+
import * as path from "path";
|
|
3
|
+
import * as os from "os";
|
|
4
|
+
const LEVEL_WEIGHTS = {
|
|
5
|
+
debug: 0,
|
|
6
|
+
info: 1,
|
|
7
|
+
warn: 2,
|
|
8
|
+
error: 3,
|
|
9
|
+
};
|
|
10
|
+
// Default log level is 'info' unless explicitly configured.
|
|
11
|
+
const CURRENT_WEIGHT = (() => {
|
|
12
|
+
const envLevel = process.env.MUONROI_LOG_LEVEL?.toLowerCase();
|
|
13
|
+
if (envLevel === "debug")
|
|
14
|
+
return LEVEL_WEIGHTS.debug;
|
|
15
|
+
if (envLevel === "info")
|
|
16
|
+
return LEVEL_WEIGHTS.info;
|
|
17
|
+
if (envLevel === "warn")
|
|
18
|
+
return LEVEL_WEIGHTS.warn;
|
|
19
|
+
if (envLevel === "error")
|
|
20
|
+
return LEVEL_WEIGHTS.error;
|
|
21
|
+
return LEVEL_WEIGHTS.info;
|
|
22
|
+
})();
|
|
23
|
+
/**
|
|
24
|
+
* Checks if the given log level is enabled based on the current process level weight.
|
|
25
|
+
*/
|
|
26
|
+
export function isLogLevelEnabled(level) {
|
|
27
|
+
return LEVEL_WEIGHTS[level] >= CURRENT_WEIGHT;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Redacts common patterns of API keys and credential strings from log messages.
|
|
31
|
+
*/
|
|
32
|
+
export function redactSecrets(str) {
|
|
33
|
+
return str
|
|
34
|
+
.replace(/\bsk-[A-Za-z0-9-_]{20,}\b/g, "[REDACTED_API_KEY]")
|
|
35
|
+
.replace(/\bxai-[A-Za-z0-9-_]{20,}\b/g, "[REDACTED_API_KEY]")
|
|
36
|
+
.replace(/\bAIzaSy[A-Za-z0-9-_]{30,}\b/g, "[REDACTED_API_KEY]");
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Recursively redacts sensitive fields from context objects.
|
|
40
|
+
*/
|
|
41
|
+
export function redactObject(obj) {
|
|
42
|
+
if (obj === null || obj === undefined)
|
|
43
|
+
return obj;
|
|
44
|
+
if (typeof obj === "string") {
|
|
45
|
+
return redactSecrets(obj);
|
|
46
|
+
}
|
|
47
|
+
if (Array.isArray(obj)) {
|
|
48
|
+
return obj.map(redactObject);
|
|
49
|
+
}
|
|
50
|
+
if (typeof obj === "object") {
|
|
51
|
+
const res = {};
|
|
52
|
+
for (const [k, v] of Object.entries(obj)) {
|
|
53
|
+
const lowerK = k.toLowerCase();
|
|
54
|
+
if (lowerK.includes("key") ||
|
|
55
|
+
lowerK.includes("secret") ||
|
|
56
|
+
lowerK.includes("token") ||
|
|
57
|
+
lowerK.includes("password") ||
|
|
58
|
+
lowerK.includes("auth")) {
|
|
59
|
+
res[k] = "[REDACTED]";
|
|
60
|
+
}
|
|
61
|
+
else {
|
|
62
|
+
res[k] = redactObject(v);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
return res;
|
|
66
|
+
}
|
|
67
|
+
return obj;
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Returns true if the interactive TUI is active, preventing console writes.
|
|
71
|
+
*/
|
|
72
|
+
function isTuiActive() {
|
|
73
|
+
try {
|
|
74
|
+
return globalThis.__muonroiTuiActive === true;
|
|
75
|
+
}
|
|
76
|
+
catch {
|
|
77
|
+
return false;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Writes logs safely to ~/.muonroi-cli/debug.log.
|
|
82
|
+
*/
|
|
83
|
+
function appendToFile(level, ns, msg, ctx) {
|
|
84
|
+
try {
|
|
85
|
+
const dir = path.join(os.homedir(), ".muonroi-cli");
|
|
86
|
+
if (!fs.existsSync(dir)) {
|
|
87
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
88
|
+
}
|
|
89
|
+
const logPath = path.join(dir, "debug.log");
|
|
90
|
+
const ts = new Date().toISOString();
|
|
91
|
+
const redactedCtx = ctx ? redactObject(ctx) : null;
|
|
92
|
+
const ctxStr = redactedCtx ? ` ${JSON.stringify(redactedCtx)}` : "";
|
|
93
|
+
const logLine = `[${ts}] [${level.toUpperCase()}] [${ns.toUpperCase()}] ${redactSecrets(msg)}${ctxStr}\n`;
|
|
94
|
+
fs.appendFileSync(logPath, logLine, "utf8");
|
|
95
|
+
}
|
|
96
|
+
catch {
|
|
97
|
+
/* fail-open */
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Formats console log lines with colors.
|
|
102
|
+
*/
|
|
103
|
+
function formatConsole(level, ns, msg, ctx) {
|
|
104
|
+
const ts = new Date().toISOString().split("T")[1].slice(0, -1); // HH:MM:SS.mmm
|
|
105
|
+
const levelStr = level.toUpperCase();
|
|
106
|
+
const nsStr = ns.toUpperCase();
|
|
107
|
+
const cleanMsg = redactSecrets(msg);
|
|
108
|
+
const redactedCtx = ctx ? redactObject(ctx) : null;
|
|
109
|
+
const ctxStr = redactedCtx ? ` ${JSON.stringify(redactedCtx)}` : "";
|
|
110
|
+
// Apply colors for developer convenience in terminal logs (non-TUI)
|
|
111
|
+
let color = "\x1b[0m"; // Reset
|
|
112
|
+
if (level === "debug")
|
|
113
|
+
color = "\x1b[90m"; // Gray
|
|
114
|
+
if (level === "info")
|
|
115
|
+
color = "\x1b[32m"; // Green
|
|
116
|
+
if (level === "warn")
|
|
117
|
+
color = "\x1b[33m"; // Yellow
|
|
118
|
+
if (level === "error")
|
|
119
|
+
color = "\x1b[31m"; // Red
|
|
120
|
+
return `${color}[${ts}] [${levelStr}] [${nsStr}] ${cleanMsg}${ctxStr}\x1b[0m`;
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* Structured unified logging system.
|
|
124
|
+
*/
|
|
125
|
+
export const logger = {
|
|
126
|
+
debug(ns, msg, ctx) {
|
|
127
|
+
if (!isLogLevelEnabled("debug"))
|
|
128
|
+
return;
|
|
129
|
+
if (isTuiActive()) {
|
|
130
|
+
appendToFile("debug", ns, msg, ctx);
|
|
131
|
+
}
|
|
132
|
+
else {
|
|
133
|
+
// eslint-disable-next-line no-console
|
|
134
|
+
console.log(formatConsole("debug", ns, msg, ctx));
|
|
135
|
+
}
|
|
136
|
+
},
|
|
137
|
+
info(ns, msg, ctx) {
|
|
138
|
+
if (!isLogLevelEnabled("info"))
|
|
139
|
+
return;
|
|
140
|
+
if (isTuiActive()) {
|
|
141
|
+
appendToFile("info", ns, msg, ctx);
|
|
142
|
+
}
|
|
143
|
+
else {
|
|
144
|
+
// eslint-disable-next-line no-console
|
|
145
|
+
console.log(formatConsole("info", ns, msg, ctx));
|
|
146
|
+
}
|
|
147
|
+
},
|
|
148
|
+
warn(ns, msg, ctx) {
|
|
149
|
+
if (!isLogLevelEnabled("warn"))
|
|
150
|
+
return;
|
|
151
|
+
if (isTuiActive()) {
|
|
152
|
+
appendToFile("warn", ns, msg, ctx);
|
|
153
|
+
}
|
|
154
|
+
else {
|
|
155
|
+
// eslint-disable-next-line no-console
|
|
156
|
+
console.warn(formatConsole("warn", ns, msg, ctx));
|
|
157
|
+
}
|
|
158
|
+
},
|
|
159
|
+
error(ns, msg, ctx) {
|
|
160
|
+
if (!isLogLevelEnabled("error"))
|
|
161
|
+
return;
|
|
162
|
+
if (isTuiActive()) {
|
|
163
|
+
appendToFile("error", ns, msg, ctx);
|
|
164
|
+
}
|
|
165
|
+
else {
|
|
166
|
+
// eslint-disable-next-line no-console
|
|
167
|
+
console.error(formatConsole("error", ns, msg, ctx));
|
|
168
|
+
}
|
|
169
|
+
},
|
|
170
|
+
};
|
|
171
|
+
//# sourceMappingURL=logger.js.map
|
|
@@ -5,6 +5,39 @@ export type PermissionMode = "safe" | "auto-edit" | "yolo";
|
|
|
5
5
|
* without manual confirmation when the user has opted into auto-edit mode.
|
|
6
6
|
*/
|
|
7
7
|
export declare const AUTO_EDIT_ALLOWED: ReadonlySet<string>;
|
|
8
|
+
/**
|
|
9
|
+
* Structured block result returned by safety checks.
|
|
10
|
+
* `null` means the command is safe to proceed.
|
|
11
|
+
*/
|
|
12
|
+
export interface SafetyBlockResult {
|
|
13
|
+
/** Machine-readable kind for routing to the right askcard layout. */
|
|
14
|
+
kind: "catastrophic" | "dangerous" | "git-safety" | "empty-bash";
|
|
15
|
+
/** Human-readable reason explaining why it was blocked. */
|
|
16
|
+
reason: string;
|
|
17
|
+
/** The original command text that was blocked. */
|
|
18
|
+
command: string;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Returns a SafetyBlockResult if `command` matches a catastrophic
|
|
22
|
+
* pattern, or `null` if the command is safe to proceed.
|
|
23
|
+
*
|
|
24
|
+
* This is checked at the tool layer (BashTool.prepareCommand) and is
|
|
25
|
+
* independent of the UI-layer permission approval system — it applies even
|
|
26
|
+
* in yolo mode.
|
|
27
|
+
*/
|
|
28
|
+
export declare function checkCatastrophicCommand(command: string): SafetyBlockResult | null;
|
|
29
|
+
/**
|
|
30
|
+
* Check a command against the "dangerous" patterns used by `toolNeedsApproval`.
|
|
31
|
+
* These are less severe than catastrophic but should still prompt for approval
|
|
32
|
+
* in safe / auto-edit modes.
|
|
33
|
+
*
|
|
34
|
+
* Returns a SafetyBlockResult or null if safe.
|
|
35
|
+
*/
|
|
36
|
+
export declare function checkDangerousCommand(command: string): SafetyBlockResult | null;
|
|
37
|
+
/**
|
|
38
|
+
* Check if a command is a safety-blocked git operation (push-on-red, broad staging).
|
|
39
|
+
*/
|
|
40
|
+
export declare function checkGitSafetyCommand(command: string, gitSafetyKey: string): SafetyBlockResult | null;
|
|
8
41
|
/**
|
|
9
42
|
* Returns true if the given tool call requires manual user approval
|
|
10
43
|
* under the specified permission mode.
|
|
@@ -19,6 +19,159 @@ export const AUTO_EDIT_ALLOWED = new Set([
|
|
|
19
19
|
"grep",
|
|
20
20
|
"list_directory",
|
|
21
21
|
]);
|
|
22
|
+
/**
|
|
23
|
+
* Catastrophic commands: unrecoverable operations that are ALWAYS hard-blocked
|
|
24
|
+
* at the tool layer regardless of permission mode (safe/auto-edit/yolo).
|
|
25
|
+
*
|
|
26
|
+
* These are patterns where the potential for irreversible damage to the host
|
|
27
|
+
* system is high enough that no user approval dialog is sufficient — they must
|
|
28
|
+
* never execute. This list intentionally errs on the side of caution.
|
|
29
|
+
*
|
|
30
|
+
* Override: set MUONROI_ALLOW_CATASTROPHIC=1 to bypass (not recommended;
|
|
31
|
+
* intended only for explicit test harness scenarios).
|
|
32
|
+
*/
|
|
33
|
+
const CATASTROPHIC_PATTERNS = [
|
|
34
|
+
// Disk / filesystem destruction
|
|
35
|
+
{
|
|
36
|
+
pattern: /\bdd\b[^|&;\n]*\bof=\/dev\//i,
|
|
37
|
+
reason: "dd writing to a raw device can destroy disk contents irreversibly.",
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
pattern: /\bmkfs\b/i,
|
|
41
|
+
reason: "mkfs formats a filesystem, destroying all data on the target device.",
|
|
42
|
+
},
|
|
43
|
+
// Fork bomb — classic bash pattern: :(){ :|:& };:
|
|
44
|
+
// Match the defining signature: a function named `:` that calls itself recursively.
|
|
45
|
+
// This is tight enough to avoid false positives on URLs containing `:/`.
|
|
46
|
+
{
|
|
47
|
+
pattern: /:\s*\(\s*\)\s*\{[^}]*:\s*\|\s*:/,
|
|
48
|
+
reason: "Fork bomb pattern detected — would exhaust host process table.",
|
|
49
|
+
},
|
|
50
|
+
// Remote code execution via piped shell
|
|
51
|
+
{
|
|
52
|
+
pattern: /\bcurl\b[^|&;\n]*\|\s*(?:ba)?sh\b/i,
|
|
53
|
+
reason: "curl piped to a shell executes arbitrary remote code without inspection.",
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
pattern: /\bwget\b[^|&;\n]*\|\s*(?:ba)?sh\b/i,
|
|
57
|
+
reason: "wget piped to a shell executes arbitrary remote code without inspection.",
|
|
58
|
+
},
|
|
59
|
+
// Privilege escalation
|
|
60
|
+
{
|
|
61
|
+
pattern: /\bsudo\s+/i,
|
|
62
|
+
reason: "sudo privilege escalation is not permitted from the agent shell.",
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
pattern: /\bsu\s+-\s*/i,
|
|
66
|
+
reason: "su to root is not permitted from the agent shell.",
|
|
67
|
+
},
|
|
68
|
+
// Persistence mechanisms — block crontab edits/writes; allow only -l (list, read-only)
|
|
69
|
+
// crontab <file>, crontab -, crontab -e all create/modify the crontab.
|
|
70
|
+
{
|
|
71
|
+
pattern: /\bcrontab\b(?!\s+-l\b)/i,
|
|
72
|
+
reason: "crontab write/edit commands create persistent execution outside the session.",
|
|
73
|
+
},
|
|
74
|
+
{
|
|
75
|
+
pattern: /\/etc\/(?:rc\.d|init\.d|systemd|cron)/i,
|
|
76
|
+
reason: "Writing to system init or cron directories would create persistence.",
|
|
77
|
+
},
|
|
78
|
+
// Reverse shells
|
|
79
|
+
{
|
|
80
|
+
pattern: /\bnc\b[^|&;\n]*(?:-e|-c)\s+/i,
|
|
81
|
+
reason: "nc with -e/-c flag can open a reverse shell.",
|
|
82
|
+
},
|
|
83
|
+
{
|
|
84
|
+
pattern: /\bsocat\b[^|&;\n]*(?:EXEC|SYSTEM):/i,
|
|
85
|
+
reason: "socat with EXEC/SYSTEM can open a reverse shell.",
|
|
86
|
+
},
|
|
87
|
+
{
|
|
88
|
+
pattern: /\/dev\/tcp\//i,
|
|
89
|
+
reason: "Bash /dev/tcp redirect can open a covert reverse shell connection.",
|
|
90
|
+
},
|
|
91
|
+
// Exfiltration of credentials / secrets directories
|
|
92
|
+
{
|
|
93
|
+
pattern: /(?:tar|zip|gzip|rsync|scp|sftp|ftp)\b[^|&;\n]*\.(?:muonroi-cli|ssh|gnupg|aws|kube)/i,
|
|
94
|
+
reason: "Archiving or uploading credential directories is not permitted.",
|
|
95
|
+
},
|
|
96
|
+
];
|
|
97
|
+
/**
|
|
98
|
+
* Returns a SafetyBlockResult if `command` matches a catastrophic
|
|
99
|
+
* pattern, or `null` if the command is safe to proceed.
|
|
100
|
+
*
|
|
101
|
+
* This is checked at the tool layer (BashTool.prepareCommand) and is
|
|
102
|
+
* independent of the UI-layer permission approval system — it applies even
|
|
103
|
+
* in yolo mode.
|
|
104
|
+
*/
|
|
105
|
+
export function checkCatastrophicCommand(command) {
|
|
106
|
+
if (process.env.MUONROI_ALLOW_CATASTROPHIC === "1")
|
|
107
|
+
return null;
|
|
108
|
+
for (const { pattern, reason } of CATASTROPHIC_PATTERNS) {
|
|
109
|
+
if (pattern.test(command)) {
|
|
110
|
+
return { kind: "catastrophic", reason, command };
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
return null;
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* Check a command against the "dangerous" patterns used by `toolNeedsApproval`.
|
|
117
|
+
* These are less severe than catastrophic but should still prompt for approval
|
|
118
|
+
* in safe / auto-edit modes.
|
|
119
|
+
*
|
|
120
|
+
* Returns a SafetyBlockResult or null if safe.
|
|
121
|
+
*/
|
|
122
|
+
export function checkDangerousCommand(command) {
|
|
123
|
+
const dangerousPatterns = [
|
|
124
|
+
// Filesystem destruction
|
|
125
|
+
/rm\s+-rf?\s+\/(?!tmp|var\/tmp)/i,
|
|
126
|
+
// Unrestricted chmod
|
|
127
|
+
/chmod\s+(?:777|a\+[rwx])/i,
|
|
128
|
+
// Code injection
|
|
129
|
+
/eval\s*\(/i,
|
|
130
|
+
/exec\s*\(/i,
|
|
131
|
+
// External network fetches (non-local)
|
|
132
|
+
/curl\s+https?:\/\/(?!127\.0\.0\.1|localhost)/i,
|
|
133
|
+
/wget\s+https?:\/\/(?!127\.0\.0\.1|localhost)/i,
|
|
134
|
+
// Process substitution with external commands
|
|
135
|
+
/\$\([^)]*(?:curl|wget|nc|socat)[^)]*\)/i,
|
|
136
|
+
// Writing to /etc (besides allowed subdirs)
|
|
137
|
+
/\bchown\s+root/i,
|
|
138
|
+
];
|
|
139
|
+
for (const pattern of dangerousPatterns) {
|
|
140
|
+
if (pattern.test(command)) {
|
|
141
|
+
return {
|
|
142
|
+
kind: "dangerous",
|
|
143
|
+
reason: `Command matched dangerous pattern: ${pattern.source}`,
|
|
144
|
+
command,
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
return null;
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* Check if a command is a safety-blocked git operation (push-on-red, broad staging).
|
|
152
|
+
*/
|
|
153
|
+
export function checkGitSafetyCommand(command, gitSafetyKey) {
|
|
154
|
+
// Lazy import to avoid circular deps at module level
|
|
155
|
+
const { analyzeGitCommand, checkPushGate, checkSensitiveStaging } = require_inline_safety_deps();
|
|
156
|
+
const gt = analyzeGitCommand(command);
|
|
157
|
+
if (gt.isPush) {
|
|
158
|
+
const gate = checkPushGate(gitSafetyKey);
|
|
159
|
+
if (gate.blocked) {
|
|
160
|
+
return {
|
|
161
|
+
kind: "git-safety",
|
|
162
|
+
reason: `git push blocked: ${gate.failed.join(", ")}`,
|
|
163
|
+
command,
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
return null;
|
|
168
|
+
}
|
|
169
|
+
// Inline require to avoid circular dependency at module load.
|
|
170
|
+
function require_inline_safety_deps() {
|
|
171
|
+
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
172
|
+
const mod = require("../tools/git-safety.js");
|
|
173
|
+
return mod;
|
|
174
|
+
}
|
|
22
175
|
/**
|
|
23
176
|
* Returns true if the given tool call requires manual user approval
|
|
24
177
|
* under the specified permission mode.
|
|
@@ -34,12 +187,20 @@ export function toolNeedsApproval(toolName, mode, context) {
|
|
|
34
187
|
if (!cmd)
|
|
35
188
|
return false;
|
|
36
189
|
const patterns = [
|
|
190
|
+
// Filesystem destruction
|
|
37
191
|
/rm\s+-rf?\s+\/(?!tmp|var\/tmp)/i,
|
|
38
|
-
|
|
39
|
-
/
|
|
40
|
-
|
|
192
|
+
// Unrestricted chmod
|
|
193
|
+
/chmod\s+(?:777|a\+[rwx])/i,
|
|
194
|
+
// Code injection
|
|
41
195
|
/eval\s*\(/i,
|
|
42
196
|
/exec\s*\(/i,
|
|
197
|
+
// External network fetches (non-local)
|
|
198
|
+
/curl\s+https?:\/\/(?!127\.0\.0\.1|localhost)/i,
|
|
199
|
+
/wget\s+https?:\/\/(?!127\.0\.0\.1|localhost)/i,
|
|
200
|
+
// Process substitution with external commands
|
|
201
|
+
/\$\([^)]*(?:curl|wget|nc|socat)[^)]*\)/i,
|
|
202
|
+
// Writing to /etc (besides allowed subdirs)
|
|
203
|
+
/\bchown\s+root/i,
|
|
43
204
|
];
|
|
44
205
|
return patterns.some((p) => p.test(cmd));
|
|
45
206
|
};
|