muonroi-cli 1.4.1 → 1.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -21
- package/README.md +122 -122
- package/dist/packages/agent-harness-core/src/predicate.d.ts +1 -1
- package/dist/src/agent-harness/__tests__/mock-model.spec.js +48 -1
- package/dist/src/agent-harness/mock-model.d.ts +11 -0
- package/dist/src/agent-harness/mock-model.js +21 -0
- package/dist/src/cli/cost-forensics.js +12 -12
- package/dist/src/council/__tests__/clarification-prompt.test.js +51 -0
- package/dist/src/council/__tests__/clarifier-ready-gate.test.js +32 -0
- package/dist/src/council/__tests__/decisions-lock.test.js +17 -1
- package/dist/src/council/__tests__/oauth-reachable.test.d.ts +1 -0
- package/dist/src/council/__tests__/oauth-reachable.test.js +31 -0
- package/dist/src/council/__tests__/parse-outcome-fallback.test.js +11 -0
- package/dist/src/council/clarifier.js +9 -1
- package/dist/src/council/debate.js +5 -1
- package/dist/src/council/decisions-lock.js +3 -3
- package/dist/src/council/index.js +12 -5
- package/dist/src/council/leader.d.ts +0 -17
- package/dist/src/council/leader.js +22 -15
- package/dist/src/council/planner.js +1 -1
- package/dist/src/council/prompts.js +63 -57
- package/dist/src/council/types.d.ts +7 -0
- package/dist/src/ee/__tests__/ee-onboarding.test.d.ts +1 -0
- package/dist/src/ee/__tests__/ee-onboarding.test.js +32 -0
- package/dist/src/ee/artifact-cache.d.ts +56 -0
- package/dist/src/ee/artifact-cache.js +155 -0
- package/dist/src/ee/artifact-cache.test.d.ts +1 -0
- package/dist/src/ee/artifact-cache.test.js +69 -0
- package/dist/src/ee/auth.d.ts +9 -0
- package/dist/src/ee/auth.js +19 -0
- package/dist/src/ee/ee-onboarding.d.ts +5 -0
- package/dist/src/ee/ee-onboarding.js +76 -0
- package/dist/src/ee/search.js +7 -5
- package/dist/src/ee/search.test.d.ts +1 -0
- package/dist/src/ee/search.test.js +23 -0
- package/dist/src/generated/version.d.ts +1 -1
- package/dist/src/generated/version.js +1 -1
- package/dist/src/headless/output.js +6 -4
- package/dist/src/headless/output.test.js +4 -3
- package/dist/src/index.js +20 -1
- package/dist/src/mcp/__tests__/auto-setup.test.js +74 -0
- package/dist/src/mcp/__tests__/client-pool.spec.d.ts +1 -0
- package/dist/src/mcp/__tests__/client-pool.spec.js +98 -0
- package/dist/src/mcp/__tests__/parallel-build.spec.d.ts +1 -0
- package/dist/src/mcp/__tests__/parallel-build.spec.js +67 -0
- package/dist/src/mcp/__tests__/smart-filter.test.js +56 -0
- package/dist/src/mcp/auto-setup.js +56 -2
- package/dist/src/mcp/client-pool.d.ts +46 -0
- package/dist/src/mcp/client-pool.js +212 -0
- package/dist/src/mcp/oauth-callback.js +2 -2
- package/dist/src/mcp/parse-headers.test.js +14 -14
- package/dist/src/mcp/runtime.d.ts +28 -0
- package/dist/src/mcp/runtime.js +117 -51
- package/dist/src/mcp/self-verify-runner.d.ts +14 -0
- package/dist/src/mcp/self-verify-runner.js +38 -0
- package/dist/src/mcp/setup-guide-text.d.ts +9 -0
- package/dist/src/mcp/setup-guide-text.js +84 -0
- package/dist/src/mcp/smart-filter.js +49 -0
- package/dist/src/mcp/smoke.test.js +43 -43
- package/dist/src/mcp/tools-server.d.ts +7 -0
- package/dist/src/mcp/tools-server.js +19 -22
- package/dist/src/models/catalog.json +349 -349
- package/dist/src/ops/__tests__/doctor-ee-health.test.js +21 -0
- package/dist/src/ops/doctor.d.ts +3 -2
- package/dist/src/ops/doctor.js +47 -11
- package/dist/src/ops/doctor.test.js +4 -3
- package/dist/src/orchestrator/__tests__/mcp-capability-block.test.d.ts +1 -0
- package/dist/src/orchestrator/__tests__/mcp-capability-block.test.js +39 -0
- package/dist/src/orchestrator/__tests__/project-stack.test.d.ts +1 -0
- package/dist/src/orchestrator/__tests__/project-stack.test.js +65 -0
- package/dist/src/orchestrator/batch-turn-runner.js +7 -11
- package/dist/src/orchestrator/compaction.d.ts +2 -0
- package/dist/src/orchestrator/compaction.js +14 -1
- package/dist/src/orchestrator/compaction.test.js +25 -1
- package/dist/src/orchestrator/message-processor.js +72 -32
- package/dist/src/orchestrator/orchestrator.js +26 -0
- package/dist/src/orchestrator/prompts.d.ts +51 -0
- package/dist/src/orchestrator/prompts.js +257 -134
- package/dist/src/orchestrator/scope-ceiling.js +6 -1
- package/dist/src/orchestrator/scope-reminder.d.ts +12 -0
- package/dist/src/orchestrator/scope-reminder.js +16 -0
- package/dist/src/orchestrator/scope-reminder.test.js +22 -1
- package/dist/src/orchestrator/stream-runner.js +23 -15
- package/dist/src/orchestrator/subagent-compactor.d.ts +14 -5
- package/dist/src/orchestrator/subagent-compactor.js +30 -8
- package/dist/src/orchestrator/subagent-compactor.spec.js +18 -0
- package/dist/src/orchestrator/text-tool-call-detector.test.js +13 -13
- package/dist/src/pil/__tests__/clarity-gate.test.js +24 -215
- package/dist/src/pil/__tests__/config.test.js +1 -17
- package/dist/src/pil/__tests__/discovery.test.js +144 -11
- package/dist/src/pil/__tests__/layer1-intent-trace.test.js +7 -2
- package/dist/src/pil/__tests__/layer1-intent.test.js +3 -0
- package/dist/src/pil/__tests__/layer16-clarity.test.js +32 -116
- package/dist/src/pil/__tests__/layer4-gsd.test.js +37 -0
- package/dist/src/pil/__tests__/layer6-output.test.js +158 -18
- package/dist/src/pil/__tests__/llm-classify.test.js +49 -2
- package/dist/src/pil/__tests__/surface-compaction-artifacts.test.d.ts +1 -0
- package/dist/src/pil/__tests__/surface-compaction-artifacts.test.js +112 -0
- package/dist/src/pil/agent-operating-contract.d.ts +1 -1
- package/dist/src/pil/agent-operating-contract.js +2 -0
- package/dist/src/pil/agent-operating-contract.test.js +7 -2
- package/dist/src/pil/cheap-model-playbook.js +35 -35
- package/dist/src/pil/cheap-model-workbooks.js +16 -13
- package/dist/src/pil/clarity-gate.d.ts +21 -19
- package/dist/src/pil/clarity-gate.js +26 -153
- package/dist/src/pil/config.d.ts +9 -1
- package/dist/src/pil/config.js +15 -4
- package/dist/src/pil/discovery.js +211 -136
- package/dist/src/pil/layer1-intent.d.ts +12 -0
- package/dist/src/pil/layer1-intent.js +283 -38
- package/dist/src/pil/layer1-intent.test.js +210 -4
- package/dist/src/pil/layer16-clarity.d.ts +25 -11
- package/dist/src/pil/layer16-clarity.js +19 -306
- package/dist/src/pil/layer3-ee-injection.d.ts +19 -0
- package/dist/src/pil/layer3-ee-injection.js +96 -4
- package/dist/src/pil/layer4-gsd.js +18 -6
- package/dist/src/pil/layer6-output.d.ts +2 -0
- package/dist/src/pil/layer6-output.js +151 -25
- package/dist/src/pil/llm-classify.d.ts +26 -0
- package/dist/src/pil/llm-classify.js +34 -5
- package/dist/src/pil/native-capabilities-workbook.d.ts +1 -1
- package/dist/src/pil/native-capabilities-workbook.js +82 -76
- package/dist/src/pil/pipeline.js +15 -9
- package/dist/src/pil/schema.d.ts +8 -0
- package/dist/src/pil/schema.js +12 -1
- package/dist/src/pil/task-tier-map.js +4 -0
- package/dist/src/pil/types.d.ts +11 -1
- package/dist/src/product-loop/done-gate.js +3 -3
- package/dist/src/product-loop/loop-driver.js +18 -18
- package/dist/src/product-loop/progress-snapshot.js +4 -4
- package/dist/src/providers/auth/gemini-oauth.js +6 -15
- package/dist/src/providers/auth/grok-oauth.js +6 -15
- package/dist/src/providers/auth/openai-oauth.js +6 -15
- package/dist/src/providers/mcp-vision-bridge.js +48 -48
- package/dist/src/reporter/index.js +1 -1
- package/dist/src/scaffold/bb-ecosystem-apply.js +47 -47
- package/dist/src/scaffold/bb-quality-gate.js +5 -5
- package/dist/src/scaffold/continuation-prompt.js +60 -60
- package/dist/src/scaffold/init-new.js +453 -453
- package/dist/src/self-qa/__tests__/scenario-planner.test.js +3 -3
- package/dist/src/self-qa/agentic-loop.js +24 -19
- package/dist/src/self-qa/spec-emitter.js +26 -23
- package/dist/src/storage/__tests__/migrations.test.js +2 -2
- package/dist/src/storage/interaction-log.js +5 -5
- package/dist/src/storage/migrations.js +122 -122
- package/dist/src/storage/sessions.js +42 -42
- package/dist/src/storage/transcript.js +91 -84
- package/dist/src/storage/usage.js +14 -14
- package/dist/src/storage/workspaces.js +12 -12
- package/dist/src/tools/__tests__/native-tools.test.d.ts +1 -0
- package/dist/src/tools/__tests__/native-tools.test.js +53 -0
- package/dist/src/tools/git-safety.d.ts +61 -0
- package/dist/src/tools/git-safety.js +141 -0
- package/dist/src/tools/git-safety.test.d.ts +1 -0
- package/dist/src/tools/git-safety.test.js +111 -0
- package/dist/src/tools/native-tools.d.ts +31 -0
- package/dist/src/tools/native-tools.js +273 -0
- package/dist/src/tools/registry-ee-query.test.js +18 -1
- package/dist/src/tools/registry-git-safety.test.d.ts +7 -0
- package/dist/src/tools/registry-git-safety.test.js +92 -0
- package/dist/src/tools/registry.js +52 -6
- package/dist/src/ui/__tests__/markdown-render.test.d.ts +1 -0
- package/dist/src/ui/__tests__/markdown-render.test.js +48 -0
- package/dist/src/ui/app.js +0 -0
- package/dist/src/ui/components/message-view.js +4 -1
- package/dist/src/ui/components/structured-response-view.js +7 -3
- package/dist/src/ui/components/tool-group.js +7 -1
- package/dist/src/ui/markdown-render.d.ts +41 -0
- package/dist/src/ui/markdown-render.js +223 -0
- package/dist/src/ui/markdown.d.ts +10 -0
- package/dist/src/ui/markdown.js +12 -35
- package/dist/src/ui/slash/council-inspect.js +4 -4
- package/dist/src/ui/slash/export.js +4 -4
- package/dist/src/ui/utils/text.d.ts +8 -0
- package/dist/src/ui/utils/text.js +16 -0
- package/dist/src/ui/utils/text.test.d.ts +1 -0
- package/dist/src/ui/utils/text.test.js +23 -0
- package/dist/src/usage/ledger.js +48 -15
- package/dist/src/utils/__tests__/footprint-gitignore.test.d.ts +1 -0
- package/dist/src/utils/__tests__/footprint-gitignore.test.js +50 -0
- package/dist/src/utils/clipboard-image.js +23 -23
- package/dist/src/utils/open-url.d.ts +56 -0
- package/dist/src/utils/open-url.js +58 -0
- package/dist/src/utils/open-url.test.d.ts +1 -0
- package/dist/src/utils/open-url.test.js +86 -0
- package/dist/src/utils/settings.d.ts +12 -0
- package/dist/src/utils/settings.js +48 -0
- package/dist/src/utils/side-question.js +2 -2
- package/dist/src/utils/skills.js +3 -3
- package/dist/src/verify/__tests__/coverage-parsers.test.js +30 -30
- package/dist/src/verify/environment.js +2 -1
- package/package.json +1 -1
- package/dist/src/pil/layer16-clarity.test.js +0 -31
- /package/dist/src/{pil/layer16-clarity.test.d.ts → council/__tests__/clarification-prompt.test.d.ts} +0 -0
|
@@ -84,6 +84,27 @@ describe("doctor EE health checks (CQ-16c/16d)", () => {
|
|
|
84
84
|
expect(eeHealth?.status).toBe("warn");
|
|
85
85
|
expect(eeHealth?.detail).toContain("72.61.127.154");
|
|
86
86
|
});
|
|
87
|
+
it("ee.health does NOT report unreachable when server is up but gates degraded (VERIFY F9)", async () => {
|
|
88
|
+
// Live ee_query works (server reachable) yet the gates sub-check fails —
|
|
89
|
+
// doctor must not call this "unreachable" (false negative). server.ok is
|
|
90
|
+
// the reachability signal, not result.ok.
|
|
91
|
+
healthDetailedMock.mockResolvedValue({
|
|
92
|
+
ok: false,
|
|
93
|
+
status: 200,
|
|
94
|
+
mode: "thin-client",
|
|
95
|
+
circuit: "closed",
|
|
96
|
+
components: {
|
|
97
|
+
server: { ok: true, status: 200 },
|
|
98
|
+
gates: { ok: false, status: 0 },
|
|
99
|
+
},
|
|
100
|
+
});
|
|
101
|
+
const results = await runDoctor();
|
|
102
|
+
const eeHealth = results.find((r) => r.name === "ee.health");
|
|
103
|
+
expect(eeHealth?.status).toBe("warn");
|
|
104
|
+
expect(eeHealth?.detail).not.toContain("unreachable");
|
|
105
|
+
expect(eeHealth?.detail).toContain("server=ok");
|
|
106
|
+
expect(eeHealth?.detail.toLowerCase()).toContain("gates");
|
|
107
|
+
});
|
|
87
108
|
it("ee.health warns gracefully when healthDetailed throws", async () => {
|
|
88
109
|
healthDetailedMock.mockRejectedValue(new Error("network timeout"));
|
|
89
110
|
const results = await runDoctor();
|
package/dist/src/ops/doctor.d.ts
CHANGED
|
@@ -2,9 +2,10 @@
|
|
|
2
2
|
* src/ops/doctor.ts
|
|
3
3
|
*
|
|
4
4
|
* Health check runner for muonroi-cli doctor command.
|
|
5
|
-
* Runs
|
|
5
|
+
* Runs 10 named checks and returns pass/warn/fail results.
|
|
6
6
|
*
|
|
7
|
-
* Checks: bun_version, os, key_presence, ollama, ee,
|
|
7
|
+
* Checks: bun_version, os, key_presence, ollama, dotnet, ee.health, ee.brain,
|
|
8
|
+
* qdrant, error_rate, council.mcp
|
|
8
9
|
* Never throws — all checks handle errors gracefully (warn, not crash).
|
|
9
10
|
*/
|
|
10
11
|
export interface CheckResult {
|
package/dist/src/ops/doctor.js
CHANGED
|
@@ -2,11 +2,13 @@
|
|
|
2
2
|
* src/ops/doctor.ts
|
|
3
3
|
*
|
|
4
4
|
* Health check runner for muonroi-cli doctor command.
|
|
5
|
-
* Runs
|
|
5
|
+
* Runs 10 named checks and returns pass/warn/fail results.
|
|
6
6
|
*
|
|
7
|
-
* Checks: bun_version, os, key_presence, ollama, ee,
|
|
7
|
+
* Checks: bun_version, os, key_presence, ollama, dotnet, ee.health, ee.brain,
|
|
8
|
+
* qdrant, error_rate, council.mcp
|
|
8
9
|
* Never throws — all checks handle errors gracefully (warn, not crash).
|
|
9
10
|
*/
|
|
11
|
+
import { spawnSync } from "node:child_process";
|
|
10
12
|
import { readFile } from "fs/promises";
|
|
11
13
|
import os from "os";
|
|
12
14
|
import path from "path";
|
|
@@ -116,7 +118,6 @@ async function checkEEDetailed() {
|
|
|
116
118
|
const result = await healthDetailed();
|
|
117
119
|
const serverOk = result.components.server.ok;
|
|
118
120
|
const gatesOk = result.components.gates?.ok ?? true; // null if local mode
|
|
119
|
-
const isHealthy = result.ok;
|
|
120
121
|
const parts = [
|
|
121
122
|
`mode=${result.mode}`,
|
|
122
123
|
`circuit=${result.circuit}`,
|
|
@@ -125,7 +126,11 @@ async function checkEEDetailed() {
|
|
|
125
126
|
if (result.components.gates !== null) {
|
|
126
127
|
parts.push(`gates=${gatesOk ? "ok" : `fail(${result.components.gates.status})`}`);
|
|
127
128
|
}
|
|
128
|
-
|
|
129
|
+
// Reachability is the SERVER component, not result.ok. A failing gates
|
|
130
|
+
// sub-check (e.g. read-token scope in thin-client mode) does NOT mean the
|
|
131
|
+
// EE server is unreachable — labelling it "unreachable" is a false negative
|
|
132
|
+
// that contradicts a live ee_query working. See VERIFY F9.
|
|
133
|
+
if (!serverOk) {
|
|
129
134
|
const hint = result.mode === "thin-client"
|
|
130
135
|
? "Hint: check VPS 72.61.127.154:8082 is reachable; verify ~/.experience/config.json serverBaseUrl + serverReadAuthToken"
|
|
131
136
|
: "Hint: start EE locally or configure thin-client in ~/.experience/config.json";
|
|
@@ -135,6 +140,13 @@ async function checkEEDetailed() {
|
|
|
135
140
|
detail: `EE unreachable — ${parts.join(", ")}. ${hint}`,
|
|
136
141
|
};
|
|
137
142
|
}
|
|
143
|
+
if (!gatesOk) {
|
|
144
|
+
return {
|
|
145
|
+
name: "ee.health",
|
|
146
|
+
status: "warn",
|
|
147
|
+
detail: `EE reachable; gates check degraded — ${parts.join(", ")}. Hint: gates needs serverReadAuthToken scope in ~/.experience/config.json`,
|
|
148
|
+
};
|
|
149
|
+
}
|
|
138
150
|
return {
|
|
139
151
|
name: "ee.health",
|
|
140
152
|
status: "pass",
|
|
@@ -157,9 +169,9 @@ async function checkBrainEmptiness() {
|
|
|
157
169
|
// Count ee_injection events with event_subtype='no_match' in last 30 days
|
|
158
170
|
const cutoff = new Date(Date.now() - 30 * 86_400_000).toISOString();
|
|
159
171
|
const row = db
|
|
160
|
-
.prepare(`SELECT COUNT(*) as cnt FROM interaction_logs
|
|
161
|
-
WHERE event_type = 'ee_injection'
|
|
162
|
-
AND event_subtype = 'no_match'
|
|
172
|
+
.prepare(`SELECT COUNT(*) as cnt FROM interaction_logs
|
|
173
|
+
WHERE event_type = 'ee_injection'
|
|
174
|
+
AND event_subtype = 'no_match'
|
|
163
175
|
AND created_at >= ?`)
|
|
164
176
|
.get(cutoff);
|
|
165
177
|
const noMatchCount = row?.cnt ?? 0;
|
|
@@ -193,6 +205,29 @@ async function checkBrainEmptiness() {
|
|
|
193
205
|
return { name: "ee.brain", status: "pass", detail: "brain check skipped (DB unavailable)" };
|
|
194
206
|
}
|
|
195
207
|
}
|
|
208
|
+
async function checkDotnet() {
|
|
209
|
+
// BB-aware scaffolding (muonroi-building-block) needs the .NET SDK for its
|
|
210
|
+
// restore/build/modular-boundaries quality gate. Doctor previously had no
|
|
211
|
+
// dotnet probe, so BB tasks had no preflight. See VERIFY F1.
|
|
212
|
+
try {
|
|
213
|
+
const res = spawnSync("dotnet", ["--version"], { encoding: "utf8", timeout: 5000 });
|
|
214
|
+
if (res.status === 0 && typeof res.stdout === "string" && res.stdout.trim().length > 0) {
|
|
215
|
+
return { name: "dotnet", status: "pass", detail: `dotnet ${res.stdout.trim()} — BB/.NET scaffold ready` };
|
|
216
|
+
}
|
|
217
|
+
return {
|
|
218
|
+
name: "dotnet",
|
|
219
|
+
status: "warn",
|
|
220
|
+
detail: "dotnet not found (optional — needed for muonroi-building-block scaffolding + quality gate)",
|
|
221
|
+
};
|
|
222
|
+
}
|
|
223
|
+
catch (err) {
|
|
224
|
+
return {
|
|
225
|
+
name: "dotnet",
|
|
226
|
+
status: "warn",
|
|
227
|
+
detail: `dotnet probe failed: ${err.message} (optional — needed for BB scaffolding)`,
|
|
228
|
+
};
|
|
229
|
+
}
|
|
230
|
+
}
|
|
196
231
|
async function checkQdrant() {
|
|
197
232
|
try {
|
|
198
233
|
const qdrantUrl = process.env.QDRANT_URL ?? "http://localhost:6333";
|
|
@@ -258,10 +293,10 @@ async function checkCouncilMcpNudge() {
|
|
|
258
293
|
// 2. Query DB for [Council Memory] records with URL or research topics
|
|
259
294
|
const db = getDatabase();
|
|
260
295
|
const rows = db
|
|
261
|
-
.prepare(`SELECT message_json FROM messages
|
|
262
|
-
WHERE role = 'system'
|
|
263
|
-
AND message_json LIKE '%[Council Memory]%'
|
|
264
|
-
ORDER BY created_at DESC
|
|
296
|
+
.prepare(`SELECT message_json FROM messages
|
|
297
|
+
WHERE role = 'system'
|
|
298
|
+
AND message_json LIKE '%[Council Memory]%'
|
|
299
|
+
ORDER BY created_at DESC
|
|
265
300
|
LIMIT 50`)
|
|
266
301
|
.all();
|
|
267
302
|
let qualifyingCount = 0;
|
|
@@ -323,6 +358,7 @@ export async function runDoctor() {
|
|
|
323
358
|
checkOS(),
|
|
324
359
|
checkKeyPresence(),
|
|
325
360
|
checkOllamaHealth(),
|
|
361
|
+
checkDotnet(), // NEW — VERIFY F1: BB/.NET scaffold preflight
|
|
326
362
|
checkEEDetailed(), // replaces checkEE() — CQ-16c
|
|
327
363
|
checkBrainEmptiness(), // NEW — CQ-16d
|
|
328
364
|
checkQdrant(),
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
2
2
|
// RED phase: import module under test (will fail until doctor.ts is created)
|
|
3
3
|
import { formatDoctorReport, runDoctor } from "./doctor.js";
|
|
4
|
-
describe("doctor — runDoctor returns
|
|
4
|
+
describe("doctor — runDoctor returns 10 checks", () => {
|
|
5
5
|
beforeEach(() => {
|
|
6
6
|
// Mock fetch to avoid real network calls in tests
|
|
7
7
|
vi.stubGlobal("fetch", vi.fn().mockResolvedValue({ ok: false, status: 503 }));
|
|
@@ -10,9 +10,9 @@ describe("doctor — runDoctor returns 9 checks", () => {
|
|
|
10
10
|
vi.unstubAllGlobals();
|
|
11
11
|
vi.restoreAllMocks();
|
|
12
12
|
});
|
|
13
|
-
it("returns exactly
|
|
13
|
+
it("returns exactly 10 CheckResult entries (dotnet added in VERIFY F1)", async () => {
|
|
14
14
|
const results = await runDoctor();
|
|
15
|
-
expect(results).toHaveLength(
|
|
15
|
+
expect(results).toHaveLength(10);
|
|
16
16
|
});
|
|
17
17
|
it("each CheckResult has valid name, status, and detail fields", async () => {
|
|
18
18
|
const results = await runDoctor();
|
|
@@ -32,6 +32,7 @@ describe("doctor — runDoctor returns 9 checks", () => {
|
|
|
32
32
|
expect(names).toContain("os");
|
|
33
33
|
expect(names).toContain("key_presence");
|
|
34
34
|
expect(names).toContain("ollama");
|
|
35
|
+
expect(names).toContain("dotnet");
|
|
35
36
|
expect(names).toContain("ee.health");
|
|
36
37
|
expect(names).toContain("ee.brain");
|
|
37
38
|
expect(names).toContain("qdrant");
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import { buildMcpCapabilityBlock } from "../prompts.js";
|
|
3
|
+
describe("buildMcpCapabilityBlock", () => {
|
|
4
|
+
it("returns '' when no MCP tools are connected (non-agent / chitchat / no-client-tools turns add nothing)", () => {
|
|
5
|
+
expect(buildMcpCapabilityBlock([])).toBe("");
|
|
6
|
+
expect(buildMcpCapabilityBlock(["read_file", "grep", "bash", "edit_file"])).toBe("");
|
|
7
|
+
});
|
|
8
|
+
it("names the exact callable mcp_<server>__<tool> tools connected this turn (regression: session f6f7881a5fae)", () => {
|
|
9
|
+
const block = buildMcpCapabilityBlock([
|
|
10
|
+
"read_file",
|
|
11
|
+
"bash",
|
|
12
|
+
"mcp_muonroi-docs__setup_guide",
|
|
13
|
+
"mcp_muonroi-docs__docs_search",
|
|
14
|
+
]);
|
|
15
|
+
// The failure was the agent not knowing it could call setup_guide directly.
|
|
16
|
+
expect(block).toContain("mcp_muonroi-docs__setup_guide");
|
|
17
|
+
expect(block).toContain("mcp_muonroi-docs__docs_search");
|
|
18
|
+
expect(block).toMatch(/CONNECTED MCP TOOLS/);
|
|
19
|
+
// Steers away from the bash-JSON-RPC fallback the agent actually did.
|
|
20
|
+
expect(block).toMatch(/do NOT shell out to bash/i);
|
|
21
|
+
});
|
|
22
|
+
it("groups tools by server (id with a hyphen split on the first '__')", () => {
|
|
23
|
+
const block = buildMcpCapabilityBlock([
|
|
24
|
+
"mcp_muonroi-docs__setup_guide",
|
|
25
|
+
"mcp_context7__query_docs",
|
|
26
|
+
"mcp_muonroi-docs__docs_search",
|
|
27
|
+
]);
|
|
28
|
+
// muonroi-docs appears once as a group header with both its tools.
|
|
29
|
+
expect(block.match(/muonroi-docs:/g)?.length).toBe(1);
|
|
30
|
+
expect(block).toMatch(/context7:/);
|
|
31
|
+
});
|
|
32
|
+
it("ignores non-mcp tool names and is deterministic (tools sorted within a server)", () => {
|
|
33
|
+
const block = buildMcpCapabilityBlock(["mcp_srv__b_tool", "write_file", "mcp_srv__a_tool"]);
|
|
34
|
+
expect(block).not.toContain("write_file");
|
|
35
|
+
// a_tool sorts before b_tool → stable output regardless of input order.
|
|
36
|
+
expect(block.indexOf("mcp_srv__a_tool")).toBeLessThan(block.indexOf("mcp_srv__b_tool"));
|
|
37
|
+
});
|
|
38
|
+
});
|
|
39
|
+
//# sourceMappingURL=mcp-capability-block.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { mkdtempSync, rmSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { tmpdir } from "node:os";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import { describe, expect, it } from "vitest";
|
|
5
|
+
import { detectProjectStack } from "../prompts.js";
|
|
6
|
+
// detectProjectStack feeds the ENVIRONMENT block so every model — in any mode,
|
|
7
|
+
// on any provider — knows the concrete stack of the repo it is running inside,
|
|
8
|
+
// instead of assuming Python / asking the user to describe the project
|
|
9
|
+
// (2026-06-14 dogfood: "model native doesn't know what it can do in the CLI").
|
|
10
|
+
describe("detectProjectStack", () => {
|
|
11
|
+
const mkTemp = (slug) => mkdtempSync(join(tmpdir(), `mr-stack-${slug}-`));
|
|
12
|
+
it("detects the current repo as a JS/TS project under git", () => {
|
|
13
|
+
const out = detectProjectStack(process.cwd());
|
|
14
|
+
expect(out).toMatch(/TypeScript|JavaScript/);
|
|
15
|
+
expect(out).toMatch(/vcs: git/);
|
|
16
|
+
});
|
|
17
|
+
it("returns empty string for a bare directory (greenfield)", () => {
|
|
18
|
+
const dir = mkTemp("empty");
|
|
19
|
+
try {
|
|
20
|
+
expect(detectProjectStack(dir)).toBe("");
|
|
21
|
+
}
|
|
22
|
+
finally {
|
|
23
|
+
rmSync(dir, { recursive: true, force: true });
|
|
24
|
+
}
|
|
25
|
+
});
|
|
26
|
+
it("detects a Rust project from Cargo.toml", () => {
|
|
27
|
+
const dir = mkTemp("rust");
|
|
28
|
+
try {
|
|
29
|
+
writeFileSync(join(dir, "Cargo.toml"), "[package]\nname = 'x'\n");
|
|
30
|
+
expect(detectProjectStack(dir)).toMatch(/^Rust/);
|
|
31
|
+
}
|
|
32
|
+
finally {
|
|
33
|
+
rmSync(dir, { recursive: true, force: true });
|
|
34
|
+
}
|
|
35
|
+
});
|
|
36
|
+
it("detects a .NET project from a .csproj file", () => {
|
|
37
|
+
const dir = mkTemp("net");
|
|
38
|
+
try {
|
|
39
|
+
writeFileSync(join(dir, "App.csproj"), "<Project/>");
|
|
40
|
+
expect(detectProjectStack(dir)).toMatch(/\.NET\/C#/);
|
|
41
|
+
}
|
|
42
|
+
finally {
|
|
43
|
+
rmSync(dir, { recursive: true, force: true });
|
|
44
|
+
}
|
|
45
|
+
});
|
|
46
|
+
it("reports package manager + test runner for a bun/vitest TS project", () => {
|
|
47
|
+
const dir = mkTemp("ts");
|
|
48
|
+
try {
|
|
49
|
+
writeFileSync(join(dir, "tsconfig.json"), "{}");
|
|
50
|
+
writeFileSync(join(dir, "bun.lock"), "");
|
|
51
|
+
writeFileSync(join(dir, "vitest.config.ts"), "export default {}");
|
|
52
|
+
const out = detectProjectStack(dir);
|
|
53
|
+
expect(out).toMatch(/TypeScript/);
|
|
54
|
+
expect(out).toMatch(/pkg: bun/);
|
|
55
|
+
expect(out).toMatch(/tests: vitest/);
|
|
56
|
+
}
|
|
57
|
+
finally {
|
|
58
|
+
rmSync(dir, { recursive: true, force: true });
|
|
59
|
+
}
|
|
60
|
+
});
|
|
61
|
+
it("returns empty (no throw) for a missing directory", () => {
|
|
62
|
+
expect(detectProjectStack(join(tmpdir(), "definitely-missing-dir-9f8a7b6c"))).toBe("");
|
|
63
|
+
});
|
|
64
|
+
});
|
|
65
|
+
//# sourceMappingURL=project-stack.test.js.map
|
|
@@ -25,9 +25,10 @@
|
|
|
25
25
|
// `recordUsage`, `appendCompletedTurn`, `discardAbortedTurn`,
|
|
26
26
|
// `getCompactedThisTurn` / `setCompactedThisTurn`, etc.) so a future
|
|
27
27
|
// `TurnRunnerDepsBase` hoist is mechanical.
|
|
28
|
-
import {
|
|
28
|
+
import { acquireMcpTools } from "../mcp/client-pool.js";
|
|
29
29
|
import { getProviderCapabilities } from "../providers/capabilities.js";
|
|
30
30
|
import { requireRuntimeProvider } from "../providers/runtime.js";
|
|
31
|
+
import { openUrl } from "../utils/open-url.js";
|
|
31
32
|
import { loadMcpServers } from "../utils/settings.js";
|
|
32
33
|
import { accumulateUsage, buildAssistantBatchMessage, buildBatchChatCompletionRequest, buildBatchName, buildToolBatchMessage, getBatchFinishReason, getBatchUsage, hasUsage, toLocalToolCall, } from "./batch-utils.js";
|
|
33
34
|
import { relaxCompactionSettings } from "./compaction.js";
|
|
@@ -104,17 +105,12 @@ export class BatchTurnRunner {
|
|
|
104
105
|
});
|
|
105
106
|
let tools = !batchCaps.supportsClientTools(runtime.modelInfo) ? {} : baseTools;
|
|
106
107
|
if (deps.mode === "agent" && batchCaps.supportsClientTools(runtime.modelInfo)) {
|
|
107
|
-
const mcpBundle = await
|
|
108
|
+
const mcpBundle = await acquireMcpTools(loadMcpServers(), {
|
|
108
109
|
onOAuthRequired: (_serverId, url) => {
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
: process.platform === "darwin"
|
|
114
|
-
? `open "${urlStr}"`
|
|
115
|
-
: `xdg-open "${urlStr}"`;
|
|
116
|
-
exec(cmd);
|
|
117
|
-
});
|
|
110
|
+
// Server-supplied URL is untrusted — openUrl validates the scheme
|
|
111
|
+
// and spawns via execFile (no shell), closing the command-injection
|
|
112
|
+
// vector the old exec() opener had.
|
|
113
|
+
openUrl(url);
|
|
118
114
|
},
|
|
119
115
|
});
|
|
120
116
|
closeMcp = mcpBundle.close;
|
|
@@ -23,6 +23,8 @@ export declare const DEFAULT_RESERVE_TOKENS = 16384;
|
|
|
23
23
|
export declare const DEFAULT_KEEP_RECENT_TOKENS = 20000;
|
|
24
24
|
export declare const POST_TURN_MIN_TOKENS = 2000;
|
|
25
25
|
export declare const COMPACTION_MAX_OUTPUT_TOKENS = 4096;
|
|
26
|
+
export declare const COMPACTION_META_MAX_OUTPUT_TOKENS = 1536;
|
|
27
|
+
export declare function metaCompactionMaxTokens(): number;
|
|
26
28
|
export declare const TOOL_RESULT_MAX_CHARS_CONFIGURABLE = 8000;
|
|
27
29
|
export declare const COMPACTION_SUMMARY_HEADER = "[Context checkpoint summary]";
|
|
28
30
|
export declare function extractUserContent(content: unknown): string;
|
|
@@ -10,6 +10,19 @@ export const DEFAULT_RESERVE_TOKENS = 16_384;
|
|
|
10
10
|
export const DEFAULT_KEEP_RECENT_TOKENS = 20_000;
|
|
11
11
|
export const POST_TURN_MIN_TOKENS = 2_000;
|
|
12
12
|
export const COMPACTION_MAX_OUTPUT_TOKENS = 4_096;
|
|
13
|
+
// Meta-analysis (agent/PIL self-eval) summaries are capped tighter than normal
|
|
14
|
+
// to prevent runaway summaries (session df2dbb878984: 73k input → 14k-char
|
|
15
|
+
// summary). Default 1536 (was a hard 1024) — modestly more fidelity now that
|
|
16
|
+
// anti-mù recovery (layer3 surfacing + the in-process/disk artifact cache)
|
|
17
|
+
// backstops detail loss, still ~2.3x below the 14k-char problem. Tune per machine
|
|
18
|
+
// with MUONROI_META_COMPACT_MAX_TOKENS (clamped 512..COMPACTION_MAX_OUTPUT_TOKENS).
|
|
19
|
+
export const COMPACTION_META_MAX_OUTPUT_TOKENS = 1_536;
|
|
20
|
+
export function metaCompactionMaxTokens() {
|
|
21
|
+
const raw = Number(process.env.MUONROI_META_COMPACT_MAX_TOKENS);
|
|
22
|
+
if (Number.isFinite(raw) && raw >= 512 && raw <= COMPACTION_MAX_OUTPUT_TOKENS)
|
|
23
|
+
return Math.floor(raw);
|
|
24
|
+
return COMPACTION_META_MAX_OUTPUT_TOKENS;
|
|
25
|
+
}
|
|
13
26
|
export const TOOL_RESULT_MAX_CHARS_CONFIGURABLE = 8000;
|
|
14
27
|
export const COMPACTION_SUMMARY_HEADER = "[Context checkpoint summary]";
|
|
15
28
|
const SUMMARIZATION_SYSTEM_PROMPT = `You are a context summarization assistant.
|
|
@@ -450,7 +463,7 @@ async function summarizeConversation(provider, modelId, messages, reserveTokens,
|
|
|
450
463
|
const userText = messages.map((m) => extractUserContent(m.content)).join("\n");
|
|
451
464
|
const isMeta = isMetaAnalysisPrompt(userText);
|
|
452
465
|
const effectiveMax = isMeta
|
|
453
|
-
? Math.min(
|
|
466
|
+
? Math.min(metaCompactionMaxTokens(), Math.max(512, Math.floor(reserveTokens * 0.5)))
|
|
454
467
|
: Math.min(COMPACTION_MAX_OUTPUT_TOKENS, Math.max(512, Math.floor(reserveTokens * 0.8)));
|
|
455
468
|
if (previousSummary) {
|
|
456
469
|
promptParts.push(`Existing summary:\n${previousSummary}`);
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { afterAll, beforeAll, describe, expect, it } from "vitest";
|
|
2
2
|
import { buildEffectiveTranscript } from "../storage/transcript-view.js";
|
|
3
|
-
import { COMPACTION_SUMMARY_HEADER, createCompactionSummaryMessage, findCutPoint, prepareCompaction, serializeConversation, shouldCompactContext, } from "./compaction.js";
|
|
3
|
+
import { COMPACTION_META_MAX_OUTPUT_TOKENS, COMPACTION_SUMMARY_HEADER, createCompactionSummaryMessage, findCutPoint, metaCompactionMaxTokens, prepareCompaction, serializeConversation, shouldCompactContext, } from "./compaction.js";
|
|
4
4
|
import { buildCheckpointReminder } from "./scope-reminder.js";
|
|
5
5
|
import { __forceFallbackForTests } from "./token-counter.js";
|
|
6
6
|
// Pin token counts to the chars/4 fallback so cut-point assertions remain stable.
|
|
@@ -160,4 +160,28 @@ describe("compaction helpers", () => {
|
|
|
160
160
|
expect(r).toContain("tool-artifact");
|
|
161
161
|
});
|
|
162
162
|
});
|
|
163
|
+
describe("metaCompactionMaxTokens — meta summary cap (tunable, session 2b7a10219499)", () => {
|
|
164
|
+
it("defaults to 1536 — looser than the old hard 1024, still well below the 14k-char problem", () => {
|
|
165
|
+
delete process.env.MUONROI_META_COMPACT_MAX_TOKENS;
|
|
166
|
+
expect(metaCompactionMaxTokens()).toBe(COMPACTION_META_MAX_OUTPUT_TOKENS);
|
|
167
|
+
expect(COMPACTION_META_MAX_OUTPUT_TOKENS).toBe(1536);
|
|
168
|
+
expect(COMPACTION_META_MAX_OUTPUT_TOKENS).toBeGreaterThan(1024);
|
|
169
|
+
});
|
|
170
|
+
it("honors a valid MUONROI_META_COMPACT_MAX_TOKENS override", () => {
|
|
171
|
+
process.env.MUONROI_META_COMPACT_MAX_TOKENS = "2048";
|
|
172
|
+
try {
|
|
173
|
+
expect(metaCompactionMaxTokens()).toBe(2048);
|
|
174
|
+
}
|
|
175
|
+
finally {
|
|
176
|
+
delete process.env.MUONROI_META_COMPACT_MAX_TOKENS;
|
|
177
|
+
}
|
|
178
|
+
});
|
|
179
|
+
it("clamps out-of-range / garbage overrides to the default", () => {
|
|
180
|
+
for (const bad of ["999999", "100", "-5", "abc", ""]) {
|
|
181
|
+
process.env.MUONROI_META_COMPACT_MAX_TOKENS = bad;
|
|
182
|
+
expect(metaCompactionMaxTokens(), bad).toBe(COMPACTION_META_MAX_OUTPUT_TOKENS);
|
|
183
|
+
}
|
|
184
|
+
delete process.env.MUONROI_META_COMPACT_MAX_TOKENS;
|
|
185
|
+
});
|
|
186
|
+
});
|
|
163
187
|
//# sourceMappingURL=compaction.test.js.map
|
|
@@ -50,6 +50,7 @@
|
|
|
50
50
|
// - O1 (providerOptions shape forensics) — extractProviderOptionsShape
|
|
51
51
|
// - siliconflow reasoning-strip — turnCaps.sanitizeHistory
|
|
52
52
|
import { stepCountIs, streamText } from "ai";
|
|
53
|
+
import { recordArtifact } from "../ee/artifact-cache.js";
|
|
53
54
|
import { getCachedAuthToken, getCachedServerBaseUrl } from "../ee/auth.js";
|
|
54
55
|
import { routeFeedback, routeModel } from "../ee/bridge.js";
|
|
55
56
|
import { getDefaultEEClient } from "../ee/intercept.js";
|
|
@@ -59,7 +60,7 @@ import * as phaseTracker from "../ee/phase-tracker.js";
|
|
|
59
60
|
import { buildScope as buildScopeForVeto } from "../ee/scope.js";
|
|
60
61
|
import { fireTrajectoryEvent } from "../ee/session-trajectory.js";
|
|
61
62
|
import { getTenantId as getTenantIdForVeto } from "../ee/tenant.js";
|
|
62
|
-
import {
|
|
63
|
+
import { acquireMcpTools } from "../mcp/client-pool.js";
|
|
63
64
|
import { dropRedundantFsMcpTools, filterMcpServersByMessage } from "../mcp/smart-filter.js";
|
|
64
65
|
import { getModelInfo } from "../models/registry.js";
|
|
65
66
|
import { cheapModelShellLine, injectCheapModelPlaybook, injectCheapModelShellDirective, shouldInjectCheapModelPlaybook, } from "../pil/cheap-model-playbook.js";
|
|
@@ -83,6 +84,7 @@ import { visionToolsNeeded } from "../tools/vision-gate.js";
|
|
|
83
84
|
import { isDebugEnabled, recordTurnTrace } from "../ui/slash/debug.js";
|
|
84
85
|
import { statusBarStore } from "../ui/status-bar/store.js";
|
|
85
86
|
import { appendDecisionLog } from "../usage/decision-log.js";
|
|
87
|
+
import { openUrl } from "../utils/open-url.js";
|
|
86
88
|
import { appendAudit, toolNeedsApproval } from "../utils/permission-mode.js";
|
|
87
89
|
import { getAutoCouncilConfidence, getAutoCouncilMinRoles, getProviderStallTimeoutMs, getRoleModels, getTopLevelCompactKeepLast, getTopLevelCompactThresholdChars, getTopLevelToolBudgetChars, isAutoCouncilEnabled, isProviderDisabled, loadMcpServers, loadValidSubAgents, } from "../utils/settings.js";
|
|
88
90
|
import { resolveShell } from "../utils/shell.js";
|
|
@@ -92,7 +94,7 @@ import { humanizeApiError, isAuthenticationError, isContextLimitError, summarize
|
|
|
92
94
|
import { buildGroundingFootnote, findUnverifiedClaims } from "./grounding-check.js";
|
|
93
95
|
import { buildInterruptedTurnNote } from "./interrupted-turn.js";
|
|
94
96
|
import { stableCallId } from "./pending-calls.js";
|
|
95
|
-
import { applyModelConstraints, buildSystemPromptParts } from "./prompts.js";
|
|
97
|
+
import { applyModelConstraints, buildMcpCapabilityBlock, buildSystemPromptParts } from "./prompts.js";
|
|
96
98
|
import { extractProviderOptionsShape } from "./provider-options-shape.js";
|
|
97
99
|
import { wrapToolSetWithReadBudget } from "./read-path-budget.js";
|
|
98
100
|
import { containsEncryptedReasoning, sanitizeModelMessages } from "./reasoning.js";
|
|
@@ -100,11 +102,11 @@ import { repairToolCallHook } from "./repair-tool-call.js";
|
|
|
100
102
|
import { buildRepetitionReminder, recordAssistantBurst, shouldInjectRepetitionReminder, } from "./repetition-detector.js";
|
|
101
103
|
import { classifyStreamError } from "./retry-classifier.js";
|
|
102
104
|
import { forcedFinalize, getSessionLastTask, incSessionStep, parseBudgetOverride, recordSessionLastTask, resetSessionStep, resolveCeiling, } from "./scope-ceiling.js";
|
|
103
|
-
import { attachReminderToMessages, buildCheckpointReminder, buildScopeReminder, cadenceForSize, shouldInjectCeilingCrossing, shouldInjectReminder, shouldInjectSoftWarn, } from "./scope-reminder.js";
|
|
105
|
+
import { attachReminderToMessages, buildCheckpointReminder, buildScopeReminder, cadenceForSize, shouldInjectCeilingCrossing, shouldInjectReminder, shouldInjectSoftWarn, shouldPreWarnCompaction, } from "./scope-reminder.js";
|
|
104
106
|
import { attemptStallRescue, pushStallToolResult } from "./stall-rescue.js";
|
|
105
107
|
import { createStallWatchdog, STALL_ERROR_MESSAGE } from "./stall-watchdog.js";
|
|
106
108
|
import { wrapToolSetWithCap } from "./sub-agent-cap.js";
|
|
107
|
-
import { compactSubAgentMessages } from "./subagent-compactor.js";
|
|
109
|
+
import { compactSubAgentMessages, cumulativeMessageChars } from "./subagent-compactor.js";
|
|
108
110
|
import { detectTextEmittedToolCall, parseDsmlToolCalls } from "./text-tool-call-detector.js";
|
|
109
111
|
import { createToolLoopCapPredicate } from "./tool-loop-cap.js";
|
|
110
112
|
import { buildToolRepetitionAbortMessage, recordToolError as recordToolRepetitionError, recordToolSuccess as recordToolRepetitionSuccess, } from "./tool-repetition-detector.js";
|
|
@@ -1017,32 +1019,26 @@ export class MessageProcessor {
|
|
|
1017
1019
|
const filteredServers = filterMcpServersByMessage(loadMcpServers(), userMessage, {
|
|
1018
1020
|
disabled: process.env.MUONROI_DISABLE_SMART_MCP === "1",
|
|
1019
1021
|
});
|
|
1020
|
-
// MCP non-blocking:
|
|
1021
|
-
//
|
|
1022
|
-
//
|
|
1023
|
-
//
|
|
1024
|
-
//
|
|
1022
|
+
// MCP non-blocking: acquireMcpTools self-bounds — it connects servers
|
|
1023
|
+
// in parallel and returns PARTIAL results at its internal deadline
|
|
1024
|
+
// (fast/cached servers included; slow first-connects reported in
|
|
1025
|
+
// .errors and available next turn). Clients are POOLED across turns
|
|
1026
|
+
// (client-pool.ts), so a server cold-spawns at most once per session
|
|
1027
|
+
// instead of every turn. No outer race: the old race discarded the
|
|
1028
|
+
// WHOLE bundle on timeout (Phase 1c — session f6f7881a5fae).
|
|
1025
1029
|
let mcpBundle = null;
|
|
1026
1030
|
try {
|
|
1027
|
-
mcpBundle = await
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
? `open "${urlStr}"`
|
|
1036
|
-
: `xdg-open "${urlStr}"`;
|
|
1037
|
-
exec(cmd);
|
|
1038
|
-
});
|
|
1039
|
-
},
|
|
1040
|
-
}),
|
|
1041
|
-
new Promise((_, reject) => setTimeout(() => reject(new Error("MCP build timeout (2500ms)")), 2500)),
|
|
1042
|
-
]);
|
|
1031
|
+
mcpBundle = await acquireMcpTools(filteredServers, {
|
|
1032
|
+
onOAuthRequired: (_serverId, url) => {
|
|
1033
|
+
// Server-supplied URL is untrusted — openUrl validates the
|
|
1034
|
+
// scheme and spawns via execFile (no shell), closing the
|
|
1035
|
+
// command-injection vector the old exec() opener had.
|
|
1036
|
+
openUrl(url);
|
|
1037
|
+
},
|
|
1038
|
+
});
|
|
1043
1039
|
}
|
|
1044
1040
|
catch (err) {
|
|
1045
|
-
console.error("[MCP] buildMcpToolSet
|
|
1041
|
+
console.error("[MCP] buildMcpToolSet failed, proceeding with builtins only", err);
|
|
1046
1042
|
}
|
|
1047
1043
|
if (mcpBundle) {
|
|
1048
1044
|
closeMcp = mcpBundle.close;
|
|
@@ -1056,6 +1052,19 @@ export class MessageProcessor {
|
|
|
1056
1052
|
const _builtinToolNames = new Set(Object.keys(rawToolSet));
|
|
1057
1053
|
const { tools: _dedupedMcpTools, dropped: _droppedFsMcp } = dropRedundantFsMcpTools(mcpBundle.tools, _builtinToolNames);
|
|
1058
1054
|
rawToolSet = { ...rawToolSet, ..._dedupedMcpTools };
|
|
1055
|
+
// muonroi-tools is THIS CLI: every tool it exposes (ee_query,
|
|
1056
|
+
// ee_feedback, ee_health, usage_forensics, lsp_query, setup_guide,
|
|
1057
|
+
// selfverify_*) is now a NATIVE in-process builtin (src/tools/
|
|
1058
|
+
// native-tools.ts) — strictly better (no subprocess, no cold-start).
|
|
1059
|
+
// If an external/legacy config still self-spawns muonroi-tools, drop
|
|
1060
|
+
// any MCP twin whose native equivalent is present so the model never
|
|
1061
|
+
// sees two interchangeable copies. (The CLI no longer self-spawns it
|
|
1062
|
+
// by default — see auto-setup.ts.)
|
|
1063
|
+
for (const key of Object.keys(rawToolSet)) {
|
|
1064
|
+
const twin = key.match(/^mcp_muonroi-tools__(.+)$/);
|
|
1065
|
+
if (twin && rawToolSet[twin[1]])
|
|
1066
|
+
delete rawToolSet[key];
|
|
1067
|
+
}
|
|
1059
1068
|
if (_droppedFsMcp.length > 0 && deps.session) {
|
|
1060
1069
|
try {
|
|
1061
1070
|
logInteraction(deps.session.id, "routing", {
|
|
@@ -1068,7 +1077,20 @@ export class MessageProcessor {
|
|
|
1068
1077
|
}
|
|
1069
1078
|
}
|
|
1070
1079
|
if (mcpBundle.errors.length > 0) {
|
|
1071
|
-
|
|
1080
|
+
// A pooled server that is still cold-starting is NOT "unavailable"
|
|
1081
|
+
// — it's warming up and will be ready next turn. Only surface
|
|
1082
|
+
// GENUINE failures as "unavailable"; show warming servers as a
|
|
1083
|
+
// soft, non-alarming note (and only the first time, since the
|
|
1084
|
+
// pool connects them in the background).
|
|
1085
|
+
const warming = mcpBundle.errors.filter((e) => /still connecting/.test(e));
|
|
1086
|
+
const failed = mcpBundle.errors.filter((e) => !/still connecting/.test(e));
|
|
1087
|
+
if (failed.length > 0) {
|
|
1088
|
+
yield { type: "content", content: `MCP unavailable: ${failed.join(" | ")}\n\n` };
|
|
1089
|
+
}
|
|
1090
|
+
if (warming.length > 0) {
|
|
1091
|
+
const names = warming.map((e) => e.split(":")[0]).join(", ");
|
|
1092
|
+
yield { type: "content", content: `MCP warming up (${names}) — ready from the next turn.\n\n` };
|
|
1093
|
+
}
|
|
1072
1094
|
}
|
|
1073
1095
|
}
|
|
1074
1096
|
}
|
|
@@ -1169,6 +1191,15 @@ export class MessageProcessor {
|
|
|
1169
1191
|
const systemWithShell = shouldInjectCheapModelPlaybook(runtime.modelInfo)
|
|
1170
1192
|
? injectCheapModelShellDirective(systemWithPlaybook, cheapModelShellLine(resolveShell({}).kind, process.platform))
|
|
1171
1193
|
: systemWithPlaybook;
|
|
1194
|
+
// Append the LIVE MCP tool roster so the agent calls connected MCP
|
|
1195
|
+
// tools by their exact mcp_<server>__<tool> name instead of shelling
|
|
1196
|
+
// out (session f6f7881a5fae). Built from the FINAL toolset for this
|
|
1197
|
+
// iteration (post smart-filter + fs-dedup), so it never names a tool
|
|
1198
|
+
// the model can't actually call. Dynamic per turn → must live OUTSIDE
|
|
1199
|
+
// the cached staticPrefix; for claude it lands in the second
|
|
1200
|
+
// (non-cached) system message via the slice below.
|
|
1201
|
+
const mcpCapabilityBlock = buildMcpCapabilityBlock(Object.keys(tools));
|
|
1202
|
+
const systemWithCaps = mcpCapabilityBlock ? `${systemWithShell}${mcpCapabilityBlock}` : systemWithShell;
|
|
1172
1203
|
const systemForModel = runtime.modelId.startsWith("claude")
|
|
1173
1204
|
? [
|
|
1174
1205
|
{
|
|
@@ -1178,10 +1209,10 @@ export class MessageProcessor {
|
|
|
1178
1209
|
},
|
|
1179
1210
|
{
|
|
1180
1211
|
role: "system",
|
|
1181
|
-
content:
|
|
1212
|
+
content: systemWithCaps.slice(systemParts.staticPrefix.length),
|
|
1182
1213
|
},
|
|
1183
1214
|
]
|
|
1184
|
-
:
|
|
1215
|
+
: systemWithCaps;
|
|
1185
1216
|
// Capture prompt-size breakdown so recordUsage can attach it to the
|
|
1186
1217
|
// cost-log entry. Without this, "system prompt is huge" is unfalsifiable.
|
|
1187
1218
|
// chars/4 ≈ tokens for English; reported as chars to keep math obvious.
|
|
@@ -1470,6 +1501,10 @@ export class MessageProcessor {
|
|
|
1470
1501
|
const _cwd = process.cwd();
|
|
1471
1502
|
const _sess = undefined; // best-effort; EE artifact still indexable by content + meta.toolCallId
|
|
1472
1503
|
const persistArtifact = (toolCallId, toolName, fullContent, reason) => {
|
|
1504
|
+
// Local-first: record the FULL output in-process so ee_query can
|
|
1505
|
+
// rehydrate it even if EE is down (the EE extract below caps at 8k
|
|
1506
|
+
// and needs the network; the cache keeps up to 200k, no network).
|
|
1507
|
+
recordArtifact(toolCallId, toolName, fullContent);
|
|
1473
1508
|
try {
|
|
1474
1509
|
getDefaultEEClient()
|
|
1475
1510
|
.extract({
|
|
@@ -1502,9 +1537,14 @@ export class MessageProcessor {
|
|
|
1502
1537
|
// Pre-compaction visibility: give the agent one step of notice
|
|
1503
1538
|
// before B4 actually rewrites history into stubs. This is the
|
|
1504
1539
|
// advance warning that was missing — agent can now decide to
|
|
1505
|
-
// summarize, finish, or request preservation.
|
|
1506
|
-
|
|
1507
|
-
|
|
1540
|
+
// summarize, finish, or request preservation. Fires when we did
|
|
1541
|
+
// NOT compact this step (compacted === stripped, restored by the
|
|
1542
|
+
// compactSubAgentMessages no-op ref contract) AND the prompt is
|
|
1543
|
+
// approaching the threshold. Must compare CHARS (messages +
|
|
1544
|
+
// envelope), not stripped.length (a message count that never
|
|
1545
|
+
// exceeds a char-scaled threshold) — session 2b7a10219499.
|
|
1546
|
+
const _preWarnChars = cumulativeMessageChars(stripped) + envelopeChars;
|
|
1547
|
+
if (compacted === stripped && shouldPreWarnCompaction(_preWarnChars, topLevelCompactThreshold)) {
|
|
1508
1548
|
const _cp = buildCheckpointReminder(sn, true);
|
|
1509
1549
|
const _pre = `[pre-compaction warning at step ${sn} — next step(s) will likely rewrite older tool results to stubs (threshold ${topLevelCompactThreshold}, keepLast=${topLevelCompactKeepLast}). ${_cp} Summarize or finish if possible.]`;
|
|
1510
1550
|
return { messages: attachReminderToMessages(stripped, _pre) };
|