muonroi-cli 1.8.5 → 1.9.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/dist/packages/agent-harness-core/src/event-filter.js +11 -0
- package/dist/packages/agent-harness-core/src/event-redact.js +7 -0
- package/dist/packages/agent-harness-core/src/event-tee.d.ts +20 -4
- package/dist/packages/agent-harness-core/src/event-tee.js +32 -5
- package/dist/packages/agent-harness-core/src/mcp-server.d.ts +14 -0
- package/dist/packages/agent-harness-core/src/mcp-server.js +86 -10
- package/dist/packages/agent-harness-core/src/predicate.d.ts +1 -1
- package/dist/packages/agent-harness-core/src/protocol.d.ts +24 -2
- package/dist/src/agent-harness/mock-model.d.ts +10 -0
- package/dist/src/agent-harness/mock-model.js +6 -2
- package/dist/src/chat/chat-keychain.d.ts +7 -12
- package/dist/src/chat/chat-keychain.js +19 -86
- package/dist/src/cli/keys.d.ts +8 -45
- package/dist/src/cli/keys.js +18 -324
- package/dist/src/council/clarifier.d.ts +4 -2
- package/dist/src/council/clarifier.js +158 -36
- package/dist/src/council/debate-planner.js +3 -2
- package/dist/src/council/debate.js +59 -5
- package/dist/src/council/index.d.ts +23 -0
- package/dist/src/council/index.js +37 -2
- package/dist/src/council/llm.d.ts +62 -0
- package/dist/src/council/llm.js +123 -23
- package/dist/src/council/panel-select.js +13 -3
- package/dist/src/council/planner.js +17 -0
- package/dist/src/council/preflight.d.ts +10 -0
- package/dist/src/council/preflight.js +36 -0
- package/dist/src/council/prompts.d.ts +9 -1
- package/dist/src/council/prompts.js +23 -6
- package/dist/src/council/types.d.ts +10 -0
- package/dist/src/ee/auth.d.ts +19 -0
- package/dist/src/ee/auth.js +39 -0
- package/dist/src/ee/client.js +28 -3
- package/dist/src/ee/ee-onboarding.js +6 -26
- package/dist/src/flow/compaction/compress.d.ts +2 -2
- package/dist/src/flow/compaction/compress.js +21 -8
- package/dist/src/flow/compaction/extract.d.ts +3 -3
- package/dist/src/flow/compaction/extract.js +6 -6
- package/dist/src/flow/compaction/index.d.ts +2 -1
- package/dist/src/flow/compaction/index.js +29 -3
- package/dist/src/flow/compaction/progress.d.ts +35 -0
- package/dist/src/flow/compaction/progress.js +35 -0
- package/dist/src/generated/version.d.ts +1 -1
- package/dist/src/generated/version.js +1 -1
- package/dist/src/gsd/flags.d.ts +11 -0
- package/dist/src/gsd/flags.js +19 -0
- package/dist/src/gsd/plan-council.js +104 -72
- package/dist/src/gsd/verdict-schema.d.ts +1 -1
- package/dist/src/headless/council-answers.js +4 -0
- package/dist/src/index.js +129 -260
- package/dist/src/lsp/builtins.js +3 -1
- package/dist/src/lsp/manager.d.ts +5 -1
- package/dist/src/lsp/manager.js +249 -3
- package/dist/src/lsp/npm-cache.d.ts +11 -1
- package/dist/src/lsp/npm-cache.js +17 -1
- package/dist/src/lsp/runtime.d.ts +6 -1
- package/dist/src/lsp/runtime.js +17 -1
- package/dist/src/lsp/types.d.ts +83 -1
- package/dist/src/lsp/types.js +10 -0
- package/dist/src/mcp/client-pool.js +43 -15
- package/dist/src/mcp/lsp-tools.d.ts +5 -1
- package/dist/src/mcp/lsp-tools.js +93 -2
- package/dist/src/mcp/mcp-keychain.d.ts +3 -5
- package/dist/src/mcp/mcp-keychain.js +9 -49
- package/dist/src/mcp/setup-guide-text.d.ts +1 -1
- package/dist/src/mcp/setup-guide-text.js +22 -2
- package/dist/src/mcp/tools-server.d.ts +10 -0
- package/dist/src/mcp/tools-server.js +10 -2
- package/dist/src/models/catalog.json +19 -19
- package/dist/src/orchestrator/ask-user.d.ts +61 -0
- package/dist/src/orchestrator/ask-user.js +65 -0
- package/dist/src/orchestrator/compaction.d.ts +2 -3
- package/dist/src/orchestrator/compaction.js +8 -8
- package/dist/src/orchestrator/council-manager.js +9 -8
- package/dist/src/orchestrator/council-request.d.ts +49 -0
- package/dist/src/orchestrator/council-request.js +62 -0
- package/dist/src/orchestrator/interactive-pause.d.ts +26 -0
- package/dist/src/orchestrator/interactive-pause.js +36 -0
- package/dist/src/orchestrator/message-processor.d.ts +4 -0
- package/dist/src/orchestrator/message-processor.js +26 -8
- package/dist/src/orchestrator/orchestrator.d.ts +25 -0
- package/dist/src/orchestrator/orchestrator.js +204 -50
- package/dist/src/orchestrator/preprocessor.js +2 -2
- package/dist/src/orchestrator/safety-askcard.d.ts +1 -1
- package/dist/src/orchestrator/safety-askcard.js +5 -2
- package/dist/src/orchestrator/safety-intercept.d.ts +5 -0
- package/dist/src/orchestrator/safety-intercept.js +7 -0
- package/dist/src/orchestrator/stall-watchdog.d.ts +8 -1
- package/dist/src/orchestrator/stall-watchdog.js +24 -3
- package/dist/src/orchestrator/stream-runner.d.ts +13 -3
- package/dist/src/orchestrator/stream-runner.js +54 -21
- package/dist/src/orchestrator/tool-engine.d.ts +19 -0
- package/dist/src/orchestrator/tool-engine.js +241 -25
- package/dist/src/orchestrator/turn-watchdog.d.ts +7 -0
- package/dist/src/orchestrator/turn-watchdog.js +38 -9
- package/dist/src/pil/agent-operating-contract.d.ts +1 -1
- package/dist/src/pil/agent-operating-contract.js +6 -4
- package/dist/src/pil/discovery.d.ts +1 -1
- package/dist/src/pil/discovery.js +2 -2
- package/dist/src/pil/layer1_5-complexity-size.d.ts +7 -0
- package/dist/src/pil/layer1_5-complexity-size.js +31 -5
- package/dist/src/pil/llm-classify.d.ts +78 -3
- package/dist/src/pil/llm-classify.js +351 -111
- package/dist/src/pil/native-capabilities-workbook.d.ts +1 -1
- package/dist/src/pil/native-capabilities-workbook.js +7 -0
- package/dist/src/pil/pipeline.js +2 -0
- package/dist/src/pil/repo-grounding-probe.d.ts +15 -0
- package/dist/src/pil/repo-grounding-probe.js +136 -0
- package/dist/src/pil/repo-structure-hints.d.ts +7 -0
- package/dist/src/pil/repo-structure-hints.js +45 -0
- package/dist/src/product-loop/artifact-io.js +4 -0
- package/dist/src/product-loop/criteria-seed.d.ts +51 -0
- package/dist/src/product-loop/criteria-seed.js +200 -0
- package/dist/src/product-loop/discovery-interview.d.ts +9 -0
- package/dist/src/product-loop/discovery-interview.js +37 -18
- package/dist/src/product-loop/discovery-recommender.js +2 -1
- package/dist/src/product-loop/discovery-schema.js +14 -1
- package/dist/src/product-loop/discovery-triage.d.ts +23 -0
- package/dist/src/product-loop/discovery-triage.js +109 -0
- package/dist/src/product-loop/gather.js +150 -2
- package/dist/src/product-loop/index.js +7 -0
- package/dist/src/product-loop/loop-driver.js +21 -8
- package/dist/src/product-loop/phase-plan.d.ts +16 -0
- package/dist/src/product-loop/phase-plan.js +42 -4
- package/dist/src/product-loop/phase-rituals.d.ts +3 -0
- package/dist/src/product-loop/phase-rituals.js +8 -3
- package/dist/src/product-loop/phase-runner.js +30 -11
- package/dist/src/product-loop/plan-adherence-review.d.ts +26 -0
- package/dist/src/product-loop/plan-adherence-review.js +144 -0
- package/dist/src/product-loop/sprint-runner.d.ts +62 -0
- package/dist/src/product-loop/sprint-runner.js +309 -8
- package/dist/src/product-loop/types.d.ts +25 -0
- package/dist/src/providers/anthropic.d.ts +9 -8
- package/dist/src/providers/anthropic.js +13 -47
- package/dist/src/providers/auth/grok-oauth.d.ts +1 -0
- package/dist/src/providers/auth/grok-oauth.js +30 -5
- package/dist/src/providers/auth/openai-oauth.d.ts +1 -0
- package/dist/src/providers/auth/openai-oauth.js +14 -0
- package/dist/src/providers/auth/token-store.d.ts +9 -9
- package/dist/src/providers/auth/token-store.js +8 -70
- package/dist/src/providers/auth/types.d.ts +8 -0
- package/dist/src/providers/env-store.d.ts +17 -0
- package/dist/src/providers/env-store.js +228 -0
- package/dist/src/providers/keychain.d.ts +21 -17
- package/dist/src/providers/keychain.js +124 -135
- package/dist/src/providers/runtime.d.ts +24 -9
- package/dist/src/providers/runtime.js +48 -37
- package/dist/src/providers/strategies/thinking-mode.js +9 -1
- package/dist/src/providers/strategies/xai.strategy.js +27 -0
- package/dist/src/providers/warm.d.ts +65 -0
- package/dist/src/providers/warm.js +145 -0
- package/dist/src/self-qa/agentic-loop.js +3 -2
- package/dist/src/storage/transcript.js +56 -2
- package/dist/src/tools/git-safety.d.ts +19 -0
- package/dist/src/tools/git-safety.js +168 -0
- package/dist/src/tools/native-tools.d.ts +1 -1
- package/dist/src/tools/native-tools.js +76 -1
- package/dist/src/tools/registry.d.ts +17 -0
- package/dist/src/tools/registry.js +116 -1
- package/dist/src/types/index.d.ts +29 -1
- package/dist/src/ui/app.js +67 -4
- package/dist/src/ui/components/agent-rail-activities.d.ts +26 -0
- package/dist/src/ui/components/agent-rail-activities.js +47 -0
- package/dist/src/ui/components/compact-progress-card.d.ts +24 -0
- package/dist/src/ui/components/compact-progress-card.js +42 -0
- package/dist/src/ui/components/council-phase-timeline.js +17 -2
- package/dist/src/ui/components/council-question-card.js +1 -0
- package/dist/src/ui/components/message-view.d.ts +15 -0
- package/dist/src/ui/components/message-view.js +50 -1
- package/dist/src/ui/components/tool-group.d.ts +15 -3
- package/dist/src/ui/components/tool-group.js +69 -11
- package/dist/src/ui/containers/modals-layer.d.ts +2 -1
- package/dist/src/ui/containers/modals-layer.js +2 -2
- package/dist/src/ui/council-harness-event.d.ts +57 -0
- package/dist/src/ui/council-harness-event.js +46 -0
- package/dist/src/ui/heartbeat-debug.d.ts +29 -0
- package/dist/src/ui/heartbeat-debug.js +45 -0
- package/dist/src/ui/modals/api-key-modal.js +1 -1
- package/dist/src/ui/modals/model-picker-modal.d.ts +8 -18
- package/dist/src/ui/modals/model-picker-modal.js +8 -10
- package/dist/src/ui/slash/ee.js +81 -0
- package/dist/src/ui/slash/menu-items.js +11 -2
- package/dist/src/ui/use-app-logic.js +354 -224
- package/dist/src/ui/utils/agent-activities.d.ts +39 -0
- package/dist/src/ui/utils/agent-activities.js +96 -0
- package/dist/src/ui/utils/group-tool-entries.d.ts +26 -0
- package/dist/src/ui/utils/group-tool-entries.js +111 -0
- package/dist/src/ui/utils/tool-summary.d.ts +21 -0
- package/dist/src/ui/utils/tool-summary.js +91 -0
- package/dist/src/utils/event-loop-monitor.d.ts +85 -0
- package/dist/src/utils/event-loop-monitor.js +107 -0
- package/dist/src/utils/llm-deadline.d.ts +14 -0
- package/dist/src/utils/llm-deadline.js +19 -0
- package/dist/src/utils/loop-profiler.d.ts +102 -0
- package/dist/src/utils/loop-profiler.js +202 -0
- package/dist/src/utils/settings.d.ts +27 -0
- package/dist/src/utils/settings.js +38 -2
- package/dist/src/utils/side-question.d.ts +1 -2
- package/dist/src/utils/side-question.js +2 -2
- package/dist/src/verify/entrypoint.js +51 -16
- package/dist/src/verify/orchestrator.d.ts +1 -1
- package/dist/src/verify/orchestrator.js +20 -3
- package/package.json +1 -2
- package/dist/src/cli/bw-vault.d.ts +0 -55
- package/dist/src/cli/bw-vault.js +0 -133
- package/dist/src/mcp/ee-tools.d.ts +0 -46
- package/dist/src/mcp/ee-tools.js +0 -194
package/dist/src/ee/auth.js
CHANGED
|
@@ -71,6 +71,45 @@ export async function writeExperienceConfig(patch, opts = {}) {
|
|
|
71
71
|
await fs.mkdir(path.dirname(p), { recursive: true, mode: 0o700 });
|
|
72
72
|
await fs.writeFile(p, `${JSON.stringify(merged, null, 2)}\n`, "utf8");
|
|
73
73
|
}
|
|
74
|
+
/** Where the EE config lives, for display in setup/status output. */
|
|
75
|
+
export function getExperienceConfigPath(opts = {}) {
|
|
76
|
+
return configPath(opts.home);
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Best-effort reachability probe against an EE server's /health.
|
|
80
|
+
*
|
|
81
|
+
* Returns a reason on failure rather than a bare boolean: "saved but not
|
|
82
|
+
* reachable" and "saved, server says 401" are different problems for the user
|
|
83
|
+
* to act on, and collapsing them to `false` hides which one they have.
|
|
84
|
+
* Never throws — a probe is diagnostics, and must not fail a config write that
|
|
85
|
+
* already succeeded.
|
|
86
|
+
*/
|
|
87
|
+
export async function probeEEHealth(baseUrl, token, opts = {}) {
|
|
88
|
+
const ac = new AbortController();
|
|
89
|
+
const timer = setTimeout(() => ac.abort(), opts.timeoutMs ?? 4000);
|
|
90
|
+
try {
|
|
91
|
+
const res = await fetch(`${baseUrl}/health`, {
|
|
92
|
+
signal: ac.signal,
|
|
93
|
+
headers: token ? { authorization: `Bearer ${token}` } : undefined,
|
|
94
|
+
});
|
|
95
|
+
if (res.ok)
|
|
96
|
+
return { ok: true, detail: `HTTP ${res.status}` };
|
|
97
|
+
return {
|
|
98
|
+
ok: false,
|
|
99
|
+
detail: res.status === 401 || res.status === 403
|
|
100
|
+
? `HTTP ${res.status} — the server rejected this auth token`
|
|
101
|
+
: `HTTP ${res.status}`,
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
catch (err) {
|
|
105
|
+
const message = err?.name === "AbortError" ? "timed out" : (err?.message ?? String(err));
|
|
106
|
+
console.error(`[ee/auth] /health probe failed for ${baseUrl}: ${message}`);
|
|
107
|
+
return { ok: false, detail: message };
|
|
108
|
+
}
|
|
109
|
+
finally {
|
|
110
|
+
clearTimeout(timer);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
74
113
|
export function getCachedAuthToken() {
|
|
75
114
|
return _token;
|
|
76
115
|
}
|
package/dist/src/ee/client.js
CHANGED
|
@@ -207,13 +207,23 @@ export function createEEClient(opts = {}) {
|
|
|
207
207
|
}
|
|
208
208
|
return {
|
|
209
209
|
async health() {
|
|
210
|
+
const startedAt = Date.now();
|
|
210
211
|
try {
|
|
211
212
|
const resp = await f(`${baseUrl}/health`, {
|
|
212
213
|
signal: AbortSignal.timeout(DEFAULT_HEALTH_TIMEOUT_MS),
|
|
213
214
|
});
|
|
214
215
|
return { ok: resp.ok, status: resp.status };
|
|
215
216
|
}
|
|
216
|
-
catch {
|
|
217
|
+
catch (err) {
|
|
218
|
+
// status:0 means "no response" and says nothing about WHY. Timeout, DNS,
|
|
219
|
+
// refused connection and TLS failure all degrade to the same shape, so a
|
|
220
|
+
// bare catch here leaves an agent with an unfalsifiable symptom — exactly
|
|
221
|
+
// what happened on 2026-07-17, when status:0 was really the server
|
|
222
|
+
// blocking its event loop.
|
|
223
|
+
logEeFailure("client.health", classifyEeError(err), err, {
|
|
224
|
+
elapsedMs: Date.now() - startedAt,
|
|
225
|
+
budgetMs: DEFAULT_HEALTH_TIMEOUT_MS,
|
|
226
|
+
});
|
|
217
227
|
return { ok: false, status: 0 };
|
|
218
228
|
}
|
|
219
229
|
},
|
|
@@ -599,6 +609,7 @@ export function createEEClient(opts = {}) {
|
|
|
599
609
|
// races the server and loses → spurious ee_unavailable. 15s gives margin
|
|
600
610
|
// so a slow-but-valid recall still lands rather than being dropped.
|
|
601
611
|
const timeoutMs = opts.timeoutMs ?? 15000;
|
|
612
|
+
const startedAt = Date.now();
|
|
602
613
|
try {
|
|
603
614
|
const resp = await f(`${baseUrl}/api/recall`, {
|
|
604
615
|
method: "POST",
|
|
@@ -606,11 +617,25 @@ export function createEEClient(opts = {}) {
|
|
|
606
617
|
body: JSON.stringify(redactedBody),
|
|
607
618
|
signal: opts.signal ?? AbortSignal.timeout(timeoutMs),
|
|
608
619
|
});
|
|
609
|
-
if (!resp.ok)
|
|
620
|
+
if (!resp.ok) {
|
|
621
|
+
// Keep returning null — callers map it to "EE unavailable" — but never
|
|
622
|
+
// discard the status. Collapsing 429/401/500 into the same null a
|
|
623
|
+
// timeout produces is what made the 2026-07-17 incident unfalsifiable
|
|
624
|
+
// from the client: "is the server rate-limiting us?" could not be
|
|
625
|
+
// answered without reading the server's source.
|
|
626
|
+
logEeFailure("client.recall", "error", new Error(`HTTP ${resp.status}`), {
|
|
627
|
+
status: resp.status,
|
|
628
|
+
elapsedMs: Date.now() - startedAt,
|
|
629
|
+
});
|
|
610
630
|
return null;
|
|
631
|
+
}
|
|
611
632
|
return (await resp.json());
|
|
612
633
|
}
|
|
613
|
-
catch {
|
|
634
|
+
catch (err) {
|
|
635
|
+
logEeFailure("client.recall", classifyEeError(err), err, {
|
|
636
|
+
elapsedMs: Date.now() - startedAt,
|
|
637
|
+
budgetMs: timeoutMs,
|
|
638
|
+
});
|
|
614
639
|
return null;
|
|
615
640
|
}
|
|
616
641
|
},
|
|
@@ -8,27 +8,7 @@
|
|
|
8
8
|
* health probe is reported, not hidden, and never blocks setup.
|
|
9
9
|
*/
|
|
10
10
|
import { createInterface } from "node:readline";
|
|
11
|
-
import { writeExperienceConfig } from "./auth.js";
|
|
12
|
-
/** Best-effort reachability probe — returns true/false, never throws. */
|
|
13
|
-
async function probeHealth(baseUrl, token) {
|
|
14
|
-
try {
|
|
15
|
-
const ac = new AbortController();
|
|
16
|
-
const timer = setTimeout(() => ac.abort(), 4000);
|
|
17
|
-
try {
|
|
18
|
-
const res = await fetch(`${baseUrl}/health`, {
|
|
19
|
-
signal: ac.signal,
|
|
20
|
-
headers: token ? { authorization: `Bearer ${token}` } : undefined,
|
|
21
|
-
});
|
|
22
|
-
return res.ok;
|
|
23
|
-
}
|
|
24
|
-
finally {
|
|
25
|
-
clearTimeout(timer);
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
|
-
catch {
|
|
29
|
-
return false;
|
|
30
|
-
}
|
|
31
|
-
}
|
|
11
|
+
import { probeEEHealth, writeExperienceConfig } from "./auth.js";
|
|
32
12
|
/**
|
|
33
13
|
* Returns true when a config was written (so the caller can reload EE auth).
|
|
34
14
|
* Returns false when skipped or invalid.
|
|
@@ -38,8 +18,8 @@ export async function firstRunEESetup() {
|
|
|
38
18
|
const ask = (q) => new Promise((resolve) => rl.question(q, (a) => resolve(a)));
|
|
39
19
|
try {
|
|
40
20
|
process.stderr.write("\nExperience Engine (optional) — a shared brain that recalls past decisions, gotchas,\n" +
|
|
41
|
-
"and recipes so the agent works like a senior on your stack.
|
|
42
|
-
"
|
|
21
|
+
"and recipes so the agent works like a senior on your stack. Skipping is fine: you can\n" +
|
|
22
|
+
"connect one at any time with `/ee config <url> [token]`.\n\n");
|
|
43
23
|
const url = (await ask("EE server URL (blank to skip): ")).trim();
|
|
44
24
|
if (!url) {
|
|
45
25
|
process.stderr.write("Skipped Experience Engine setup.\n");
|
|
@@ -59,10 +39,10 @@ export async function firstRunEESetup() {
|
|
|
59
39
|
...(token ? { serverAuthToken: token } : {}),
|
|
60
40
|
});
|
|
61
41
|
process.stderr.write(`Wrote Experience Engine config → ~/.experience/config.json (serverBaseUrl=${normalized}).\n`);
|
|
62
|
-
const
|
|
63
|
-
process.stderr.write(
|
|
42
|
+
const health = await probeEEHealth(normalized, token || undefined);
|
|
43
|
+
process.stderr.write(health.ok
|
|
64
44
|
? " ✓ EE server reachable.\n"
|
|
65
|
-
:
|
|
45
|
+
: ` ⚠ Could not reach the EE server right now — ${health.detail} (saved anyway; recheck with '/ee config').\n`);
|
|
66
46
|
return true;
|
|
67
47
|
}
|
|
68
48
|
catch (err) {
|
|
@@ -17,6 +17,6 @@ export interface CompressResult {
|
|
|
17
17
|
* 1. Serialize messages via serializeConversation().
|
|
18
18
|
* 2. Extract preserved blocks.
|
|
19
19
|
* 3. If under budget, return as-is (with preserved blocks restored).
|
|
20
|
-
* 4. If over budget, compress via LLM (if
|
|
20
|
+
* 4. If over budget, compress via LLM (if modelId given) or truncate, and restore blocks.
|
|
21
21
|
*/
|
|
22
|
-
export declare function compressChat(messages: ModelMessage[], _systemPrompt: string, tokenBudget: number,
|
|
22
|
+
export declare function compressChat(messages: ModelMessage[], _systemPrompt: string, tokenBudget: number, modelId?: string, customInstructions?: string, onFraction?: (fraction: number) => void): Promise<CompressResult>;
|
|
@@ -4,9 +4,9 @@
|
|
|
4
4
|
* Serializes messages, extracts preserved blocks, compresses if over budget,
|
|
5
5
|
* then restores preserved blocks into the output.
|
|
6
6
|
*/
|
|
7
|
-
import {
|
|
7
|
+
import { streamText } from "ai";
|
|
8
8
|
import { serializeConversation } from "../../orchestrator/compaction.js";
|
|
9
|
-
import { resolveModelRuntime, resolveTemperatureParam
|
|
9
|
+
import { resolveModelRuntime, resolveTemperatureParam } from "../../providers/runtime.js";
|
|
10
10
|
import { logger } from "../../utils/logger.js";
|
|
11
11
|
import { capCompactionInput } from "./input-guard.js";
|
|
12
12
|
import { extractPreservedBlocks, restorePreservedBlocks } from "./preserve.js";
|
|
@@ -16,9 +16,9 @@ import { extractPreservedBlocks, restorePreservedBlocks } from "./preserve.js";
|
|
|
16
16
|
* 1. Serialize messages via serializeConversation().
|
|
17
17
|
* 2. Extract preserved blocks.
|
|
18
18
|
* 3. If under budget, return as-is (with preserved blocks restored).
|
|
19
|
-
* 4. If over budget, compress via LLM (if
|
|
19
|
+
* 4. If over budget, compress via LLM (if modelId given) or truncate, and restore blocks.
|
|
20
20
|
*/
|
|
21
|
-
export async function compressChat(messages, _systemPrompt, tokenBudget,
|
|
21
|
+
export async function compressChat(messages, _systemPrompt, tokenBudget, modelId, customInstructions, onFraction) {
|
|
22
22
|
const serialized = serializeConversation(messages);
|
|
23
23
|
const { cleaned, blocks } = extractPreservedBlocks(serialized);
|
|
24
24
|
// Estimate tokens of cleaned text
|
|
@@ -38,21 +38,34 @@ export async function compressChat(messages, _systemPrompt, tokenBudget, provide
|
|
|
38
38
|
const availableChars = availableTokens * 4;
|
|
39
39
|
let compressedContent = cleaned;
|
|
40
40
|
let usedLLM = false;
|
|
41
|
-
if (
|
|
41
|
+
if (modelId) {
|
|
42
42
|
try {
|
|
43
|
-
const runtime = resolveModelRuntime(
|
|
43
|
+
const runtime = resolveModelRuntime(modelId);
|
|
44
44
|
const extraPrompt = customInstructions ? `\n\nUSER FOCUS/INSTRUCTIONS:\n${customInstructions}\n` : "";
|
|
45
45
|
// Guard the compaction INPUT against the model's own context window —
|
|
46
46
|
// compaction fires when history is large, which is exactly when the full
|
|
47
47
|
// serialized text can overflow the summarizer. Keep head + tail.
|
|
48
48
|
const guardedInput = capCompactionInput(cleaned, runtime.modelInfo?.contextWindow ?? 0);
|
|
49
|
-
|
|
49
|
+
// Streamed rather than awaited whole so the caller can report real
|
|
50
|
+
// progress: this pass is the bulk of a /compact's wall-clock, and a bar
|
|
51
|
+
// that freezes for a minute reads as a hang. The summary text is still
|
|
52
|
+
// only used once complete.
|
|
53
|
+
const result = streamText({
|
|
50
54
|
model: runtime.model,
|
|
51
55
|
system: "You are an AI context compaction agent. Your job is to heavily summarize a chat history. Keep the core outcomes, the final state, and the technical context. Remove verbose pleasantries, step-by-step thinking, and irrelevant intermediate steps. Do NOT wrap in markdown unless it's code.",
|
|
52
56
|
prompt: `The following conversation exceeds the token budget. Please summarize it concisely, maintaining the essence of what was discussed, what code was written, and what decisions were reached:${extraPrompt}\n\n${guardedInput}`,
|
|
53
57
|
...resolveTemperatureParam(runtime, 0.1),
|
|
54
58
|
});
|
|
55
|
-
|
|
59
|
+
let streamed = "";
|
|
60
|
+
for await (const delta of result.textStream) {
|
|
61
|
+
streamed += delta;
|
|
62
|
+
// availableChars is the size the model was ASKED to fit under, so it is
|
|
63
|
+
// the only honest denominator available — a concise summary finishes
|
|
64
|
+
// early and the caller jumps the bar to done rather than overshooting.
|
|
65
|
+
if (onFraction && availableChars > 0)
|
|
66
|
+
onFraction(Math.min(1, streamed.length / availableChars));
|
|
67
|
+
}
|
|
68
|
+
compressedContent = streamed.trim();
|
|
56
69
|
usedLLM = true;
|
|
57
70
|
}
|
|
58
71
|
catch (e) {
|
|
@@ -14,7 +14,7 @@ export interface ExtractedDecisions {
|
|
|
14
14
|
/**
|
|
15
15
|
* Extract decisions, facts, and constraints from messages.
|
|
16
16
|
*
|
|
17
|
-
* Scans serialized conversation text for explicit markers, but if
|
|
18
|
-
*
|
|
17
|
+
* Scans serialized conversation text for explicit markers, but if modelId is
|
|
18
|
+
* given, it asks the LLM to intelligently extract them.
|
|
19
19
|
*/
|
|
20
|
-
export declare function extractDecisions(messages: ModelMessage[],
|
|
20
|
+
export declare function extractDecisions(messages: ModelMessage[], modelId?: string, customInstructions?: string): Promise<ExtractedDecisions>;
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
*/
|
|
8
8
|
import { generateText } from "ai";
|
|
9
9
|
import { serializeConversation } from "../../orchestrator/compaction.js";
|
|
10
|
-
import { resolveModelRuntime, resolveTemperatureParam
|
|
10
|
+
import { resolveModelRuntime, resolveTemperatureParam } from "../../providers/runtime.js";
|
|
11
11
|
import { logger } from "../../utils/logger.js";
|
|
12
12
|
import { capCompactionInput } from "./input-guard.js";
|
|
13
13
|
import { extractPreservedBlocks } from "./preserve.js";
|
|
@@ -27,10 +27,10 @@ function matchAll(text, re) {
|
|
|
27
27
|
/**
|
|
28
28
|
* Extract decisions, facts, and constraints from messages.
|
|
29
29
|
*
|
|
30
|
-
* Scans serialized conversation text for explicit markers, but if
|
|
31
|
-
*
|
|
30
|
+
* Scans serialized conversation text for explicit markers, but if modelId is
|
|
31
|
+
* given, it asks the LLM to intelligently extract them.
|
|
32
32
|
*/
|
|
33
|
-
export async function extractDecisions(messages,
|
|
33
|
+
export async function extractDecisions(messages, modelId, customInstructions) {
|
|
34
34
|
if (messages.length === 0) {
|
|
35
35
|
return { decisions: [], facts: [], constraints: [] };
|
|
36
36
|
}
|
|
@@ -42,9 +42,9 @@ export async function extractDecisions(messages, provider, modelId, customInstru
|
|
|
42
42
|
const facts = [];
|
|
43
43
|
const constraints = [];
|
|
44
44
|
let usedLLM = false;
|
|
45
|
-
if (
|
|
45
|
+
if (modelId) {
|
|
46
46
|
try {
|
|
47
|
-
const runtime = resolveModelRuntime(
|
|
47
|
+
const runtime = resolveModelRuntime(modelId);
|
|
48
48
|
const extraPrompt = customInstructions ? `\n\nUSER FOCUS/INSTRUCTIONS:\n${customInstructions}\n` : "";
|
|
49
49
|
// Guard input against the summarizer's context window (see input-guard).
|
|
50
50
|
const guardedInput = capCompactionInput(serialized, runtime.modelInfo?.contextWindow ?? 0);
|
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
* Snapshot full chat to history/<timestamp>.md before compressing.
|
|
7
7
|
*/
|
|
8
8
|
import type { ModelMessage } from "ai";
|
|
9
|
+
import { type CompactProgressFn } from "./progress.js";
|
|
9
10
|
/**
|
|
10
11
|
* Anti-mù for the deliberate (/compact) path — parity with the auto/tool-loop
|
|
11
12
|
* compaction which persists every elided tool output via persistArtifact.
|
|
@@ -33,4 +34,4 @@ export interface CompactionResult {
|
|
|
33
34
|
* 3. Pass 2: compressChat -> compressed output
|
|
34
35
|
* 4. Return metrics
|
|
35
36
|
*/
|
|
36
|
-
export declare function deliberateCompact(flowDir: string, messages: ModelMessage[], systemPrompt: string, tokenBudget: number,
|
|
37
|
+
export declare function deliberateCompact(flowDir: string, messages: ModelMessage[], systemPrompt: string, tokenBudget: number, modelId?: string, customInstructions?: string, onProgress?: CompactProgressFn): Promise<CompactionResult>;
|
|
@@ -15,6 +15,7 @@ import { logger } from "../../utils/logger.js";
|
|
|
15
15
|
import { readArtifact, writeArtifact } from "../artifact-io.js";
|
|
16
16
|
import { compressChat } from "./compress.js";
|
|
17
17
|
import { extractDecisions } from "./extract.js";
|
|
18
|
+
import { stageProgress } from "./progress.js";
|
|
18
19
|
/**
|
|
19
20
|
* Anti-mù for the deliberate (/compact) path — parity with the auto/tool-loop
|
|
20
21
|
* compaction which persists every elided tool output via persistArtifact.
|
|
@@ -83,13 +84,35 @@ export function recordToolArtifactsForRehydrate(messages, projectPath) {
|
|
|
83
84
|
* 3. Pass 2: compressChat -> compressed output
|
|
84
85
|
* 4. Return metrics
|
|
85
86
|
*/
|
|
86
|
-
export async function deliberateCompact(flowDir, messages, systemPrompt, tokenBudget,
|
|
87
|
+
export async function deliberateCompact(flowDir, messages, systemPrompt, tokenBudget, modelId, customInstructions, onProgress) {
|
|
88
|
+
// Callers reach this through @ts-nocheck UI code, so an argument-order drift
|
|
89
|
+
// is invisible to tsc. It is NOT invisible at runtime: a non-string modelId
|
|
90
|
+
// makes resolveModelRuntime throw, compressChat catches it, and /compact
|
|
91
|
+
// silently degrades to truncation — which is how commit e2100fb7 shipped a
|
|
92
|
+
// broken /compact. Fail loudly instead of summarizing nothing.
|
|
93
|
+
if (modelId !== undefined && typeof modelId !== "string") {
|
|
94
|
+
throw new TypeError(`deliberateCompact: modelId must be a model id string, got ${typeof modelId}. ` +
|
|
95
|
+
`Check the argument order at the call site.`);
|
|
96
|
+
}
|
|
97
|
+
const report = (stage, fraction) => {
|
|
98
|
+
if (!onProgress)
|
|
99
|
+
return;
|
|
100
|
+
try {
|
|
101
|
+
onProgress(stageProgress(stage, fraction));
|
|
102
|
+
}
|
|
103
|
+
catch (err) {
|
|
104
|
+
// A broken progress sink must never fail the compaction it is reporting.
|
|
105
|
+
logger.error("orchestrator", "compact progress callback threw", { stage, message: err?.message });
|
|
106
|
+
}
|
|
107
|
+
};
|
|
108
|
+
report("artifacts");
|
|
87
109
|
// Anti-mù parity: persist every tool result to the artifact cache (+ EE)
|
|
88
110
|
// BEFORE we summarize the history away, so the model can still rehydrate a
|
|
89
111
|
// specific tool output via ee_query "tool-artifact id=<id>" after /compact.
|
|
90
112
|
recordToolArtifactsForRehydrate(messages, flowDir);
|
|
91
113
|
// Pass 1: Extract decisions/facts/constraints
|
|
92
|
-
|
|
114
|
+
report("extract");
|
|
115
|
+
const extracted = await extractDecisions(messages, modelId, customInstructions);
|
|
93
116
|
const totalExtracted = extracted.decisions.length + extracted.facts.length + extracted.constraints.length;
|
|
94
117
|
// Append to decisions.md
|
|
95
118
|
if (totalExtracted > 0) {
|
|
@@ -110,6 +133,7 @@ export async function deliberateCompact(flowDir, messages, systemPrompt, tokenBu
|
|
|
110
133
|
await writeArtifact(flowDir, "decisions.md", existing, ["Decisions", "Facts", "Constraints"]);
|
|
111
134
|
}
|
|
112
135
|
// Snapshot full chat to history/
|
|
136
|
+
report("snapshot");
|
|
113
137
|
const historyDir = path.join(flowDir, "history");
|
|
114
138
|
await fs.mkdir(historyDir, { recursive: true });
|
|
115
139
|
const timestamp = new Date().toISOString().replace(/[:.]/g, "-");
|
|
@@ -122,7 +146,9 @@ export async function deliberateCompact(flowDir, messages, systemPrompt, tokenBu
|
|
|
122
146
|
// Token estimation before
|
|
123
147
|
const tokensBefore = estimateConversationTokens(systemPrompt, messages);
|
|
124
148
|
// Pass 2: Compress
|
|
125
|
-
|
|
149
|
+
report("compress");
|
|
150
|
+
const compressed = await compressChat(messages, systemPrompt, tokenBudget, modelId, customInstructions, (f) => report("compress", f));
|
|
151
|
+
report("done");
|
|
126
152
|
return {
|
|
127
153
|
decisionsExtracted: totalExtracted,
|
|
128
154
|
tokensBeforeCompress: tokensBefore,
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* src/flow/compaction/progress.ts
|
|
3
|
+
*
|
|
4
|
+
* Progress model for deliberate (/compact) compaction.
|
|
5
|
+
*
|
|
6
|
+
* A compaction is a fixed pipeline of stages (see index.ts), so the percentage
|
|
7
|
+
* is stage-weighted rather than invented: each stage owns a span of the bar and
|
|
8
|
+
* only advances when that stage really advances. The two LLM passes dominate the
|
|
9
|
+
* wall-clock, which is why they own most of the span; the compress pass streams,
|
|
10
|
+
* so it reports a genuine within-stage fraction instead of freezing.
|
|
11
|
+
*
|
|
12
|
+
* Weights are ordered spans covering 0..100 with no gaps — asserted in tests so
|
|
13
|
+
* a new stage can't silently make the bar jump or stall.
|
|
14
|
+
*/
|
|
15
|
+
export type CompactStage = "artifacts" | "extract" | "snapshot" | "compress" | "done";
|
|
16
|
+
export interface CompactProgress {
|
|
17
|
+
stage: CompactStage;
|
|
18
|
+
/** 0..100, monotonic across the run. */
|
|
19
|
+
percent: number;
|
|
20
|
+
/** Human-readable current step, e.g. "Compressing history…". */
|
|
21
|
+
label: string;
|
|
22
|
+
}
|
|
23
|
+
export type CompactProgressFn = (progress: CompactProgress) => void;
|
|
24
|
+
interface StageSpan {
|
|
25
|
+
from: number;
|
|
26
|
+
to: number;
|
|
27
|
+
label: string;
|
|
28
|
+
}
|
|
29
|
+
export declare const COMPACT_STAGE_SPANS: Record<CompactStage, StageSpan>;
|
|
30
|
+
/**
|
|
31
|
+
* Percent for a stage at `fraction` (0..1) of its own work. Callers that cannot
|
|
32
|
+
* measure within-stage progress pass 0 and land on the stage's start.
|
|
33
|
+
*/
|
|
34
|
+
export declare function stageProgress(stage: CompactStage, fraction?: number): CompactProgress;
|
|
35
|
+
export {};
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* src/flow/compaction/progress.ts
|
|
3
|
+
*
|
|
4
|
+
* Progress model for deliberate (/compact) compaction.
|
|
5
|
+
*
|
|
6
|
+
* A compaction is a fixed pipeline of stages (see index.ts), so the percentage
|
|
7
|
+
* is stage-weighted rather than invented: each stage owns a span of the bar and
|
|
8
|
+
* only advances when that stage really advances. The two LLM passes dominate the
|
|
9
|
+
* wall-clock, which is why they own most of the span; the compress pass streams,
|
|
10
|
+
* so it reports a genuine within-stage fraction instead of freezing.
|
|
11
|
+
*
|
|
12
|
+
* Weights are ordered spans covering 0..100 with no gaps — asserted in tests so
|
|
13
|
+
* a new stage can't silently make the bar jump or stall.
|
|
14
|
+
*/
|
|
15
|
+
export const COMPACT_STAGE_SPANS = {
|
|
16
|
+
artifacts: { from: 0, to: 5, label: "Preserving tool artifacts…" },
|
|
17
|
+
extract: { from: 5, to: 40, label: "Extracting decisions…" },
|
|
18
|
+
snapshot: { from: 40, to: 50, label: "Snapshotting history…" },
|
|
19
|
+
compress: { from: 50, to: 100, label: "Compressing history…" },
|
|
20
|
+
done: { from: 100, to: 100, label: "Compacted" },
|
|
21
|
+
};
|
|
22
|
+
/**
|
|
23
|
+
* Percent for a stage at `fraction` (0..1) of its own work. Callers that cannot
|
|
24
|
+
* measure within-stage progress pass 0 and land on the stage's start.
|
|
25
|
+
*/
|
|
26
|
+
export function stageProgress(stage, fraction = 0) {
|
|
27
|
+
const span = COMPACT_STAGE_SPANS[stage];
|
|
28
|
+
const clamped = Math.max(0, Math.min(1, Number.isFinite(fraction) ? fraction : 0));
|
|
29
|
+
return {
|
|
30
|
+
stage,
|
|
31
|
+
percent: Math.round(span.from + (span.to - span.from) * clamped),
|
|
32
|
+
label: span.label,
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
//# sourceMappingURL=progress.js.map
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export declare const PACKAGE_VERSION = "1.
|
|
1
|
+
export declare const PACKAGE_VERSION = "1.9.0";
|
|
2
2
|
export declare const PACKAGE_DESCRIPTION = "BYOK AI coding agent with multi-model council debate, role-based routing, and auto-compact.";
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// AUTO-GENERATED by scripts/sync-version.cjs. DO NOT EDIT BY HAND.
|
|
2
2
|
// Sourced from package.json at build time so it survives bun --compile bundling.
|
|
3
|
-
export const PACKAGE_VERSION = "1.
|
|
3
|
+
export const PACKAGE_VERSION = "1.9.0";
|
|
4
4
|
export const PACKAGE_DESCRIPTION = "BYOK AI coding agent with multi-model council debate, role-based routing, and auto-compact.";
|
|
5
5
|
//# sourceMappingURL=version.js.map
|
package/dist/src/gsd/flags.d.ts
CHANGED
|
@@ -53,3 +53,14 @@ export declare function isRoundGroupsEnabled(): boolean;
|
|
|
53
53
|
* MUONROI_TASK_AWARE_PANEL=0.
|
|
54
54
|
*/
|
|
55
55
|
export declare function isTaskAwarePanelEnabled(): boolean;
|
|
56
|
+
/**
|
|
57
|
+
* Plan-review debate retry budget. The council debate can return an EMPTY
|
|
58
|
+
* synthesis on any of its fail-open paths (provider unreachable, sub-phase
|
|
59
|
+
* catch, user/watchdog abort) — a silent null that plan-council previously
|
|
60
|
+
* collapsed into a permanent forced-`revise`, bricking an autonomous /ideal
|
|
61
|
+
* heavy run (0 code, no human to re-trigger plan-review). On empty synthesis
|
|
62
|
+
* we retry the debate up to this many times before falling back to the
|
|
63
|
+
* perspective path. Default 1 (up to 2 debate attempts). Env override:
|
|
64
|
+
* MUONROI_PLAN_REVIEW_DEBATE_RETRIES. Clamped to [0, 5].
|
|
65
|
+
*/
|
|
66
|
+
export declare function getPlanReviewDebateRetries(): number;
|
package/dist/src/gsd/flags.js
CHANGED
|
@@ -80,4 +80,23 @@ export function isTaskAwarePanelEnabled() {
|
|
|
80
80
|
const raw = process.env.MUONROI_TASK_AWARE_PANEL;
|
|
81
81
|
return raw !== "0" && raw?.toLowerCase() !== "false";
|
|
82
82
|
}
|
|
83
|
+
/**
|
|
84
|
+
* Plan-review debate retry budget. The council debate can return an EMPTY
|
|
85
|
+
* synthesis on any of its fail-open paths (provider unreachable, sub-phase
|
|
86
|
+
* catch, user/watchdog abort) — a silent null that plan-council previously
|
|
87
|
+
* collapsed into a permanent forced-`revise`, bricking an autonomous /ideal
|
|
88
|
+
* heavy run (0 code, no human to re-trigger plan-review). On empty synthesis
|
|
89
|
+
* we retry the debate up to this many times before falling back to the
|
|
90
|
+
* perspective path. Default 1 (up to 2 debate attempts). Env override:
|
|
91
|
+
* MUONROI_PLAN_REVIEW_DEBATE_RETRIES. Clamped to [0, 5].
|
|
92
|
+
*/
|
|
93
|
+
export function getPlanReviewDebateRetries() {
|
|
94
|
+
const raw = process.env.MUONROI_PLAN_REVIEW_DEBATE_RETRIES;
|
|
95
|
+
if (raw === undefined)
|
|
96
|
+
return 1;
|
|
97
|
+
const n = Number.parseInt(raw, 10);
|
|
98
|
+
if (Number.isNaN(n) || n < 0)
|
|
99
|
+
return 1;
|
|
100
|
+
return Math.min(n, 5);
|
|
101
|
+
}
|
|
83
102
|
//# sourceMappingURL=flags.js.map
|