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
|
@@ -26,6 +26,7 @@ import { SETUP_GUIDE_TEXT } from "../mcp/setup-guide-text.js";
|
|
|
26
26
|
export const NATIVE_MUONROI_TOOL_NAMES = [
|
|
27
27
|
"ee_health",
|
|
28
28
|
"ee_feedback",
|
|
29
|
+
"ee_write",
|
|
29
30
|
"usage_forensics",
|
|
30
31
|
"lsp_query",
|
|
31
32
|
"setup_guide",
|
|
@@ -103,6 +104,61 @@ export function registerNativeMuonroiTools(tools, opts = {}) {
|
|
|
103
104
|
}
|
|
104
105
|
},
|
|
105
106
|
});
|
|
107
|
+
tools.ee_write = dynamicTool({
|
|
108
|
+
description: "Save a NEW lesson to the Experience Engine brain so you and future sessions recall it. Call this the MOMENT " +
|
|
109
|
+
"you hit a mistake / error / dead-end and find the working fix: record the pitfall AND the fix in one concise, " +
|
|
110
|
+
"generalizable lesson (1-3 sentences — what to do or avoid next time, NOT a play-by-play of this turn). The " +
|
|
111
|
+
"lesson is embedded immediately and becomes recallable via ee_query in THIS and future sessions. Use ee_query " +
|
|
112
|
+
"first when unsure if a lesson already exists. collection defaults to experience-behavioral (use " +
|
|
113
|
+
"experience-principles only for a broad, project-independent principle).",
|
|
114
|
+
inputSchema: jsonSchema({
|
|
115
|
+
type: "object",
|
|
116
|
+
properties: {
|
|
117
|
+
lesson: {
|
|
118
|
+
type: "string",
|
|
119
|
+
description: "The lesson: the mistake/pitfall AND the fix, concise and generalizable.",
|
|
120
|
+
},
|
|
121
|
+
title: { type: "string", description: "Optional short title (<=120 chars)." },
|
|
122
|
+
collection: {
|
|
123
|
+
type: "string",
|
|
124
|
+
enum: ["experience-behavioral", "experience-principles"],
|
|
125
|
+
description: "Target tier; default experience-behavioral.",
|
|
126
|
+
},
|
|
127
|
+
},
|
|
128
|
+
required: ["lesson"],
|
|
129
|
+
additionalProperties: false,
|
|
130
|
+
}),
|
|
131
|
+
execute: async (input) => {
|
|
132
|
+
const lesson = typeof input?.lesson === "string" ? input.lesson.trim() : "";
|
|
133
|
+
if (lesson.length < 12) {
|
|
134
|
+
return errLine("invalid_args", "ee_write requires a non-empty, substantive lesson (>=12 chars)");
|
|
135
|
+
}
|
|
136
|
+
// Cap oversized bodies (EE import-quality rule: bare points >1500 chars are
|
|
137
|
+
// status-dumps, not lessons).
|
|
138
|
+
const text = lesson.length > 1500 ? `${lesson.slice(0, 1497)}...` : lesson;
|
|
139
|
+
const collection = input?.collection === "experience-principles" ? "experience-principles" : "experience-behavioral";
|
|
140
|
+
const title = typeof input?.title === "string" && input.title.trim() ? input.title.trim().slice(0, 120) : undefined;
|
|
141
|
+
try {
|
|
142
|
+
const { writeExperienceEE } = await import("../ee/search.js");
|
|
143
|
+
const path = await import("node:path");
|
|
144
|
+
const cwd = opts.cwd ?? process.cwd();
|
|
145
|
+
// confidence 0.65 (above the import default 0.6): an agent-asserted lesson
|
|
146
|
+
// is deliberate but unproven — the ee_feedback loop then tunes it.
|
|
147
|
+
const result = await writeExperienceEE(text, {
|
|
148
|
+
collection,
|
|
149
|
+
title,
|
|
150
|
+
projectSlug: path.basename(cwd),
|
|
151
|
+
confidence: 0.65,
|
|
152
|
+
});
|
|
153
|
+
if (!result.ok)
|
|
154
|
+
return errLine("write_failed", result.error ?? "import-memory POST failed");
|
|
155
|
+
return json({ ok: true, id: result.id, collection, recallable: "now — same session via ee_query" });
|
|
156
|
+
}
|
|
157
|
+
catch (e) {
|
|
158
|
+
return errLine("write_failed", e instanceof Error ? e.message : String(e));
|
|
159
|
+
}
|
|
160
|
+
},
|
|
161
|
+
});
|
|
106
162
|
// ── Self-diagnostics: usage_forensics ─────────────────────────────────────
|
|
107
163
|
tools.usage_forensics = dynamicTool({
|
|
108
164
|
description: "Per-session token-cost forensics by session-id prefix: peak input, cache-hit ratio, per-event breakdown.",
|
|
@@ -180,6 +236,7 @@ export function registerNativeMuonroiTools(tools, opts = {}) {
|
|
|
180
236
|
goal: { type: "string" },
|
|
181
237
|
llm: { type: "string" },
|
|
182
238
|
turns: { type: "number" },
|
|
239
|
+
mockLlmDir: { type: "string", description: "Use 'none' to run child TUI against real LLM APIs" },
|
|
183
240
|
},
|
|
184
241
|
required: ["mode"],
|
|
185
242
|
}),
|
|
@@ -191,7 +248,15 @@ export function registerNativeMuonroiTools(tools, opts = {}) {
|
|
|
191
248
|
const { getModelInfo } = await import("../models/registry.js");
|
|
192
249
|
if (!getModelInfo(input.llm))
|
|
193
250
|
return errLine("unknown_model", `llm '${input.llm}' is not in catalog.json`);
|
|
194
|
-
return json({
|
|
251
|
+
return json({
|
|
252
|
+
runId: jm.start({
|
|
253
|
+
kind: "agentic",
|
|
254
|
+
goal: input.goal,
|
|
255
|
+
llm: input.llm,
|
|
256
|
+
turns: input.turns,
|
|
257
|
+
mockLlmDir: input.mockLlmDir,
|
|
258
|
+
}),
|
|
259
|
+
});
|
|
195
260
|
}
|
|
196
261
|
return json({
|
|
197
262
|
runId: jm.start({ kind: "tier1", since: input?.since, max: input?.max, emit: input?.emit, out: input?.out }),
|
|
@@ -26,7 +26,8 @@ describe("bash tool — empty/missing command corrective guard", () => {
|
|
|
26
26
|
const bash = new BashTool(os.tmpdir());
|
|
27
27
|
const tools = createBuiltinTools(bash, "agent");
|
|
28
28
|
const out = await runBash(tools, {});
|
|
29
|
-
|
|
29
|
+
// Guard uses "BLOCKED (empty-bash):" prefix — more semantic than bare "ERROR".
|
|
30
|
+
expect(out).toMatch(/BLOCKED/);
|
|
30
31
|
expect(out).toMatch(/non-empty/i);
|
|
31
32
|
expect(out).toMatch(/command/i);
|
|
32
33
|
});
|
|
@@ -34,14 +35,14 @@ describe("bash tool — empty/missing command corrective guard", () => {
|
|
|
34
35
|
const bash = new BashTool(os.tmpdir());
|
|
35
36
|
const tools = createBuiltinTools(bash, "agent");
|
|
36
37
|
const out = await runBash(tools, { command: "" });
|
|
37
|
-
expect(out).toMatch(/
|
|
38
|
+
expect(out).toMatch(/BLOCKED/);
|
|
38
39
|
expect(out).toMatch(/non-empty/i);
|
|
39
40
|
});
|
|
40
41
|
it("returns the corrective error for a whitespace-only command", async () => {
|
|
41
42
|
const bash = new BashTool(os.tmpdir());
|
|
42
43
|
const tools = createBuiltinTools(bash, "agent");
|
|
43
44
|
const out = await runBash(tools, { command: " " });
|
|
44
|
-
expect(out).toMatch(/
|
|
45
|
+
expect(out).toMatch(/BLOCKED/);
|
|
45
46
|
expect(out).toMatch(/non-empty/i);
|
|
46
47
|
});
|
|
47
48
|
it("does NOT block a real command", async () => {
|
|
@@ -12,11 +12,11 @@ import { BashTool } from "./bash.js";
|
|
|
12
12
|
import { clearBashOutputCache } from "./bash-output-cache.js";
|
|
13
13
|
import { __resetGitSafetyState, recordCommandOutcome } from "./git-safety.js";
|
|
14
14
|
import { createBuiltinTools } from "./registry.js";
|
|
15
|
-
async function runBash(tools, args) {
|
|
15
|
+
async function runBash(tools, args, extra) {
|
|
16
16
|
const t = tools.bash;
|
|
17
17
|
if (!t?.execute)
|
|
18
18
|
throw new Error("bash tool has no execute");
|
|
19
|
-
const out = await t.execute(args);
|
|
19
|
+
const out = await t.execute(args, extra);
|
|
20
20
|
return typeof out === "string" ? out : JSON.stringify(out);
|
|
21
21
|
}
|
|
22
22
|
describe("git-safety wiring in bash tool", () => {
|
|
@@ -28,6 +28,7 @@ describe("git-safety wiring in bash tool", () => {
|
|
|
28
28
|
});
|
|
29
29
|
afterEach(() => {
|
|
30
30
|
delete process.env.MUONROI_ALLOW_PUSH_ON_RED;
|
|
31
|
+
delete globalThis.__muonroiSafetyApproved;
|
|
31
32
|
});
|
|
32
33
|
it("BLOCKS git push (without executing) after a verification failed this session", async () => {
|
|
33
34
|
const bash = new BashTool(os.tmpdir());
|
|
@@ -35,7 +36,7 @@ describe("git-safety wiring in bash tool", () => {
|
|
|
35
36
|
// Simulate a failed test earlier in the session.
|
|
36
37
|
recordCommandOutcome("GS1", "npm test", false);
|
|
37
38
|
const out = await runBash(tools, { command: "git push origin main", timeout: 10_000 });
|
|
38
|
-
expect(out).toMatch(/^BLOCKED:/);
|
|
39
|
+
expect(out).toMatch(/^BLOCKED \(git-safety\):/);
|
|
39
40
|
expect(out).toMatch(/npm test/);
|
|
40
41
|
// The distinctive block message proves git push never ran (a real push in
|
|
41
42
|
// tmpdir would fail with a git error like "not a git repository", not this).
|
|
@@ -72,16 +73,26 @@ describe("git-safety wiring in bash tool", () => {
|
|
|
72
73
|
// Fresh anon registry (simulates the per-turn rebuild).
|
|
73
74
|
const toolsB = createBuiltinTools(bash, "agent"); // no sessionId
|
|
74
75
|
const pushOut = await runBash(toolsB, { command: "git push origin main", timeout: 10_000 });
|
|
75
|
-
expect(pushOut).toMatch(/^BLOCKED:/);
|
|
76
|
+
expect(pushOut).toMatch(/^BLOCKED \(git-safety\):/);
|
|
76
77
|
}, 30_000);
|
|
77
|
-
it("
|
|
78
|
+
it("uses command-matched safety approval when the retry has a new toolCallId", async () => {
|
|
79
|
+
const bash = new BashTool(os.tmpdir());
|
|
80
|
+
const tools = createBuiltinTools(bash, "agent", { sessionId: "GS-APPROVE" });
|
|
81
|
+
recordCommandOutcome("GS-APPROVE", "npm test", false);
|
|
82
|
+
globalThis.__muonroiSafetyApproved = new Map([
|
|
83
|
+
["blocked-tool-call-id", { kind: "once", command: "git push origin main" }],
|
|
84
|
+
]);
|
|
85
|
+
const out = await runBash(tools, { command: "git push origin main", timeout: 10_000 }, { toolCallId: "retry-tool-call-id" });
|
|
86
|
+
expect(out).not.toMatch(/^BLOCKED/);
|
|
87
|
+
}, 20_000);
|
|
88
|
+
it("blocks broad git add when sensitive paths exist", async () => {
|
|
78
89
|
const dir = mkdtempSync(join(os.tmpdir(), "gs-stage-"));
|
|
79
90
|
writeFileSync(join(dir, ".env"), "API_KEY=secret");
|
|
80
91
|
try {
|
|
81
92
|
const bash = new BashTool(dir);
|
|
82
93
|
const tools = createBuiltinTools(bash, "agent", { sessionId: "GS4" });
|
|
83
94
|
const out = await runBash(tools, { command: "git add -A", timeout: 10_000 });
|
|
84
|
-
expect(out).toMatch(
|
|
95
|
+
expect(out).toMatch(/^BLOCKED \(git-safety\):/);
|
|
85
96
|
expect(out).toMatch(/\.env/);
|
|
86
97
|
}
|
|
87
98
|
finally {
|
|
@@ -13,7 +13,9 @@ interface ToolRegistryOpts {
|
|
|
13
13
|
runDelegation?: (request: TaskRequest, abortSignal?: AbortSignal) => Promise<ToolResult>;
|
|
14
14
|
readDelegation?: (id: string) => Promise<ToolResult>;
|
|
15
15
|
listDelegations?: () => Promise<ToolResult>;
|
|
16
|
+
killDelegation?: (id: string) => Promise<ToolResult>;
|
|
16
17
|
modelId?: string;
|
|
18
|
+
consultParentSession?: (question: string) => Promise<string>;
|
|
17
19
|
/**
|
|
18
20
|
* When false, the 3 vision-proxy tools (analyze_image, ask_vision_proxy,
|
|
19
21
|
* list_vision_cache) are omitted even for vision-proxy models. Used by the
|
|
@@ -51,6 +53,10 @@ interface BashRepeatEntry {
|
|
|
51
53
|
}
|
|
52
54
|
declare global {
|
|
53
55
|
var __muonroiBashRepeatState: Map<string, BashRepeatEntry> | undefined;
|
|
56
|
+
var __muonroiSafetyApproved: Map<string, {
|
|
57
|
+
kind: "once" | "session";
|
|
58
|
+
command: string;
|
|
59
|
+
}> | undefined;
|
|
54
60
|
}
|
|
55
61
|
export declare const MAX_TOOL_OUTPUT_CHARS: number;
|
|
56
62
|
export declare function truncateOutput(text: string, maxChars?: number): string;
|
|
@@ -12,10 +12,16 @@ import { needsVisionProxy } from "../providers/vision-proxy.js";
|
|
|
12
12
|
import { getBashRun, sliceBashOutput } from "./bash-output-cache.js";
|
|
13
13
|
import { editFile, readFile, writeFile } from "./file.js";
|
|
14
14
|
import { FileTracker } from "./file-tracker.js";
|
|
15
|
-
import { analyzeGitCommand, checkPushGate, pushBlockedMessage, recordCommandOutcome,
|
|
15
|
+
import { analyzeGitCommand, checkPushGate, checkSensitiveStaging, commitBlockedMessage, pushBlockedMessage, recordCommandOutcome, } from "./git-safety.js";
|
|
16
16
|
import { executeGrep } from "./grep.js";
|
|
17
17
|
import { registerNativeMuonroiTools } from "./native-tools.js";
|
|
18
18
|
import { VISION_TOOL_NAMES } from "./vision-gate.js";
|
|
19
|
+
function getSafetyApprovedMap() {
|
|
20
|
+
if (!globalThis.__muonroiSafetyApproved) {
|
|
21
|
+
globalThis.__muonroiSafetyApproved = new Map();
|
|
22
|
+
}
|
|
23
|
+
return globalThis.__muonroiSafetyApproved;
|
|
24
|
+
}
|
|
19
25
|
function getBashRepeatState() {
|
|
20
26
|
if (!globalThis.__muonroiBashRepeatState) {
|
|
21
27
|
globalThis.__muonroiBashRepeatState = new Map();
|
|
@@ -80,7 +86,7 @@ export function createBuiltinTools(bash, mode, opts) {
|
|
|
80
86
|
const fileTracker = new FileTracker();
|
|
81
87
|
// read_file
|
|
82
88
|
tools.read_file = dynamicTool({
|
|
83
|
-
description: "Read file contents
|
|
89
|
+
description: "Read file contents. For large files, you MUST use start_line and end_line to extract only the needed sections (e.g. specific functions). Reading full large files will quickly exhaust your context budget. Use grep or lsp first to find line numbers.",
|
|
84
90
|
inputSchema: jsonSchema({
|
|
85
91
|
type: "object",
|
|
86
92
|
properties: {
|
|
@@ -97,7 +103,7 @@ export function createBuiltinTools(bash, mode, opts) {
|
|
|
97
103
|
});
|
|
98
104
|
// grep
|
|
99
105
|
tools.grep = dynamicTool({
|
|
100
|
-
description: "Fast regex content search across the codebase using ripgrep. Returns matching lines with file paths and line numbers.",
|
|
106
|
+
description: "Fast regex content search across the codebase using ripgrep. Returns matching lines with file paths and line numbers. Use this to find precise line numbers before calling read_file with start_line/end_line.",
|
|
101
107
|
inputSchema: jsonSchema({
|
|
102
108
|
type: "object",
|
|
103
109
|
properties: {
|
|
@@ -135,11 +141,17 @@ export function createBuiltinTools(bash, mode, opts) {
|
|
|
135
141
|
// else a single process-stable key. Over-sharing here is the safe direction
|
|
136
142
|
// (it can only over-block a push, never wrongly allow one).
|
|
137
143
|
const gitSafetyKey = opts?.sessionId && opts.sessionId.length > 0 ? opts.sessionId : `__proc_default__:${process.pid}`;
|
|
144
|
+
// Per-session empty-bash streak counter: escalates from guidance (strike 1-2)
|
|
145
|
+
// to hard block (strike 3+) so a cheap model that repeatedly emits `bash: {}`
|
|
146
|
+
// cannot loop indefinitely (live: deepseek session bf58d0f46b51 — 8+ empty calls).
|
|
147
|
+
const _emptyBashStreak = new Map();
|
|
148
|
+
const _prefixBlock = (kind, msg) => `BLOCKED (${kind}): ${msg}`;
|
|
138
149
|
tools.bash = dynamicTool({
|
|
139
150
|
description: "Execute a shell command. Output is automatically cached — every call returns a " +
|
|
140
151
|
"run_id you can re-query via bash_output_get(run_id, mode=tail|head|grep|lines). " +
|
|
141
152
|
"Do NOT pipe `| tail`, `| head`, `| grep`, or `> file` — that hides output from " +
|
|
142
|
-
"the cache. Run unpiped and slice via bash_output_get instead. For collecting system info (OS, versions, cwd layout, git, disk, processes) batch with ; or && in ONE call, e.g. 'uname -a; node -v; bun --version; ls -la | head -15; git status --short; df -h .; ps aux | head -5'. Set background=true for long-running processes (dev servers, watchers)."
|
|
153
|
+
"the cache. Run unpiped and slice via bash_output_get instead. For collecting system info (OS, versions, cwd layout, git, disk, processes) batch with ; or && in ONE call, e.g. 'uname -a; node -v; bun --version; ls -la | head -15; git status --short; df -h .; ps aux | head -5'. Set background=true for long-running processes (dev servers, watchers). " +
|
|
154
|
+
'Avoid nesting double-quotes inline for database queries/scripts (e.g. executing raw SQL via `sqlite3 db "SELECT..."`) since they can fail parsing in shell environments; write query scripts to a temporary file first and run them.',
|
|
143
155
|
inputSchema: jsonSchema({
|
|
144
156
|
type: "object",
|
|
145
157
|
properties: {
|
|
@@ -149,7 +161,10 @@ export function createBuiltinTools(bash, mode, opts) {
|
|
|
149
161
|
},
|
|
150
162
|
required: ["command"],
|
|
151
163
|
}),
|
|
152
|
-
|
|
164
|
+
// Phase 8 — safety-blocked commands queue, keyed by sessionId + toolCallId.
|
|
165
|
+
// When the orchestrator intercepts a blocked result, the approval handler
|
|
166
|
+
// stores the approved command here so bash.execute() can retry it.
|
|
167
|
+
execute: async (input, extra) => {
|
|
153
168
|
// Corrective guard for malformed calls: a cheap model sometimes emits a
|
|
154
169
|
// bash call with a missing / empty `command` (live: deepseek sent `{}`
|
|
155
170
|
// repeatedly until the loop-guard fired). Passing undefined to
|
|
@@ -158,9 +173,41 @@ export function createBuiltinTools(bash, mode, opts) {
|
|
|
158
173
|
// both look like progress and feed the loop. Return a crisp instruction
|
|
159
174
|
// so the next step supplies a real command instead of repeating.
|
|
160
175
|
if (typeof input.command !== "string" || input.command.trim() === "") {
|
|
161
|
-
|
|
176
|
+
// Track empty-bash streak per session; escalate from guidance to hard block.
|
|
177
|
+
const _ebKey = gitSafetyKey ?? "no-session";
|
|
178
|
+
let _eb = _emptyBashStreak.get(_ebKey) ?? 0;
|
|
179
|
+
_eb++;
|
|
180
|
+
_emptyBashStreak.set(_ebKey, _eb);
|
|
181
|
+
if (_eb >= 3) {
|
|
182
|
+
return ('BLOCKED (empty-bash): the `bash` tool has been called with an empty/missing "command" 3+ times in a row. ' +
|
|
183
|
+
"Bash is now DISABLED for the remainder of this session — use read_file, grep, or other tools instead. " +
|
|
184
|
+
"If you need to run a shell command, state the blocker explicitly and the CLI will enable it again on the next turn.");
|
|
185
|
+
}
|
|
186
|
+
if (_eb >= 2) {
|
|
187
|
+
return ('BLOCKED (empty-bash): ERROR (2nd consecutive empty bash call): the `bash` tool requires a non-empty "command" string ' +
|
|
188
|
+
"but this is the 2nd call in a row with empty arguments. One more empty call will BLOCK bash for the session. " +
|
|
189
|
+
'Provide a real command, e.g. {"command":"ls -la"}.');
|
|
190
|
+
}
|
|
191
|
+
return 'BLOCKED (empty-bash): the `bash` tool requires a non-empty "command" string, but the call had empty arguments. Provide the shell command to run, e.g. {"command":"ls -la"}.';
|
|
162
192
|
}
|
|
193
|
+
// Reset the empty-bash streak on any successful command.
|
|
194
|
+
_emptyBashStreak.delete(gitSafetyKey ?? "no-session");
|
|
163
195
|
const cmd = typeof input.command === "string" ? input.command : "";
|
|
196
|
+
// Safety override check: approval is granted after a blocked call, but
|
|
197
|
+
// the model's retry receives a new toolCallId. Match by id first, then
|
|
198
|
+
// by exact command so allow-once survives the retry boundary.
|
|
199
|
+
const _approvedMap = getSafetyApprovedMap();
|
|
200
|
+
const _approvalKey = extra?.toolCallId && _approvedMap.has(extra.toolCallId)
|
|
201
|
+
? extra.toolCallId
|
|
202
|
+
: [..._approvedMap.entries()].find(([, approval]) => approval.command === cmd)?.[0];
|
|
203
|
+
const _approvalEntry = _approvalKey ? _approvedMap.get(_approvalKey) : undefined;
|
|
204
|
+
if (_approvalEntry) {
|
|
205
|
+
if (_approvalEntry.kind === "once") {
|
|
206
|
+
_approvedMap.delete(_approvalKey);
|
|
207
|
+
}
|
|
208
|
+
const result = await bash.execute(input.command, input.timeout ?? 30000);
|
|
209
|
+
return formatResult(result);
|
|
210
|
+
}
|
|
164
211
|
// Git safety (pre-execution). Block `git push` while a verification
|
|
165
212
|
// command failed this session and was not re-run green; warn on broad
|
|
166
213
|
// `git add -A` / `git commit -a` when sensitive paths exist. Applied to
|
|
@@ -169,16 +216,46 @@ export function createBuiltinTools(bash, mode, opts) {
|
|
|
169
216
|
// (or the real sessionId) — unlike repeatKey, whose anon fallback changes
|
|
170
217
|
// on every registry rebuild and would silently drop the gate across turns.
|
|
171
218
|
const gitShape = analyzeGitCommand(cmd);
|
|
172
|
-
|
|
219
|
+
// Hard-block broad staging when sensitive files are present.
|
|
220
|
+
// This runs PRE-EXECUTION (before bash.execute) regardless of permission mode.
|
|
221
|
+
if (gitShape.isBroadStage) {
|
|
222
|
+
const stagingBlock = checkSensitiveStaging(bash.getCwd());
|
|
223
|
+
if (stagingBlock.blocked) {
|
|
224
|
+
return _prefixBlock("git-safety", stagingBlock.message);
|
|
225
|
+
}
|
|
226
|
+
}
|
|
173
227
|
if (gitShape.isPush) {
|
|
174
228
|
const gate = checkPushGate(gitSafetyKey);
|
|
175
229
|
if (gate.blocked) {
|
|
176
|
-
return
|
|
230
|
+
return _prefixBlock("git-safety", pushBlockedMessage(gate.failed));
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
// G1 follow-up: a raw bash `git commit` must not bypass the LSP commit
|
|
234
|
+
// gate that the `git_commit` tool + auto-commit backstop enforce. Derive
|
|
235
|
+
// the to-be-committed paths from git state and block (pre-exec, never run
|
|
236
|
+
// the commit) if any staged source file has a severity-1 LSP error. The
|
|
237
|
+
// gate is fail-OPEN (LSP slow/down → allow) and self-disables under
|
|
238
|
+
// MUONROI_COMMIT_GATE=0 / the unit-test suite. Lazy-import keeps the
|
|
239
|
+
// LSP-heavy module off the hot bash path (mirrors git_commit below).
|
|
240
|
+
if (gitShape.isCommit) {
|
|
241
|
+
const { gateStagedPaths, isCommitGateEnabled, pathsForCommitGate } = await import("../orchestrator/auto-commit.js");
|
|
242
|
+
if (isCommitGateEnabled()) {
|
|
243
|
+
const commitCwd = bash.getCwd();
|
|
244
|
+
const paths = await pathsForCommitGate(commitCwd, {
|
|
245
|
+
broadAdd: gitShape.isBroadAdd,
|
|
246
|
+
commitAll: gitShape.isCommitAll,
|
|
247
|
+
});
|
|
248
|
+
if (paths.length > 0) {
|
|
249
|
+
const gate = await gateStagedPaths(commitCwd, paths);
|
|
250
|
+
if (!gate.ok) {
|
|
251
|
+
return _prefixBlock("git-safety", commitBlockedMessage(gate.summary));
|
|
252
|
+
}
|
|
253
|
+
}
|
|
177
254
|
}
|
|
178
255
|
}
|
|
179
256
|
if (input.background) {
|
|
180
257
|
const result = await bash.startBackground(input.command);
|
|
181
|
-
return
|
|
258
|
+
return formatResult(result);
|
|
182
259
|
}
|
|
183
260
|
// 3-3: compute canonical form BEFORE running so we can attach an
|
|
184
261
|
// inline reminder if it matches the previous bash call.
|
|
@@ -213,9 +290,9 @@ export function createBuiltinTools(bash, mode, opts) {
|
|
|
213
290
|
const hint = chars >= 4_000
|
|
214
291
|
? ` — ${chars} chars cached; use bash_output_get(run_id, mode=tail|head|grep|lines) to re-query`
|
|
215
292
|
: "";
|
|
216
|
-
return `${formatted}\n\n[bash_run_id: ${result.bashRunId}${hint}]${reminder}
|
|
293
|
+
return `${formatted}\n\n[bash_run_id: ${result.bashRunId}${hint}]${reminder}`;
|
|
217
294
|
}
|
|
218
|
-
return
|
|
295
|
+
return formatted;
|
|
219
296
|
},
|
|
220
297
|
});
|
|
221
298
|
// bash_output_get — re-query the cached full output of a previous bash run.
|
|
@@ -344,6 +421,58 @@ export function createBuiltinTools(bash, mode, opts) {
|
|
|
344
421
|
};
|
|
345
422
|
},
|
|
346
423
|
});
|
|
424
|
+
// git_commit — the agent commits its OWN work with a message IT writes.
|
|
425
|
+
tools.git_commit = dynamicTool({
|
|
426
|
+
description: "Commit the files you have created/edited so far, with a commit message YOU write. Use this to commit each " +
|
|
427
|
+
"cohesive, working chunk the moment it is finished — and after EACH step of a multi-step plan — instead of " +
|
|
428
|
+
"leaving everything for one commit at the end. message: a clear conventional subject describing WHAT changed " +
|
|
429
|
+
"(e.g. 'feat(auth): add token refresh'), optionally followed by a body — NOT a restatement of the request. " +
|
|
430
|
+
"Only files you wrote via write_file/edit_file are staged (secrets + CLI artifacts are excluded, and the " +
|
|
431
|
+
"'Coding by - Muonroi-CLI' attribution line is appended automatically). No-op if none of your written files " +
|
|
432
|
+
"have uncommitted changes.",
|
|
433
|
+
inputSchema: jsonSchema({
|
|
434
|
+
type: "object",
|
|
435
|
+
properties: {
|
|
436
|
+
message: {
|
|
437
|
+
type: "string",
|
|
438
|
+
description: "Conventional commit subject (optionally + body) describing what changed, authored by you.",
|
|
439
|
+
},
|
|
440
|
+
},
|
|
441
|
+
required: ["message"],
|
|
442
|
+
}),
|
|
443
|
+
execute: async (input) => {
|
|
444
|
+
const message = typeof input?.message === "string" ? input.message.trim() : "";
|
|
445
|
+
if (message.length < 3) {
|
|
446
|
+
return { success: false, output: "git_commit requires a non-empty commit message." };
|
|
447
|
+
}
|
|
448
|
+
const written = fileTracker.writtenPaths();
|
|
449
|
+
if (written.length === 0) {
|
|
450
|
+
return {
|
|
451
|
+
success: false,
|
|
452
|
+
output: "Nothing to commit — you have not created or edited any file via write_file/edit_file this session.",
|
|
453
|
+
};
|
|
454
|
+
}
|
|
455
|
+
try {
|
|
456
|
+
const { commitSpecificPaths } = await import("../orchestrator/auto-commit.js");
|
|
457
|
+
const result = await commitSpecificPaths(bash.getCwd(), written, message);
|
|
458
|
+
if (!result.committed) {
|
|
459
|
+
// G1: when the LSP quality gate blocked the commit, surface the
|
|
460
|
+
// per-file errors so the agent can fix them and call git_commit again.
|
|
461
|
+
// Tell the agent to FIX the errors — do NOT advertise the bypass
|
|
462
|
+
// env here (that is a USER escape hatch; surfacing it to the agent
|
|
463
|
+
// just invites it to circumvent the gate instead of fixing).
|
|
464
|
+
const detail = result.reason === "lsp-errors"
|
|
465
|
+
? `\nStaged files have errors — fix them and call git_commit again:\n${result.detail ?? ""}`
|
|
466
|
+
: "";
|
|
467
|
+
return { success: false, output: `No commit made (${result.reason}).${detail}` };
|
|
468
|
+
}
|
|
469
|
+
return { success: true, output: `Committed ${result.fileCount} file(s) → ${result.sha}` };
|
|
470
|
+
}
|
|
471
|
+
catch (e) {
|
|
472
|
+
return { success: false, output: `git_commit failed: ${e instanceof Error ? e.message : String(e)}` };
|
|
473
|
+
}
|
|
474
|
+
},
|
|
475
|
+
});
|
|
347
476
|
// task
|
|
348
477
|
if (opts?.runTask) {
|
|
349
478
|
const runTask = opts.runTask;
|
|
@@ -358,15 +487,45 @@ export function createBuiltinTools(bash, mode, opts) {
|
|
|
358
487
|
},
|
|
359
488
|
description: { type: "string", description: "Short description of the task" },
|
|
360
489
|
prompt: { type: "string", description: "Detailed instructions for the sub-agent" },
|
|
490
|
+
maxToolRounds: {
|
|
491
|
+
type: "number",
|
|
492
|
+
description: "Optional maximum tool execution rounds (default is 12 for foreground tasks)",
|
|
493
|
+
},
|
|
361
494
|
},
|
|
362
495
|
required: ["agent", "description", "prompt"],
|
|
363
496
|
}),
|
|
364
497
|
execute: async (input) => {
|
|
365
|
-
const result = await runTask({
|
|
498
|
+
const result = await runTask({
|
|
499
|
+
agent: input.agent,
|
|
500
|
+
description: input.description,
|
|
501
|
+
prompt: input.prompt,
|
|
502
|
+
maxToolRounds: input.maxToolRounds,
|
|
503
|
+
});
|
|
366
504
|
return formatResult(result);
|
|
367
505
|
},
|
|
368
506
|
});
|
|
369
507
|
}
|
|
508
|
+
// consult_parent_session
|
|
509
|
+
if (opts?.consultParentSession) {
|
|
510
|
+
const consultParentSession = opts.consultParentSession;
|
|
511
|
+
tools.consult_parent_session = dynamicTool({
|
|
512
|
+
description: "Consult the parent session for supervision or guidance when stuck, when needing clarification on the overall goal, or when encountering critical errors. ONLY available in sub-sessions.",
|
|
513
|
+
inputSchema: jsonSchema({
|
|
514
|
+
type: "object",
|
|
515
|
+
properties: {
|
|
516
|
+
question: {
|
|
517
|
+
type: "string",
|
|
518
|
+
description: "The specific question or issue you need the parent session to advise on.",
|
|
519
|
+
},
|
|
520
|
+
},
|
|
521
|
+
required: ["question"],
|
|
522
|
+
}),
|
|
523
|
+
execute: async (input) => {
|
|
524
|
+
const result = await consultParentSession(input.question);
|
|
525
|
+
return result;
|
|
526
|
+
},
|
|
527
|
+
});
|
|
528
|
+
}
|
|
370
529
|
// delegate
|
|
371
530
|
if (opts?.runDelegation) {
|
|
372
531
|
const runDelegation = opts.runDelegation;
|
|
@@ -378,6 +537,10 @@ export function createBuiltinTools(bash, mode, opts) {
|
|
|
378
537
|
agent: { type: "string", description: "Sub-agent type (usually 'explore')" },
|
|
379
538
|
description: { type: "string", description: "Short description of the research task" },
|
|
380
539
|
prompt: { type: "string", description: "Detailed research instructions" },
|
|
540
|
+
maxToolRounds: {
|
|
541
|
+
type: "number",
|
|
542
|
+
description: "Optional maximum tool execution rounds (default is 60 for explore delegations)",
|
|
543
|
+
},
|
|
381
544
|
},
|
|
382
545
|
required: ["agent", "description", "prompt"],
|
|
383
546
|
}),
|
|
@@ -386,6 +549,7 @@ export function createBuiltinTools(bash, mode, opts) {
|
|
|
386
549
|
agent: input.agent,
|
|
387
550
|
description: input.description,
|
|
388
551
|
prompt: input.prompt,
|
|
552
|
+
maxToolRounds: input.maxToolRounds,
|
|
389
553
|
});
|
|
390
554
|
return formatResult(result);
|
|
391
555
|
},
|
|
@@ -424,6 +588,24 @@ export function createBuiltinTools(bash, mode, opts) {
|
|
|
424
588
|
},
|
|
425
589
|
});
|
|
426
590
|
}
|
|
591
|
+
// delegation_kill
|
|
592
|
+
if (opts?.killDelegation) {
|
|
593
|
+
const killDelegation = opts.killDelegation;
|
|
594
|
+
tools.delegation_kill = dynamicTool({
|
|
595
|
+
description: "Terminate a running background delegation/subagent by ID.",
|
|
596
|
+
inputSchema: jsonSchema({
|
|
597
|
+
type: "object",
|
|
598
|
+
properties: {
|
|
599
|
+
id: { type: "string", description: "Delegation ID to terminate" },
|
|
600
|
+
},
|
|
601
|
+
required: ["id"],
|
|
602
|
+
}),
|
|
603
|
+
execute: async (input) => {
|
|
604
|
+
const result = await killDelegation(input.id);
|
|
605
|
+
return formatResult(result);
|
|
606
|
+
},
|
|
607
|
+
});
|
|
608
|
+
}
|
|
427
609
|
// ee_query — semantic recall over the Experience Engine brain. This is the
|
|
428
610
|
// in-CLI counterpart of the MCP `ee.query` tool (src/mcp/ee-tools.ts):
|
|
429
611
|
// without it, the Agent Operating Contract + checkpoint reminders instruct
|
|
@@ -493,7 +675,13 @@ export function createBuiltinTools(bash, mode, opts) {
|
|
|
493
675
|
return "[ee_unavailable] Experience Engine returned no response (server down, timeout, circuit open, or unconfigured) and the artifact is not in this session's local cache. Proceed without EE recall — re-read the source directly if you need the elided content.";
|
|
494
676
|
}
|
|
495
677
|
recordRehydration("ee");
|
|
496
|
-
|
|
678
|
+
const points = resp.points ?? [];
|
|
679
|
+
const bestHit = points[0];
|
|
680
|
+
if (bestHit) {
|
|
681
|
+
const matchedId = query.match(/(?:id\s*=\s*|id\s*\b)([a-zA-Z0-9_:-]+)/i)?.[1] ?? "unknown";
|
|
682
|
+
return truncateOutput(`[tool-artifact id=${matchedId} — rehydrated from Experience Engine]\n${bestHit.text}`);
|
|
683
|
+
}
|
|
684
|
+
return `[tool-artifact — not found in Experience Engine]`;
|
|
497
685
|
}
|
|
498
686
|
// General recall → /api/recall (recallMode, [id col] index + surface).
|
|
499
687
|
const { recallEE, formatRecallForAgent } = await import("../ee/search.js");
|
|
@@ -503,6 +691,20 @@ export function createBuiltinTools(bash, mode, opts) {
|
|
|
503
691
|
if (resp === null) {
|
|
504
692
|
return "[ee_unavailable] Experience Engine returned no response (server down, timeout, circuit open, or unconfigured). Proceed without EE recall — re-read the source directly if you need the elided content.";
|
|
505
693
|
}
|
|
694
|
+
// Record recalled entries as pending feedback debt in the same in-process
|
|
695
|
+
// ledger the native ee_feedback builtin clears, so an in-CLI active recall
|
|
696
|
+
// accrues a verdict obligation exactly like the external MCP ee.query does
|
|
697
|
+
// (mcp/ee-tools.ts). Layer 3 surfaces this debt as a reminder on the next
|
|
698
|
+
// enriched turn; without it the in-CLI recall arm was write-only and
|
|
699
|
+
// ee_feedback.clear() was a no-op.
|
|
700
|
+
try {
|
|
701
|
+
const { sessionRecallLedger, isRecallLedgerEnabled } = await import("../ee/recall-ledger.js");
|
|
702
|
+
if (isRecallLedgerEnabled())
|
|
703
|
+
sessionRecallLedger.record(resp.entries, query);
|
|
704
|
+
}
|
|
705
|
+
catch (err) {
|
|
706
|
+
console.error(`[tools:ee_query] recall-ledger record failed: ${err?.message}`);
|
|
707
|
+
}
|
|
506
708
|
// Compact ranked `[id col]` index, not a JSON dump — the recallMode text
|
|
507
709
|
// is ~30k (wide net), so JSON.stringify wasted the budget on escaping.
|
|
508
710
|
return truncateOutput(formatRecallForAgent(resp, {
|
|
@@ -33,6 +33,7 @@ export interface TaskRequest {
|
|
|
33
33
|
agent: BuiltinSubagentId | string;
|
|
34
34
|
description: string;
|
|
35
35
|
prompt: string;
|
|
36
|
+
maxToolRounds?: number;
|
|
36
37
|
}
|
|
37
38
|
export interface TaskRun {
|
|
38
39
|
agent: string;
|
|
@@ -207,6 +208,7 @@ export interface ChatEntry {
|
|
|
207
208
|
toolResult?: ToolResult;
|
|
208
209
|
toolGroup?: ToolGroup;
|
|
209
210
|
structuredResponse?: StructuredResponse;
|
|
211
|
+
reasoning?: string;
|
|
210
212
|
}
|
|
211
213
|
export interface PaymentPrecheck {
|
|
212
214
|
security?: string;
|
|
@@ -221,13 +223,17 @@ export interface StructuredResponse {
|
|
|
221
223
|
taskType: string;
|
|
222
224
|
data: Record<string, unknown>;
|
|
223
225
|
}
|
|
224
|
-
export type CouncilQuestionPhase = "clarify" | "preflight" | "plan-confirm" | "post-debate" | "pil-interview" | "pil-acceptance" | "tool-loop-cap";
|
|
226
|
+
export type CouncilQuestionPhase = "clarify" | "preflight" | "plan-confirm" | "post-debate" | "pil-interview" | "pil-acceptance" | "tool-loop-cap" | "safety-override";
|
|
225
227
|
export type CouncilOptionKind = "choice" | "freetext" | "chat";
|
|
226
228
|
export interface CouncilQuestionOption {
|
|
227
229
|
label: string;
|
|
228
230
|
description?: string;
|
|
229
231
|
value: string;
|
|
230
232
|
kind: CouncilOptionKind;
|
|
233
|
+
/** Model-driven: if true, picking this cancels the whole interaction (discovery) */
|
|
234
|
+
isCancel?: boolean;
|
|
235
|
+
/** Model-driven: if true, picking this triggers a re-interview round (discovery) */
|
|
236
|
+
isAdjust?: boolean;
|
|
231
237
|
}
|
|
232
238
|
export interface CouncilQuestionData {
|
|
233
239
|
questionId: string;
|
|
@@ -413,7 +419,7 @@ export interface ModelInfo {
|
|
|
413
419
|
supportsVision?: boolean;
|
|
414
420
|
}
|
|
415
421
|
export type AgentMode = "agent" | "plan" | "ask";
|
|
416
|
-
export type SessionStatus = "active" | "completed" | "archived";
|
|
422
|
+
export type SessionStatus = "active" | "completed" | "archived" | "abandoned";
|
|
417
423
|
export type UsageSource = "message" | "title" | "task" | "delegation" | "council" | "other";
|
|
418
424
|
export interface WorkspaceInfo {
|
|
419
425
|
id: string;
|
package/dist/src/ui/app.d.ts
CHANGED
|
@@ -14,6 +14,7 @@ import "./slash/council.js";
|
|
|
14
14
|
import "./slash/ideal.js";
|
|
15
15
|
import "./slash/export.js";
|
|
16
16
|
import "./slash/status.js";
|
|
17
|
+
import "./slash/ponytail.js";
|
|
17
18
|
import type { AppProps } from "./types.js";
|
|
18
19
|
export type { AppStartupConfig } from "./types.js";
|
|
19
20
|
export declare function App({ agent, startupConfig, initialMessage, onExit, onRelaunch }: AppProps): import("react").ReactNode;
|
package/dist/src/ui/app.js
CHANGED
|
Binary file
|
|
@@ -8,6 +8,7 @@ const PHASE_LABEL = {
|
|
|
8
8
|
"pil-interview": "Understanding",
|
|
9
9
|
"pil-acceptance": "Confirm Intent",
|
|
10
10
|
"tool-loop-cap": "Tool-loop guard",
|
|
11
|
+
"safety-override": "Safety override",
|
|
11
12
|
};
|
|
12
13
|
/**
|
|
13
14
|
* AskUserQuestion-style card. Controlled component — parent owns the state
|