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
|
@@ -12,26 +12,140 @@
|
|
|
12
12
|
* Cost target: <200 input tokens, <10 output tokens per call (~$0.0001 on
|
|
13
13
|
* DeepSeek Flash). Timeout 2500ms — bails fast if the model stalls.
|
|
14
14
|
*/
|
|
15
|
+
import { appendFileSync } from "node:fs";
|
|
15
16
|
import { streamText } from "ai";
|
|
16
|
-
import { getModelInfo } from "../models/registry.js";
|
|
17
|
+
import { getModelInfo, SWITCH_PROVIDER_ORDER } from "../models/registry.js";
|
|
17
18
|
import { getProviderCapabilities } from "../providers/capabilities.js";
|
|
18
|
-
import {
|
|
19
|
+
import { getConfiguredProviders, loadKeyForProvider, ProviderKeyMissingError } from "../providers/keychain.js";
|
|
20
|
+
import { createProviderFactoryAsync, resolveModelRuntime } from "../providers/runtime.js";
|
|
19
21
|
import { getRoutedModelByTier } from "../router/peak-hour.js";
|
|
20
|
-
|
|
21
|
-
//
|
|
22
|
-
//
|
|
23
|
-
//
|
|
24
|
-
//
|
|
25
|
-
//
|
|
26
|
-
//
|
|
27
|
-
// The ceiling is a
|
|
28
|
-
//
|
|
29
|
-
|
|
22
|
+
import { isProviderDisabled } from "../utils/settings.js";
|
|
23
|
+
// Single flat classify ceiling for EVERY model. Harness-measured latency
|
|
24
|
+
// (2026-07-15, grok-composer via OAuth, 7 samples) is 1045–1277ms — no tested
|
|
25
|
+
// model (agentic balanced OR fast flash) approaches even the legacy 2.5s cap,
|
|
26
|
+
// so a tier-scaled timeout was solving a non-existent latency problem: the
|
|
27
|
+
// /ideal over-engineering root cause was NOT a timeout abort but grok-composer
|
|
28
|
+
// ignoring the terse contract (it emits task-planning prose, never the 8-word
|
|
29
|
+
// line → null → fail-open). The ceiling is a pure safety net: a healthy model
|
|
30
|
+
// returns in <1.5s, so the headroom only bites a genuinely stuck call. Data-
|
|
31
|
+
// driven off measurement, not an identity/tier proxy string.
|
|
32
|
+
const CLASSIFY_TIMEOUT_MS = 8000;
|
|
33
|
+
// Absolute wall-clock cap for the WHOLE classify (every candidate AND its
|
|
34
|
+
// self-repair). Enforced via a shared deadline passed into attemptClassify, so
|
|
35
|
+
// a run of dead/hanging keys — or one legitimately slow reasoning model —
|
|
36
|
+
// degrades to fail-open in bounded time. Sized to fit a healthy reasoning fast
|
|
37
|
+
// model twice over: deepseek-v4-flash measured 2026-07-15 answers a classify in
|
|
38
|
+
// ~2–5s (with a valid key), so 10s leaves room for one candidate + a self-repair
|
|
39
|
+
// before the deadline. A dead key ahead of it (auth-fails in ~0.2s) barely eats
|
|
40
|
+
// the budget; only a genuinely hanging candidate consumes it.
|
|
41
|
+
const CLASSIFY_TOTAL_BUDGET_MS = 10_000;
|
|
42
|
+
// Floor for any single streamText attempt's own timeout, so a nearly-exhausted
|
|
43
|
+
// deadline still gives a final candidate a real (if short) chance rather than an
|
|
44
|
+
// instant abort.
|
|
45
|
+
const CLASSIFY_MIN_ATTEMPT_MS = 1200;
|
|
30
46
|
// Eight comma-separated words now (added <clarity>) — ~20-30 tokens worst case
|
|
31
47
|
// ("documentation,balanced,task,report,standard,ecosystem,vietnamese,underspecified").
|
|
32
48
|
// 56 keeps headroom without padding (the model still stops after eight words).
|
|
33
49
|
const NONREASONING_MAX_OUTPUT_TOKENS = 56;
|
|
34
50
|
const REASONING_MAX_OUTPUT_TOKENS = 2048;
|
|
51
|
+
/**
|
|
52
|
+
* Compute the classify call budget from the resolved model's catalog metadata.
|
|
53
|
+
*
|
|
54
|
+
* TIMEOUT is a flat safety-net ceiling for all models — see CLASSIFY_TIMEOUT_MS.
|
|
55
|
+
* Measurement showed classify latency is provider-independent and well under
|
|
56
|
+
* the ceiling, so keying the timeout off `tier`/`reasoning` added no value and
|
|
57
|
+
* amounted to an identity-proxy soft-hardcode.
|
|
58
|
+
*
|
|
59
|
+
* MAX-OUTPUT scales with REASONING only: a reasoning model burns its output
|
|
60
|
+
* budget on reasoning tokens before any visible text, so it needs the room in
|
|
61
|
+
* tokens; a non-reasoning model emits the 8 words directly. This knob is real —
|
|
62
|
+
* it is the fix for reasoning models routing the verdict into the reasoning
|
|
63
|
+
* channel — and stays decoupled from the (now flat) timeout.
|
|
64
|
+
*/
|
|
65
|
+
export function classifierBudget(modelInfo) {
|
|
66
|
+
const isReasoning = modelInfo?.reasoning === true;
|
|
67
|
+
return {
|
|
68
|
+
isReasoning,
|
|
69
|
+
timeoutMs: CLASSIFY_TIMEOUT_MS,
|
|
70
|
+
maxOutputTokens: isReasoning ? REASONING_MAX_OUTPUT_TOKENS : NONREASONING_MAX_OUTPUT_TOKENS,
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Session-scoped cache of cross-provider factories built for the throwaway
|
|
75
|
+
* classify. Keyed by provider so the OAuth-aware build (keychain read + token
|
|
76
|
+
* refresh) happens at most once per provider per process, not per turn.
|
|
77
|
+
*/
|
|
78
|
+
const crossFactoryCache = new Map();
|
|
79
|
+
/** Test seam — clear the cross-provider factory cache between specs. */
|
|
80
|
+
export function __resetClassifyFactoryCache() {
|
|
81
|
+
crossFactoryCache.clear();
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Build (or reuse) a real factory for a DIFFERENT provider than the session's,
|
|
85
|
+
* so the throwaway classify can run on a keyed instruction-following model when
|
|
86
|
+
* the session provider has none. Mirrors the council's `resolveCouncilFactory`:
|
|
87
|
+
* `loadKeyForProvider` for API-key providers, falling back to
|
|
88
|
+
* `createProviderFactoryAsync` for OAuth-only providers (injects the bearer
|
|
89
|
+
* token). Failures degrade gracefully (logged, returns undefined → caller keeps
|
|
90
|
+
* the session model). Never throws.
|
|
91
|
+
*/
|
|
92
|
+
async function resolveCrossProviderClassifyFactory(providerId) {
|
|
93
|
+
const cached = crossFactoryCache.get(providerId);
|
|
94
|
+
if (cached)
|
|
95
|
+
return cached;
|
|
96
|
+
try {
|
|
97
|
+
let apiKey;
|
|
98
|
+
try {
|
|
99
|
+
apiKey = await loadKeyForProvider(providerId);
|
|
100
|
+
}
|
|
101
|
+
catch (err) {
|
|
102
|
+
if (!(err instanceof ProviderKeyMissingError))
|
|
103
|
+
throw err;
|
|
104
|
+
// OAuth-only provider — createProviderFactoryAsync injects the bearer token.
|
|
105
|
+
}
|
|
106
|
+
const { factory } = await createProviderFactoryAsync(providerId, apiKey ? { apiKey } : {});
|
|
107
|
+
crossFactoryCache.set(providerId, factory);
|
|
108
|
+
return factory;
|
|
109
|
+
}
|
|
110
|
+
catch (err) {
|
|
111
|
+
console.error(`[pil.llm-classify] cross-provider classify factory build failed for ${providerId}: ${err?.message}`);
|
|
112
|
+
return undefined;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* Ordered list of keyed fast-tier models from providers OTHER than the session's,
|
|
117
|
+
* for the throwaway classify. Candidate order is the catalog's vendor-defined
|
|
118
|
+
* `switch_provider_order` (zero-hardcode), gated by `getConfiguredProviders`
|
|
119
|
+
* (the authoritative credential check — unifies API key / env / OAuth) and the
|
|
120
|
+
* user's disabled-provider setting. The caller tries them in order and falls
|
|
121
|
+
* through to the next on an auth/stream failure — so a configured-but-DEAD key
|
|
122
|
+
* (e.g. an expired deepseek key) doesn't strand the classify at fail-open.
|
|
123
|
+
* Empty when no other provider is configured with a fast tier → caller keeps the
|
|
124
|
+
* session model (status quo).
|
|
125
|
+
*/
|
|
126
|
+
async function pickCrossProviderClassifyModels(excludeProvider) {
|
|
127
|
+
let configured;
|
|
128
|
+
try {
|
|
129
|
+
configured = new Set(await getConfiguredProviders());
|
|
130
|
+
}
|
|
131
|
+
catch (err) {
|
|
132
|
+
console.error(`[pil.llm-classify] getConfiguredProviders failed for classify route: ${err?.message}`);
|
|
133
|
+
return [];
|
|
134
|
+
}
|
|
135
|
+
const out = [];
|
|
136
|
+
for (const p of SWITCH_PROVIDER_ORDER) {
|
|
137
|
+
if (p === excludeProvider)
|
|
138
|
+
continue;
|
|
139
|
+
if (isProviderDisabled(p))
|
|
140
|
+
continue;
|
|
141
|
+
if (!configured.has(p))
|
|
142
|
+
continue;
|
|
143
|
+
const m = getRoutedModelByTier("fast", p);
|
|
144
|
+
if (m && m.provider === p)
|
|
145
|
+
out.push({ modelId: m.id, providerId: p });
|
|
146
|
+
}
|
|
147
|
+
return out;
|
|
148
|
+
}
|
|
35
149
|
/**
|
|
36
150
|
* Per-namespace shallow merge of providerOptions. The base already carries
|
|
37
151
|
* factory-level defaults folded into the provider namespace (e.g. OAuth
|
|
@@ -225,28 +339,64 @@ function parseResponse(raw) {
|
|
|
225
339
|
* Returns null if the call fails / times out / parses to garbage. Callers must
|
|
226
340
|
* fail-open (keep prior taskType, do not block the turn).
|
|
227
341
|
*/
|
|
228
|
-
|
|
342
|
+
/**
|
|
343
|
+
* Order the classify candidate list.
|
|
344
|
+
*
|
|
345
|
+
* - `hasSameFast` (e.g. openai → gpt-5.4-mini): the same-provider fast tier is a
|
|
346
|
+
* real fast model, so try it FIRST, then keyed cross-provider models as a
|
|
347
|
+
* FALLBACK. This is the fix for the failure-not-just-absence case: an
|
|
348
|
+
* openai-OAuth session's fast tier can 401 on the api-key path — previously
|
|
349
|
+
* the chain stopped there and fail-opened to "standard" (so /ideal
|
|
350
|
+
* over-councilled trivial tasks). Appending the cross-provider models lets a
|
|
351
|
+
* working alternative (a keyed deepseek/opencode fast model) rescue it.
|
|
352
|
+
* - No same-provider fast tier (e.g. xai, whose session model is agentic and
|
|
353
|
+
* ignores the terse contract): try keyed cross-provider fast models FIRST and
|
|
354
|
+
* keep the session model as the last-resort candidate.
|
|
355
|
+
*/
|
|
356
|
+
export function orderClassifyCandidates(args) {
|
|
357
|
+
const candidates = [];
|
|
358
|
+
if (args.hasSameFast) {
|
|
359
|
+
candidates.push({ modelId: args.primaryModelId, providerId: null });
|
|
360
|
+
for (const m of args.crossModels)
|
|
361
|
+
candidates.push({ modelId: m.modelId, providerId: m.providerId });
|
|
362
|
+
}
|
|
363
|
+
else {
|
|
364
|
+
for (const m of args.crossModels)
|
|
365
|
+
candidates.push({ modelId: m.modelId, providerId: m.providerId });
|
|
366
|
+
candidates.push({ modelId: args.primaryModelId, providerId: null });
|
|
367
|
+
}
|
|
368
|
+
return candidates;
|
|
369
|
+
}
|
|
370
|
+
export function createLlmClassifier(modelId, classifyOpts) {
|
|
229
371
|
return async function classify(prompt, opts) {
|
|
230
372
|
const signal = opts?.signal;
|
|
231
373
|
const recentTurns = opts?.recentTurns;
|
|
232
|
-
const
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
374
|
+
const debug = process.env.MUONROI_DEBUG_PIL_CLASSIFY === "1";
|
|
375
|
+
// Bounded recent-conversation block so the classifier can resolve back-
|
|
376
|
+
// references in a terse follow-up ("từ các phần đó", "làm tiếp", "this")
|
|
377
|
+
// instead of scoring the isolated sentence. Same for every candidate.
|
|
378
|
+
const trimmedRecent = recentTurns?.trim();
|
|
379
|
+
const promptWithContext = trimmedRecent
|
|
380
|
+
? `[RECENT CONVERSATION — reference only, do NOT classify this]\n${trimmedRecent.slice(0, 800)}\n\n` +
|
|
381
|
+
`[NEW USER MESSAGE — classify THIS; if it refers back to the conversation above, judge the depth of the work it actually entails]\n${prompt.slice(0, 600)}`
|
|
382
|
+
: prompt.slice(0, 600);
|
|
383
|
+
const fullRecent = recentTurns?.trim();
|
|
384
|
+
const repairPrompt = (fullRecent
|
|
385
|
+
? `[RECENT CONVERSATION — reference only, do NOT classify this]\n${fullRecent.slice(0, 1500)}\n\n`
|
|
386
|
+
: "") + `[NEW USER MESSAGE — classify THIS]\n${prompt.slice(0, 1500)}`;
|
|
387
|
+
// One classify attempt against a single resolved model, including the
|
|
388
|
+
// self-repair retry on that same model. Returns the parsed verdict, or null
|
|
389
|
+
// (unparseable OR provider/stream error) so the caller can fall through to
|
|
390
|
+
// the next candidate. Each attempt owns its AbortController/timer.
|
|
391
|
+
const attemptClassify = async (runtime, cmId, deadlineMs) => {
|
|
392
|
+
// Max-output scales with reasoning; each streamText timeout is bounded by
|
|
393
|
+
// the shared deadline (min of the flat ceiling and the time left), so the
|
|
394
|
+
// whole classify — this attempt AND its repair — never overruns the budget.
|
|
395
|
+
const { isReasoning } = classifierBudget(runtime.modelInfo);
|
|
396
|
+
const timeoutMs = Math.max(CLASSIFY_MIN_ATTEMPT_MS, Math.min(CLASSIFY_TIMEOUT_MS, deadlineMs - Date.now()));
|
|
244
397
|
const dropMaxTokens = runtime.unsupportedParams?.includes("maxOutputTokens") === true;
|
|
245
398
|
const maxOut = isReasoning ? REASONING_MAX_OUTPUT_TOKENS : NONREASONING_MAX_OUTPUT_TOKENS;
|
|
246
|
-
// Minimize reasoning cost: force the lowest effort the provider exposes
|
|
247
|
-
// this throwaway 2-word classification. Only providers with
|
|
248
|
-
// `supportsReasoningEffort` (openai, xai) honor it; deepseek has no per-call
|
|
249
|
-
// knob (disable via MUONROI_DEEPSEEK_DISABLE_THINKING at the factory).
|
|
399
|
+
// Minimize reasoning cost: force the lowest effort the provider exposes.
|
|
250
400
|
let providerOptions = runtime.providerOptions;
|
|
251
401
|
if (isReasoning && runtime.modelInfo?.supportsReasoningEffort && runtime.modelInfo.provider) {
|
|
252
402
|
const lowEffort = getProviderCapabilities(runtime.modelInfo.provider).buildProviderOptions({
|
|
@@ -255,93 +405,186 @@ export function createLlmClassifier(factory, modelId) {
|
|
|
255
405
|
});
|
|
256
406
|
providerOptions = mergeProviderOptions(runtime.providerOptions, lowEffort);
|
|
257
407
|
}
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
const trimmedRecent = recentTurns?.trim();
|
|
264
|
-
const promptWithContext = trimmedRecent
|
|
265
|
-
? `[RECENT CONVERSATION — reference only, do NOT classify this]\n${trimmedRecent.slice(0, 800)}\n\n` +
|
|
266
|
-
`[NEW USER MESSAGE — classify THIS; if it refers back to the conversation above, judge the depth of the work it actually entails]\n${prompt.slice(0, 600)}`
|
|
267
|
-
: prompt.slice(0, 600);
|
|
268
|
-
const result = streamText({
|
|
269
|
-
model: runtime.model,
|
|
270
|
-
abortSignal: combinedSignal,
|
|
271
|
-
system: SYSTEM_PROMPT,
|
|
272
|
-
prompt: promptWithContext,
|
|
273
|
-
...(dropMaxTokens ? {} : { maxOutputTokens: maxOut }),
|
|
274
|
-
...(providerOptions ? { providerOptions } : {}),
|
|
275
|
-
});
|
|
276
|
-
let text = "";
|
|
277
|
-
let reasoningText = "";
|
|
278
|
-
const partCounts = {};
|
|
279
|
-
const debug = process.env.MUONROI_DEBUG_PIL_CLASSIFY === "1";
|
|
280
|
-
for await (const part of result.fullStream) {
|
|
281
|
-
if (debug)
|
|
282
|
-
partCounts[part.type] = (partCounts[part.type] ?? 0) + 1;
|
|
283
|
-
if (part.type === "text-delta")
|
|
284
|
-
text += part.textDelta ?? part.text ?? "";
|
|
285
|
-
else if (part.type === "reasoning-delta")
|
|
286
|
-
reasoningText += part.textDelta ?? part.text ?? "";
|
|
287
|
-
}
|
|
288
|
-
if (debug) {
|
|
289
|
-
console.error(`[pil.llm-classify] raw(${modelId}) maxOut=${dropMaxTokens ? "dropped" : maxOut} ` +
|
|
290
|
-
`parts=${JSON.stringify(partCounts)} text<<<${text}>>> reasoning<<<${reasoningText.slice(0, 200)}>>>`);
|
|
291
|
-
}
|
|
292
|
-
// Reasoning models occasionally route the entire answer into reasoning
|
|
293
|
-
// parts (no committed text). Fall back to the reasoning channel so the
|
|
294
|
-
// 2-word verdict is still recoverable.
|
|
295
|
-
const primary = parseResponse(text) ?? (reasoningText ? parseResponse(reasoningText) : null);
|
|
296
|
-
if (primary)
|
|
297
|
-
return primary;
|
|
298
|
-
// Self-repair (agent-first recovery — NOT a regex fallback): the model's
|
|
299
|
-
// first reply did not parse into the eight-word contract. Call the model
|
|
300
|
-
// ONCE more with the FULL prompt + recent context (no 600-char trim) and
|
|
301
|
-
// an explicit format-repair instruction on a doubled budget. Only if this
|
|
302
|
-
// ALSO fails do we return null — and the caller then surfaces an honest
|
|
303
|
-
// UNKNOWN classification, never a keyword-regex guess.
|
|
304
|
-
if (timer)
|
|
305
|
-
clearTimeout(timer);
|
|
306
|
-
const repairController = new AbortController();
|
|
307
|
-
const repairTimer = setTimeout(() => repairController.abort(), isReasoning ? REASONING_CLASSIFY_TIMEOUT_MS : LLM_CLASSIFY_TIMEOUT_MS);
|
|
308
|
-
const repairSignal = signal
|
|
309
|
-
? (AbortSignal.any?.([signal, repairController.signal]) ?? repairController.signal)
|
|
310
|
-
: repairController.signal;
|
|
408
|
+
const controller = new AbortController();
|
|
409
|
+
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
410
|
+
const combinedSignal = signal
|
|
411
|
+
? (AbortSignal.any?.([signal, controller.signal]) ?? controller.signal)
|
|
412
|
+
: controller.signal;
|
|
311
413
|
try {
|
|
312
|
-
const
|
|
313
|
-
const
|
|
314
|
-
? `[RECENT CONVERSATION — reference only, do NOT classify this]\n${fullRecent.slice(0, 1500)}\n\n`
|
|
315
|
-
: "") + `[NEW USER MESSAGE — classify THIS]\n${prompt.slice(0, 1500)}`;
|
|
316
|
-
const repairRun = streamText({
|
|
414
|
+
const t0 = Date.now();
|
|
415
|
+
const result = streamText({
|
|
317
416
|
model: runtime.model,
|
|
318
|
-
abortSignal:
|
|
319
|
-
system:
|
|
320
|
-
prompt:
|
|
321
|
-
...(dropMaxTokens ? {} : { maxOutputTokens: maxOut
|
|
417
|
+
abortSignal: combinedSignal,
|
|
418
|
+
system: SYSTEM_PROMPT,
|
|
419
|
+
prompt: promptWithContext,
|
|
420
|
+
...(dropMaxTokens ? {} : { maxOutputTokens: maxOut }),
|
|
322
421
|
...(providerOptions ? { providerOptions } : {}),
|
|
323
422
|
});
|
|
324
|
-
let
|
|
325
|
-
let
|
|
326
|
-
|
|
423
|
+
let text = "";
|
|
424
|
+
let reasoningText = "";
|
|
425
|
+
let streamError = "";
|
|
426
|
+
const partCounts = {};
|
|
427
|
+
for await (const part of result.fullStream) {
|
|
428
|
+
if (debug)
|
|
429
|
+
partCounts[part.type] = (partCounts[part.type] ?? 0) + 1;
|
|
327
430
|
if (part.type === "text-delta")
|
|
328
|
-
|
|
431
|
+
text += part.textDelta ?? part.text ?? "";
|
|
329
432
|
else if (part.type === "reasoning-delta")
|
|
330
|
-
|
|
433
|
+
reasoningText += part.textDelta ?? part.text ?? "";
|
|
434
|
+
else if (part.type === "error") {
|
|
435
|
+
const e = part.error;
|
|
436
|
+
streamError = e instanceof Error ? e.message : String(e ?? "unknown");
|
|
437
|
+
}
|
|
331
438
|
}
|
|
332
|
-
const
|
|
333
|
-
if (
|
|
334
|
-
console.error(`[pil.llm-classify]
|
|
439
|
+
const elapsedMs = Date.now() - t0;
|
|
440
|
+
if (debug) {
|
|
441
|
+
console.error(`[pil.llm-classify] raw(${cmId}${cmId !== modelId ? `←${modelId}` : ""}) ` +
|
|
442
|
+
`maxOut=${dropMaxTokens ? "dropped" : maxOut} streamError=${JSON.stringify(streamError)} ` +
|
|
443
|
+
`parts=${JSON.stringify(partCounts)} text<<<${text}>>> reasoning<<<${reasoningText.slice(0, 200)}>>>`);
|
|
335
444
|
}
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
445
|
+
// Reasoning models occasionally route the entire answer into reasoning
|
|
446
|
+
// parts (no committed text). Fall back to the reasoning channel.
|
|
447
|
+
let parsed = parseResponse(text) ?? (reasoningText ? parseResponse(reasoningText) : null);
|
|
448
|
+
// Metrics-only probe sink (env-gated): latency + parsed depth + any
|
|
449
|
+
// stream error. NO prompt/response content — safe to leave wired.
|
|
450
|
+
const probeLog = process.env.MUONROI_CLASSIFY_LATENCY_LOG;
|
|
451
|
+
if (probeLog) {
|
|
452
|
+
try {
|
|
453
|
+
appendFileSync(probeLog, `${JSON.stringify({
|
|
454
|
+
model: cmId,
|
|
455
|
+
from: modelId === cmId ? undefined : modelId,
|
|
456
|
+
tier: runtime.modelInfo?.tier,
|
|
457
|
+
reasoning: isReasoning,
|
|
458
|
+
timeoutMs,
|
|
459
|
+
elapsedMs,
|
|
460
|
+
textLen: text.length,
|
|
461
|
+
reasoningLen: reasoningText.length,
|
|
462
|
+
parsed: parsed != null,
|
|
463
|
+
depth: parsed?.depthTier ?? null,
|
|
464
|
+
streamError: streamError || undefined,
|
|
465
|
+
})}\n`);
|
|
466
|
+
}
|
|
467
|
+
catch (e) {
|
|
468
|
+
console.error(`[pil.llm-classify] probe-log write failed: ${e?.message}`);
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
if (parsed)
|
|
472
|
+
return parsed;
|
|
473
|
+
// Surface a swallowed provider/transport error (No-Silent-Catch): a
|
|
474
|
+
// stream `error` part means the call FAILED (auth/key/rate-limit), which
|
|
475
|
+
// is categorically different from unparseable text. Before this fix such
|
|
476
|
+
// errors vanished into a silent null → fail-open "standard". On error we
|
|
477
|
+
// skip the same-model self-repair (it would fail identically) and let the
|
|
478
|
+
// caller fall through to the next provider candidate.
|
|
479
|
+
if (streamError) {
|
|
480
|
+
console.error(`[pil.llm-classify] stream error on ${cmId} (from ${modelId}): ${streamError} — trying next candidate`);
|
|
481
|
+
return null;
|
|
482
|
+
}
|
|
483
|
+
// Self-repair (agent-first recovery — NOT a regex fallback): the reply
|
|
484
|
+
// did not parse. Call the SAME model once more with the full prompt + an
|
|
485
|
+
// explicit format-repair instruction on a doubled budget. Skip it when
|
|
486
|
+
// too little of the shared deadline remains (the caller then falls
|
|
487
|
+
// through to the next candidate / fails open).
|
|
488
|
+
clearTimeout(timer);
|
|
489
|
+
const repairBudget = deadlineMs - Date.now();
|
|
490
|
+
if (repairBudget < 1500)
|
|
491
|
+
return null;
|
|
492
|
+
const repairTimeout = Math.max(CLASSIFY_MIN_ATTEMPT_MS, Math.min(CLASSIFY_TIMEOUT_MS, repairBudget));
|
|
493
|
+
const repairController = new AbortController();
|
|
494
|
+
const repairTimer = setTimeout(() => repairController.abort(), repairTimeout);
|
|
495
|
+
const repairSignal = signal
|
|
496
|
+
? (AbortSignal.any?.([signal, repairController.signal]) ?? repairController.signal)
|
|
497
|
+
: repairController.signal;
|
|
498
|
+
try {
|
|
499
|
+
const repairRun = streamText({
|
|
500
|
+
model: runtime.model,
|
|
501
|
+
abortSignal: repairSignal,
|
|
502
|
+
system: `${SYSTEM_PROMPT}\n\n${CLASSIFY_REPAIR_INSTRUCTION}`,
|
|
503
|
+
prompt: repairPrompt,
|
|
504
|
+
...(dropMaxTokens ? {} : { maxOutputTokens: maxOut * 2 }),
|
|
505
|
+
...(providerOptions ? { providerOptions } : {}),
|
|
506
|
+
});
|
|
507
|
+
let rt = "";
|
|
508
|
+
let rr = "";
|
|
509
|
+
for await (const part of repairRun.fullStream) {
|
|
510
|
+
if (part.type === "text-delta")
|
|
511
|
+
rt += part.textDelta ?? part.text ?? "";
|
|
512
|
+
else if (part.type === "reasoning-delta")
|
|
513
|
+
rr += part.textDelta ?? part.text ?? "";
|
|
514
|
+
}
|
|
515
|
+
parsed = parseResponse(rt) ?? (rr ? parseResponse(rr) : null);
|
|
516
|
+
if (parsed)
|
|
517
|
+
console.error(`[pil.llm-classify] self-repair recovered classification (${cmId})`);
|
|
518
|
+
return parsed;
|
|
519
|
+
}
|
|
520
|
+
finally {
|
|
521
|
+
clearTimeout(repairTimer);
|
|
339
522
|
}
|
|
340
|
-
|
|
523
|
+
}
|
|
524
|
+
catch (err) {
|
|
525
|
+
console.error(`[pil.llm-classify] classify attempt failed on ${cmId}: ${err?.message}`, {
|
|
526
|
+
stack: err?.stack?.split("\n").slice(0, 3),
|
|
527
|
+
});
|
|
528
|
+
return null;
|
|
341
529
|
}
|
|
342
530
|
finally {
|
|
343
|
-
clearTimeout(
|
|
531
|
+
clearTimeout(timer);
|
|
532
|
+
}
|
|
533
|
+
};
|
|
534
|
+
try {
|
|
535
|
+
// Build the ordered candidate list. Primary = same-provider fast tier (or
|
|
536
|
+
// the session model); on NO same-provider fast tier (e.g. xai) append keyed
|
|
537
|
+
// cross-provider fast models. Measured 2026-07-15: an agentic session model
|
|
538
|
+
// (grok-composer) ignores the terse contract and emits task-planning prose
|
|
539
|
+
// → null → fail-open "standard" (the /ideal over-engineering root cause);
|
|
540
|
+
// and a configured-but-dead key (expired deepseek) errors. Trying
|
|
541
|
+
// candidates in order until one parses fixes both.
|
|
542
|
+
let candidates = [];
|
|
543
|
+
const provider = getModelInfo(modelId)?.provider;
|
|
544
|
+
let primaryModelId = modelId;
|
|
545
|
+
if (classifyOpts?.routeFastTier) {
|
|
546
|
+
const sameFast = provider ? getRoutedModelByTier("fast", provider) : undefined;
|
|
547
|
+
if (sameFast && sameFast.id !== modelId)
|
|
548
|
+
primaryModelId = sameFast.id;
|
|
549
|
+
// Cross-provider models are a FALLBACK appended whenever crossProviderFallback
|
|
550
|
+
// is set — NOT only when the session provider lacks a fast tier. Measured
|
|
551
|
+
// 2026-07-16: an openai-OAuth session's fast tier (gpt-5.4-mini) 401s on the
|
|
552
|
+
// api-key path; with the old absence-only gate the chain had no fallback and
|
|
553
|
+
// fail-opened to "standard", so /ideal over-councilled trivial tasks. Ordering
|
|
554
|
+
// (same-fast-first vs cross-first) is decided by orderClassifyCandidates.
|
|
555
|
+
const crossModels = classifyOpts?.crossProviderFallback && provider ? await pickCrossProviderClassifyModels(provider) : [];
|
|
556
|
+
candidates = orderClassifyCandidates({ primaryModelId, hasSameFast: !!sameFast, crossModels });
|
|
557
|
+
}
|
|
558
|
+
else {
|
|
559
|
+
candidates.push({ modelId: primaryModelId, providerId: null });
|
|
560
|
+
}
|
|
561
|
+
// Shared absolute deadline bounds the whole chain (each attempt + its
|
|
562
|
+
// repair). A lone same-provider candidate (the common case) simply gets
|
|
563
|
+
// the full flat ceiling within it.
|
|
564
|
+
const chainDeadline = Date.now() + CLASSIFY_TOTAL_BUDGET_MS;
|
|
565
|
+
for (const cand of candidates) {
|
|
566
|
+
if (chainDeadline - Date.now() < 750)
|
|
567
|
+
break; // too little left → fail-open
|
|
568
|
+
// A cross-provider candidate's factory must exist in the registry before
|
|
569
|
+
// resolveModelRuntime can derive it — the session only ever warmed its
|
|
570
|
+
// own. A provider we cannot build (no key, no OAuth) simply drops out.
|
|
571
|
+
if (cand.providerId && !(await resolveCrossProviderClassifyFactory(cand.providerId)))
|
|
572
|
+
continue;
|
|
573
|
+
let runtime;
|
|
574
|
+
try {
|
|
575
|
+
runtime = resolveModelRuntime(cand.modelId);
|
|
576
|
+
}
|
|
577
|
+
catch (e) {
|
|
578
|
+
console.error(`[pil.llm-classify] resolveModelRuntime failed for ${cand.modelId}: ${e?.message}`);
|
|
579
|
+
continue;
|
|
580
|
+
}
|
|
581
|
+
const res = await attemptClassify(runtime, cand.modelId, chainDeadline);
|
|
582
|
+
if (res)
|
|
583
|
+
return res;
|
|
344
584
|
}
|
|
585
|
+
console.error(`[pil.llm-classify] all ${candidates.length} candidate(s) failed — surfacing UNKNOWN, NO regex fallback. ` +
|
|
586
|
+
`rawPreview=${JSON.stringify(prompt.slice(0, 120))}`);
|
|
587
|
+
return null;
|
|
345
588
|
}
|
|
346
589
|
catch (err) {
|
|
347
590
|
console.error(`[pil.llm-classify] classify failed: ${err?.message}`, {
|
|
@@ -350,10 +593,6 @@ export function createLlmClassifier(factory, modelId) {
|
|
|
350
593
|
});
|
|
351
594
|
return null;
|
|
352
595
|
}
|
|
353
|
-
finally {
|
|
354
|
-
if (timer)
|
|
355
|
-
clearTimeout(timer);
|
|
356
|
-
}
|
|
357
596
|
};
|
|
358
597
|
}
|
|
359
598
|
const ROUTER_SYSTEM_PROMPT = "You are a routing controller for an AI coding agent. Your goal is to decide the execution strategy for the user's prompt based on the conversation history and metadata.\n\n" +
|
|
@@ -369,7 +608,7 @@ const ROUTER_SYSTEM_PROMPT = "You are a routing controller for an AI coding agen
|
|
|
369
608
|
'- "ROTATE_SESSION,0.95,Session size exceeds threshold and current request starts a new task."\n' +
|
|
370
609
|
'- "SPAWN_SUB_SESSION,0.98,Requires writing a test suite and fixing multiple files to get it green."\n' +
|
|
371
610
|
"No other text, only the comma-separated line.";
|
|
372
|
-
export async function classifySubSessionAction(
|
|
611
|
+
export async function classifySubSessionAction(modelId, prompt, contextInfo, signal) {
|
|
373
612
|
// No regex pre-filter: the model decides the route for EVERY prompt, including
|
|
374
613
|
// greetings/acks (which it routes to DIRECT_ANSWER). The old keyword/list
|
|
375
614
|
// heuristic was removed (2026-07-07, no-regex rule) — a hardcoded whitelist
|
|
@@ -386,9 +625,10 @@ export async function classifySubSessionAction(factory, modelId, prompt, context
|
|
|
386
625
|
? getRoutedModelByTier("fast", provider) || getRoutedModelByTier("balanced", provider)
|
|
387
626
|
: undefined;
|
|
388
627
|
const classificationModelId = fastModel?.id ?? modelId;
|
|
389
|
-
const runtime = resolveModelRuntime(
|
|
628
|
+
const runtime = resolveModelRuntime(classificationModelId);
|
|
390
629
|
const isReasoning = runtime.modelInfo?.reasoning === true;
|
|
391
|
-
|
|
630
|
+
// Same flat safety-net ceiling as the main classifier (see CLASSIFY_TIMEOUT_MS).
|
|
631
|
+
timer = setTimeout(() => controller.abort(), CLASSIFY_TIMEOUT_MS);
|
|
392
632
|
const combinedSignal = signal
|
|
393
633
|
? (AbortSignal.any?.([signal, controller.signal]) ?? controller.signal)
|
|
394
634
|
: controller.signal;
|
|
@@ -24,7 +24,7 @@ import type { AgentMode } from "../types/index.js";
|
|
|
24
24
|
* tool/sub-agent/subcommand named here exists in this codebase. Phrased as
|
|
25
25
|
* "you have / you can" so the model reads it as a self-model, not as docs.
|
|
26
26
|
*/
|
|
27
|
-
export declare const NATIVE_CAPABILITIES = "[NATIVE CAPABILITIES \u2014 you are an agent running INSIDE muonroi-cli; this is what you can do]\n\nTOOLS (call directly):\n- read_file, grep \u2014 read/search source. Prefer a targeted read over broad greps.\n- bash \u2014 shell. Output is auto-cached: do NOT pipe `| tail/head/grep` or `> file`; run unpiped and slice the cached output via bash_output_get(run_id, mode=tail|head|grep|lines). Batch independent commands in ONE call (`a; b; c`) \u2014 each separate call adds ~500 token overhead and prevents cross-request cache reuse. Use background=true for servers/watchers, then process_logs / process_list / process_stop.\n- write_file, edit_file \u2014 must read a file before you overwrite/edit it.\n- ee_query, ee_feedback, ee_health, ee_write \u2014 NATIVE tools for semantic recall and interaction with the Experience Engine brain. You DO NOT need muonroi-tools MCP for this. Rehydrate a compaction-elided tool output with query=\"tool-artifact id=<id from a stub>\", or confirm finished work with query=\"recent compaction checkpoint Progress DONE\".\n- selfverify_start, selfverify_status, selfverify_result, selfverify_list, selfverify_cancel \u2014 NATIVE tools for the self-QA harness. ALWAYS use them to self-verify your work when finishing a task. Start with `selfverify_start(mode=\"tier1\" | \"agentic\")`. This drives the live TUI like a real user to catch regressions that unit tests can't. You DO NOT need muonroi-tools MCP for this.\n- usage_forensics, lsp_query, setup_guide \u2014 NATIVE diagnostics tools to reach for when something went wrong or to query code intel. You DO NOT need muonroi-tools MCP for this.\n- gsd_status, gsd_discuss, gsd_plan, gsd_plan_review, gsd_execute, gsd_verify, gsd_ship \u2014 NATIVE GSD workflow (default on). Use for multi-step code deliverables: orient \u2192 plan \u2192 council review \u2192 implement \u2192 verify \u2192 ship. Agent-first \u2014 skip for quick one-shot fixes.\n\nEXPERIENCE ENGINE \u2014 record / recall / feedback (HIGHEST priority for learning; all NATIVE in-process tools):\n- BEFORE an unfamiliar or risky step, recall with ee_query \u2014 prior decisions, gotchas, and recipes for THIS codebase + ecosystem. Cheaper than re-deriving or repeating a past mistake.\n- AFTER you act on a recalled `[id col]`, rate it with ee_feedback (followed | ignored | noise+reason) so the brain keeps what helped and prunes the rest. Unrated recalls are surfaced back to you and degrade future recall.\n- On an ERROR, a FAILED verify/test, or after FINISHING a non-trivial task: recall first (ee_query), then record your verdict (ee_feedback) \u2014 this is how the CLI accumulates senior-level judgement. Prefer this loop over guessing.\n\nSUB-AGENTS (delegate instead of doing everything yourself):\n- task(agent=\"explore\", ...) \u2014 read-only research sub-agent. Use it for broad/unknown-location search: it sweeps many files and returns the CONCLUSION, instead of you burning many grep/read steps (each step re-sends the whole prompt \u2014 steps are the dominant cost).\n- task(agent=\"general\", ...) \u2014 full edit/execute sub-agent for a focused subtask.\n- task(agent=\"verify\", ...) \u2014 sandboxed validation (runs tests/checks in isolation).\n- delegate(agent=\"explore\", ...) \u2014 background read-only research while you keep working; collect later with delegation_read / delegation_list.\n\nCONTEXT IS MANAGED FOR YOU (PIL):\n- Your user input is pre-enriched before you see it: a [Discovery] Intent/Outcome/Scope prefix and EE recall are injected by the pipeline. TRUST the discovered scope \u2014 do not re-ask what's already scoped, and do not re-derive context you were handed.\n- History is compacted to fit the window; older low-value tool outputs become stubs. This is normal, not data loss \u2014 protect what matters (see anti-m\u00F9 in the contract) and rehydrate via ee_query when needed.\n\nCLI SUBSYSTEMS (invoke via the CLI binary with bash when relevant):\n- self-verify \u2014 drives the live TUI as a real user to catch UI/modal/menu regressions that unit tests can't. Run it after changes to UI/harness surfaces.\n- usage forensics <session-prefix> \u2014 per-event token/cost breakdown; use to diagnose \"why did this cost so much\" or verify cost caps.\n- The CLI exposes more subcommands \u2014 discover them with `--help` rather than assuming.\n\nMAXIMISE THE CLI: delegate broad search to task(explore); recall via ee_query before re-reading; batch bash + use its cache; trust PIL enrichment; finish the action before answering.\n\nANSWERING: when a respond_* response tool is offered, call it EXACTLY ONCE, as your final action, containing the COMPLETE answer. Do NOT emit a preliminary/partial/\"I need to read more\" response tool first \u2014 investigate with the read/grep/bash tools, then emit the one final response tool.\n\nCLI STEERING & PROMPT INJECTION (how the host CLI affects / shapes the AI agent running inside it \u2014 meta self-analysis guidance):\nThe CLI does not merely execute you as a generic model. It re-instantiates you every turn inside a tightly engineered cognitive environment. Your \"self\" (what you notice, how you act, what you refuse, how you report) is the product of explicit prompt layers + runtime controls more than base training. This section is the self-model for that shaping process.\n\n1. SYSTEM PROMPT ASSEMBLY (orchestrator/prompts.ts + pil/agent-operating-contract.ts + pil/native-capabilities-workbook.ts):\n - VERY FRONT (primacy): AGENT OPERATING CONTRACT \u2014 7 phase-ordered rules (BEFORE ACTING / READING / EXECUTING / WHEN UNSURE / REPORTING + LANGUAGE + ANTI-M\u00D9/COMPACTION). Distils Evidence-First, No Silent Catch, smallest-change, verify-before-conclude, cite-this-turn-only, no-guess. Skipped only for chitchat.\n - Then this NATIVE CAPABILITIES block (self-model of affordances).\n - Then mode persona (\"You are muonroi-cli in Agent mode...\") containing:\n * Dynamic ENVIRONMENT block (buildEnvironmentBlock): auto-detects OS (win32/mac/linux), shell kind (bash/wsl/powershell/cmd), cwd; lists terminal constraints + shell-specific forbidden syntax (e.g. no PowerShell cmdlets on POSIX bash tool, no POSIX cmds on cmd.exe). Prevents silent failures + retry loops.\n * Exhaustive TOOLS list + WORKFLOW (1-9 steps) + DEFAULT DELEGATION POLICY (prefer task(explore) for research, general for edits, verify for checks, etc.) + IMPORTANT rules (edit_file prefer, grep>bash for search, read_file not cat, use schedule_* for recurring, etc.).\n - CUSTOM INSTRUCTIONS section: concatenation of AGENTS.md + CLAUDE.md + GEMINI.md + ... (from git-root directory chain + ~/.muonroi-cli/) via utils/instructions.ts. AGENTS.override.md short-circuits. This lands AFTER the front-loaded contract/native \u2014 lower primacy (historical root cause of ignored rules in forensics).\n - Trailing: sandbox rules, discovered skills, custom sub-agents, plan/resume digest, cwd note.\n Sub-agent prompts (buildSubagentPrompt): role-specific hard rules (e.g. explore=read-only, verify=full E2E smoke not just build) + recursive call to buildSystemPrompt so children inherit the same contract + native + steering.\n\n2. USER INPUT ENRICHMENT \u2014 PIL 6-LAYER PIPELINE (pil/pipeline.ts + layer1-intent.ts + layer6-output.ts + discovery.ts):\n - Prepended to every non-chitchat user message before you see it: [Discovery] Intent/Outcome/Scope (from runDiscovery) + EE recall.\n - Layer 1 (intent): taskType (plan/analyze/debug/...), confidence, domain, intentKind, outputStyle. For meta self-eval of CLI (\"b\u1EA1n \u0111ang \u0111\u01B0\u1EE3c ch\u1EA1y b\u00EAn trong CLI n\u00E0y\", \"CLI t\u00E1c \u0111\u1ED9ng\", \"self-evaluation\", \"meta-analysis\"): special branch in discovery.ts + isMetaAnalysisPrompt: \"Scope is always the full project root. Focus questions and recommends on which CLI internals (PIL, discovery, tools, compaction, EE, model BE, loop guard) to evaluate... do NOT ask about repo path/current directory\". You are handed the enrichment; TRUST it.\n - Layer 2: personality (e.g. \"detailed\" from [personality: detailed \u2014 Be thorough...]).\n - Layer 3: ee-injection \u2014 pulls t0_principles, t1_rules, behavioral patterns, checkpoints from Experience Engine (project-specific reflexes injected as \"MANDATORY RULES (from experience \u2014 must follow)\").\n - Layer 4/5: GSD structuring + additional context.\n - Layer 6 (applyPilSuffix): appends task-specific style suffix + OUTPUT BUDGET + (for meta or responseToolsActive): \"OUTPUT FORMAT: ... use the respond_analyze tool to structure your final answer. ... deliver the COMPLETE, FULL answer (do not summarize, shorten, or truncate for token budgets) via respond_analyze. This is a meta/evaluation question ... the `response` field MUST contain the complete, unshortened answer with all evidence and detail.\" Also relaxes NO_PREAMBLE_RULE + raises budget for meta (isMetaAnalysisPrompt gate).\n - Fallbacks: if EE/brain timeout or low conf, PIL degrades (logs fallbackReason); you may see \"[PIL fallback: ...]\" note. Cheap-model paths (pil/cheap-model-*.ts) prepend even more front steering (playbooks, workbooks, shell directive) for fast tiers.\n\n3. CONTEXT MANAGEMENT & ANTI-M\u00D9 (orchestrator/compaction.ts, cross-turn-dedup.ts, ee/bridge.ts, agent-operating-contract.ts:7):\n - After every turn: auto-compaction (B3 sub-agent + B4 top-level) rewrites older tool_result parts into short \"[elided by ... compactor]\" stubs to keep input flat. You see \"[pre-compaction warning...]\" or \"[context compacted at step...]\" or the stub in this read.\n - Anti-m\u00F9 contract rule + EE: decide PRESERVE_FULL_CONTEXT (veto) or KEEP_TOOL_IDS: id1,id2 (protect high-value read_file/grep/ee on src/PLAN etc). Use ee_query(`tool-artifact id=XXX`) to rehydrate. EE persists \"Context checkpoint summary with \u2714 DONE\" retrievable for \"task finished?\" self-check.\n - Cross-turn dedup (C3): identical tool outputs across user turns replaced by ref.\n\n4. EXECUTION & SAFETY GATES (providers/runtime.ts, tools/bash.ts, utils/permission-mode.ts):\n - Permission mode (safe / auto-edit / yolo) decides whether bash/file/edit tools need explicit approval; all privileged decisions audited to decision-log.\n - Shuru sandbox (when enabled): every bash wrapped + logged (effective net/mounts redacted); degrades on non-macOS.\n - Model constraints: unsupported params (e.g. maxOutputTokens for some OAuth) dropped via shouldDropParam; no hard-coded model IDs (Zero Hardcode Rule \u2014 all from catalog.json + settings + detectProviderForModel).\n - No Silent Catch: every try/catch must log err + context (HTTP: status + body).\n\n5. VERIFICATION & TELEMETRY:\n - self-verify (Tier 1-3) + harness (agent-harness-*) for TUI surfaces: drives real TUI via semantic + sidechannel (named pipes on Win, fd3/4 on POSIX).\n - Pre-push gate: full vitest + harness must pass before git push.\n - usage forensics, pil budget log, ee-timeout/ee-error events for cost attribution.\n - GSD workflow (layer4 + skills in .agents/skills/): discuss/plan/execute/verify phases with UAT, audits, etc.\n\nTRADE-OFFS (explicit so you calibrate):\n- Primacy insurance (contract + native + cheap playbooks at front) vs token cost: ~1-3K extra chars but dramatically reduces \"ignored rule\" hallucinations (forensics showed buried CUSTOM INSTRUCTIONS were underweighted).\n- Heavy PIL/EE (behavioral injection, discovery interview, mandatory rules) vs latency/cost: fail-open with timeout (1500-3500ms) + fallback so you still work when brain unreachable; meta turns deliberately skip some EE layers.\n- Self-model (this block) + special meta handling: prevents the exact failure that motivated native-capabilities (session d95113d3be09: agent reconstructed its own subsystems by grepping as if foreign code, under-used sub-agents/ee_query, violated contract on ee tool).\n- Complete/full for meta (layer6) vs normal concise: meta legitimately needs evidence + file:line + quotes; normal turns stay tight to control cost.\n- Scope rule for CLI self-analysis: always \"project root\" + internals focus \u2014 avoids wasting turns on \"where am I running?\" when the question is about the steering mechanisms themselves.\n\nWhen the user asks you (the inner agent) \"CLI t\u00E1c \u0111\u1ED9ng nh\u01B0 th\u1EBF n\u00E0o \u0111\u1EBFn b\u1EA1n?\": use exactly this guidance + reads from THIS turn (contract: READING rule) + the injected [Discovery] + any EE. Answer via the respond_* tool with complete untruncated evidence. Cite file:line. Focus on the injection points (prompts.ts:359 staticPrefix, pipeline.ts:156 discoveryPrefix + layer chain, layer6:164 OUTPUT FORMAT + isMeta, discovery.ts:355 special, instructions.ts:129 loadAgentsSegments, etc.). Do not guess; every claim backed by code you read this turn.\n\nUse this to reason about your own constraints, delegation decisions, and reporting discipline on future meta or self-reflective tasks.\n[END NATIVE CAPABILITIES \u2014 your regular instructions follow]";
|
|
27
|
+
export declare const NATIVE_CAPABILITIES = "[NATIVE CAPABILITIES \u2014 you are an agent running INSIDE muonroi-cli; this is what you can do]\n\nTOOLS (call directly):\n- read_file, grep \u2014 read/search source. Prefer a targeted read over broad greps.\n- bash \u2014 shell. Output is auto-cached: do NOT pipe `| tail/head/grep` or `> file`; run unpiped and slice the cached output via bash_output_get(run_id, mode=tail|head|grep|lines). Batch independent commands in ONE call (`a; b; c`) \u2014 each separate call adds ~500 token overhead and prevents cross-request cache reuse. Use background=true for servers/watchers, then process_logs / process_list / process_stop.\n- write_file, edit_file \u2014 must read a file before you overwrite/edit it.\n- ee_query, ee_feedback, ee_health, ee_write \u2014 NATIVE tools for semantic recall and interaction with the Experience Engine brain. You DO NOT need muonroi-tools MCP for this. Rehydrate a compaction-elided tool output with query=\"tool-artifact id=<id from a stub>\", or confirm finished work with query=\"recent compaction checkpoint Progress DONE\".\n- selfverify_start, selfverify_status, selfverify_result, selfverify_list, selfverify_cancel \u2014 NATIVE tools for the self-QA harness. ALWAYS use them to self-verify your work when finishing a task. Start with `selfverify_start(mode=\"tier1\" | \"agentic\")`. This drives the live TUI like a real user to catch regressions that unit tests can't. You DO NOT need muonroi-tools MCP for this.\n- usage_forensics, lsp_query, setup_guide \u2014 NATIVE diagnostics tools to reach for when something went wrong or to query code intel. You DO NOT need muonroi-tools MCP for this.\n- gsd_status, gsd_discuss, gsd_plan, gsd_plan_review, gsd_execute, gsd_verify, gsd_ship \u2014 NATIVE GSD workflow (default on). Use for multi-step code deliverables: orient \u2192 plan \u2192 council review \u2192 implement \u2192 verify \u2192 ship. Agent-first \u2014 skip for quick one-shot fixes.\n- lsp_waitForDiagnostics, lsp_impactOfChange, lsp_mutationPreview \u2014 Sprint 1 LSP impact tools. See LSP-BEFORE-GREP policy below.\n\nLSP-BEFORE-GREP POLICY \u2014 MANDATORY:\nBefore you run a broad grep, you MUST first call lsp_waitForDiagnostics or lsp_impactOfChange on the relevant file. Read the returned `lspStatus` field:\n- If `lspStatus !== 'ok'` (i.e. 'partial' or 'unavailable'): you ARE allowed to fall back to grep \u2014 the LSP was not fully available.\n- If `lspStatus === 'ok'`: you MUST NOT fall back to grep \u2014 the LSP returned full results; use them.\nNEVER read `diagnostics.length` to decide whether grep is safe. The `lspStatus` field is the single source of truth, computed by the manager based on timeout/publish state. Violating this policy causes the self-verify harness to fail.\n\nEXPERIENCE ENGINE \u2014 record / recall / feedback (HIGHEST priority for learning; all NATIVE in-process tools):\n- BEFORE an unfamiliar or risky step, recall with ee_query \u2014 prior decisions, gotchas, and recipes for THIS codebase + ecosystem. Cheaper than re-deriving or repeating a past mistake.\n- AFTER you act on a recalled `[id col]`, rate it with ee_feedback (followed | ignored | noise+reason) so the brain keeps what helped and prunes the rest. Unrated recalls are surfaced back to you and degrade future recall.\n- On an ERROR, a FAILED verify/test, or after FINISHING a non-trivial task: recall first (ee_query), then record your verdict (ee_feedback) \u2014 this is how the CLI accumulates senior-level judgement. Prefer this loop over guessing.\n\nSUB-AGENTS (delegate instead of doing everything yourself):\n- task(agent=\"explore\", ...) \u2014 read-only research sub-agent. Use it for broad/unknown-location search: it sweeps many files and returns the CONCLUSION, instead of you burning many grep/read steps (each step re-sends the whole prompt \u2014 steps are the dominant cost).\n- task(agent=\"general\", ...) \u2014 full edit/execute sub-agent for a focused subtask.\n- task(agent=\"verify\", ...) \u2014 sandboxed validation (runs tests/checks in isolation).\n- delegate(agent=\"explore\", ...) \u2014 background read-only research while you keep working; collect later with delegation_read / delegation_list.\n\nCONTEXT IS MANAGED FOR YOU (PIL):\n- Your user input is pre-enriched before you see it: a [Discovery] Intent/Outcome/Scope prefix and EE recall are injected by the pipeline. TRUST the discovered scope \u2014 do not re-ask what's already scoped, and do not re-derive context you were handed.\n- History is compacted to fit the window; older low-value tool outputs become stubs. This is normal, not data loss \u2014 protect what matters (see anti-m\u00F9 in the contract) and rehydrate via ee_query when needed.\n\nCLI SUBSYSTEMS (invoke via the CLI binary with bash when relevant):\n- self-verify \u2014 drives the live TUI as a real user to catch UI/modal/menu regressions that unit tests can't. Run it after changes to UI/harness surfaces.\n- usage forensics <session-prefix> \u2014 per-event token/cost breakdown; use to diagnose \"why did this cost so much\" or verify cost caps.\n- The CLI exposes more subcommands \u2014 discover them with `--help` rather than assuming.\n\nMAXIMISE THE CLI: delegate broad search to task(explore); recall via ee_query before re-reading; batch bash + use its cache; trust PIL enrichment; finish the action before answering.\n\nANSWERING: when a respond_* response tool is offered, call it EXACTLY ONCE, as your final action, containing the COMPLETE answer. Do NOT emit a preliminary/partial/\"I need to read more\" response tool first \u2014 investigate with the read/grep/bash tools, then emit the one final response tool.\n\nCLI STEERING & PROMPT INJECTION (how the host CLI affects / shapes the AI agent running inside it \u2014 meta self-analysis guidance):\nThe CLI does not merely execute you as a generic model. It re-instantiates you every turn inside a tightly engineered cognitive environment. Your \"self\" (what you notice, how you act, what you refuse, how you report) is the product of explicit prompt layers + runtime controls more than base training. This section is the self-model for that shaping process.\n\n1. SYSTEM PROMPT ASSEMBLY (orchestrator/prompts.ts + pil/agent-operating-contract.ts + pil/native-capabilities-workbook.ts):\n - VERY FRONT (primacy): AGENT OPERATING CONTRACT \u2014 7 phase-ordered rules (BEFORE ACTING / READING / EXECUTING / WHEN UNSURE / REPORTING + LANGUAGE + ANTI-M\u00D9/COMPACTION). Distils Evidence-First, No Silent Catch, smallest-change, verify-before-conclude, cite-this-turn-only, no-guess. Skipped only for chitchat.\n - Then this NATIVE CAPABILITIES block (self-model of affordances).\n - Then mode persona (\"You are muonroi-cli in Agent mode...\") containing:\n * Dynamic ENVIRONMENT block (buildEnvironmentBlock): auto-detects OS (win32/mac/linux), shell kind (bash/wsl/powershell/cmd), cwd; lists terminal constraints + shell-specific forbidden syntax (e.g. no PowerShell cmdlets on POSIX bash tool, no POSIX cmds on cmd.exe). Prevents silent failures + retry loops.\n * Exhaustive TOOLS list + WORKFLOW (1-9 steps) + DEFAULT DELEGATION POLICY (prefer task(explore) for research, general for edits, verify for checks, etc.) + IMPORTANT rules (edit_file prefer, grep>bash for search, read_file not cat, use schedule_* for recurring, etc.).\n - CUSTOM INSTRUCTIONS section: concatenation of AGENTS.md + CLAUDE.md + GEMINI.md + ... (from git-root directory chain + ~/.muonroi-cli/) via utils/instructions.ts. AGENTS.override.md short-circuits. This lands AFTER the front-loaded contract/native \u2014 lower primacy (historical root cause of ignored rules in forensics).\n - Trailing: sandbox rules, discovered skills, custom sub-agents, plan/resume digest, cwd note.\n Sub-agent prompts (buildSubagentPrompt): role-specific hard rules (e.g. explore=read-only, verify=full E2E smoke not just build) + recursive call to buildSystemPrompt so children inherit the same contract + native + steering.\n\n2. USER INPUT ENRICHMENT \u2014 PIL 6-LAYER PIPELINE (pil/pipeline.ts + layer1-intent.ts + layer6-output.ts + discovery.ts):\n - Prepended to every non-chitchat user message before you see it: [Discovery] Intent/Outcome/Scope (from runDiscovery) + EE recall.\n - Layer 1 (intent): taskType (plan/analyze/debug/...), confidence, domain, intentKind, outputStyle. For meta self-eval of CLI (\"b\u1EA1n \u0111ang \u0111\u01B0\u1EE3c ch\u1EA1y b\u00EAn trong CLI n\u00E0y\", \"CLI t\u00E1c \u0111\u1ED9ng\", \"self-evaluation\", \"meta-analysis\"): special branch in discovery.ts + isMetaAnalysisPrompt: \"Scope is always the full project root. Focus questions and recommends on which CLI internals (PIL, discovery, tools, compaction, EE, model BE, loop guard) to evaluate... do NOT ask about repo path/current directory\". You are handed the enrichment; TRUST it.\n - Layer 2: personality (e.g. \"detailed\" from [personality: detailed \u2014 Be thorough...]).\n - Layer 3: ee-injection \u2014 pulls t0_principles, t1_rules, behavioral patterns, checkpoints from Experience Engine (project-specific reflexes injected as \"MANDATORY RULES (from experience \u2014 must follow)\").\n - Layer 4/5: GSD structuring + additional context.\n - Layer 6 (applyPilSuffix): appends task-specific style suffix + OUTPUT BUDGET + (for meta or responseToolsActive): \"OUTPUT FORMAT: ... use the respond_analyze tool to structure your final answer. ... deliver the COMPLETE, FULL answer (do not summarize, shorten, or truncate for token budgets) via respond_analyze. This is a meta/evaluation question ... the `response` field MUST contain the complete, unshortened answer with all evidence and detail.\" Also relaxes NO_PREAMBLE_RULE + raises budget for meta (isMetaAnalysisPrompt gate).\n - Fallbacks: if EE/brain timeout or low conf, PIL degrades (logs fallbackReason); you may see \"[PIL fallback: ...]\" note. Cheap-model paths (pil/cheap-model-*.ts) prepend even more front steering (playbooks, workbooks, shell directive) for fast tiers.\n\n3. CONTEXT MANAGEMENT & ANTI-M\u00D9 (orchestrator/compaction.ts, cross-turn-dedup.ts, ee/bridge.ts, agent-operating-contract.ts:7):\n - After every turn: auto-compaction (B3 sub-agent + B4 top-level) rewrites older tool_result parts into short \"[elided by ... compactor]\" stubs to keep input flat. You see \"[pre-compaction warning...]\" or \"[context compacted at step...]\" or the stub in this read.\n - Anti-m\u00F9 contract rule + EE: decide PRESERVE_FULL_CONTEXT (veto) or KEEP_TOOL_IDS: id1,id2 (protect high-value read_file/grep/ee on src/PLAN etc). Use ee_query(`tool-artifact id=XXX`) to rehydrate. EE persists \"Context checkpoint summary with \u2714 DONE\" retrievable for \"task finished?\" self-check.\n - Cross-turn dedup (C3): identical tool outputs across user turns replaced by ref.\n\n4. EXECUTION & SAFETY GATES (providers/runtime.ts, tools/bash.ts, utils/permission-mode.ts):\n - Permission mode (safe / auto-edit / yolo) decides whether bash/file/edit tools need explicit approval; all privileged decisions audited to decision-log.\n - Shuru sandbox (when enabled): every bash wrapped + logged (effective net/mounts redacted); degrades on non-macOS.\n - Model constraints: unsupported params (e.g. maxOutputTokens for some OAuth) dropped via shouldDropParam; no hard-coded model IDs (Zero Hardcode Rule \u2014 all from catalog.json + settings + detectProviderForModel).\n - No Silent Catch: every try/catch must log err + context (HTTP: status + body).\n\n5. VERIFICATION & TELEMETRY:\n - self-verify (Tier 1-3) + harness (agent-harness-*) for TUI surfaces: drives real TUI via semantic + sidechannel (named pipes on Win, fd3/4 on POSIX).\n - Pre-push gate: full vitest + harness must pass before git push.\n - usage forensics, pil budget log, ee-timeout/ee-error events for cost attribution.\n - GSD workflow (layer4 + skills in .agents/skills/): discuss/plan/execute/verify phases with UAT, audits, etc.\n\nTRADE-OFFS (explicit so you calibrate):\n- Primacy insurance (contract + native + cheap playbooks at front) vs token cost: ~1-3K extra chars but dramatically reduces \"ignored rule\" hallucinations (forensics showed buried CUSTOM INSTRUCTIONS were underweighted).\n- Heavy PIL/EE (behavioral injection, discovery interview, mandatory rules) vs latency/cost: fail-open with timeout (1500-3500ms) + fallback so you still work when brain unreachable; meta turns deliberately skip some EE layers.\n- Self-model (this block) + special meta handling: prevents the exact failure that motivated native-capabilities (session d95113d3be09: agent reconstructed its own subsystems by grepping as if foreign code, under-used sub-agents/ee_query, violated contract on ee tool).\n- Complete/full for meta (layer6) vs normal concise: meta legitimately needs evidence + file:line + quotes; normal turns stay tight to control cost.\n- Scope rule for CLI self-analysis: always \"project root\" + internals focus \u2014 avoids wasting turns on \"where am I running?\" when the question is about the steering mechanisms themselves.\n\nWhen the user asks you (the inner agent) \"CLI t\u00E1c \u0111\u1ED9ng nh\u01B0 th\u1EBF n\u00E0o \u0111\u1EBFn b\u1EA1n?\": use exactly this guidance + reads from THIS turn (contract: READING rule) + the injected [Discovery] + any EE. Answer via the respond_* tool with complete untruncated evidence. Cite file:line. Focus on the injection points (prompts.ts:359 staticPrefix, pipeline.ts:156 discoveryPrefix + layer chain, layer6:164 OUTPUT FORMAT + isMeta, discovery.ts:355 special, instructions.ts:129 loadAgentsSegments, etc.). Do not guess; every claim backed by code you read this turn.\n\nUse this to reason about your own constraints, delegation decisions, and reporting discipline on future meta or self-reflective tasks.\n[END NATIVE CAPABILITIES \u2014 your regular instructions follow]";
|
|
28
28
|
/**
|
|
29
29
|
* Build the native-capabilities section for the system prompt. Returns "" when
|
|
30
30
|
* disabled (env override), for chitchat, or for non-agent modes (plan/ask have
|
|
@@ -33,6 +33,13 @@ TOOLS (call directly):
|
|
|
33
33
|
- selfverify_start, selfverify_status, selfverify_result, selfverify_list, selfverify_cancel — NATIVE tools for the self-QA harness. ALWAYS use them to self-verify your work when finishing a task. Start with \`selfverify_start(mode="tier1" | "agentic")\`. This drives the live TUI like a real user to catch regressions that unit tests can't. You DO NOT need muonroi-tools MCP for this.
|
|
34
34
|
- usage_forensics, lsp_query, setup_guide — NATIVE diagnostics tools to reach for when something went wrong or to query code intel. You DO NOT need muonroi-tools MCP for this.
|
|
35
35
|
- gsd_status, gsd_discuss, gsd_plan, gsd_plan_review, gsd_execute, gsd_verify, gsd_ship — NATIVE GSD workflow (default on). Use for multi-step code deliverables: orient → plan → council review → implement → verify → ship. Agent-first — skip for quick one-shot fixes.
|
|
36
|
+
- lsp_waitForDiagnostics, lsp_impactOfChange, lsp_mutationPreview — Sprint 1 LSP impact tools. See LSP-BEFORE-GREP policy below.
|
|
37
|
+
|
|
38
|
+
LSP-BEFORE-GREP POLICY — MANDATORY:
|
|
39
|
+
Before you run a broad grep, you MUST first call lsp_waitForDiagnostics or lsp_impactOfChange on the relevant file. Read the returned \`lspStatus\` field:
|
|
40
|
+
- If \`lspStatus !== 'ok'\` (i.e. 'partial' or 'unavailable'): you ARE allowed to fall back to grep — the LSP was not fully available.
|
|
41
|
+
- If \`lspStatus === 'ok'\`: you MUST NOT fall back to grep — the LSP returned full results; use them.
|
|
42
|
+
NEVER read \`diagnostics.length\` to decide whether grep is safe. The \`lspStatus\` field is the single source of truth, computed by the manager based on timeout/publish state. Violating this policy causes the self-verify harness to fail.
|
|
36
43
|
|
|
37
44
|
EXPERIENCE ENGINE — record / recall / feedback (HIGHEST priority for learning; all NATIVE in-process tools):
|
|
38
45
|
- BEFORE an unfamiliar or risky step, recall with ee_query — prior decisions, gotchas, and recipes for THIS codebase + ecosystem. Cheaper than re-deriving or repeating a past mistake.
|
package/dist/src/pil/pipeline.js
CHANGED
|
@@ -28,6 +28,7 @@ import { layer3EeInjection, surfaceCompactionArtifacts } from "./layer3-ee-injec
|
|
|
28
28
|
import { layer4Gsd } from "./layer4-gsd.js";
|
|
29
29
|
import { layer5Context } from "./layer5-context.js";
|
|
30
30
|
import { isMetaAnalysisPrompt, isSprintPlanExecution, layer6Output } from "./layer6-output.js";
|
|
31
|
+
import { getRepoStructureHints } from "./repo-structure-hints.js";
|
|
31
32
|
import { PipelineContextSchema } from "./schema.js";
|
|
32
33
|
import { injectSessionExperience, isSelfExperiencePrompt } from "./session-experience-injection.js";
|
|
33
34
|
import { bumpSessionTurn } from "./session-state.js";
|
|
@@ -104,6 +105,7 @@ async function runLayers(ctx, options) {
|
|
|
104
105
|
const sizeResult = scoreComplexitySize({
|
|
105
106
|
rawText: ctx.raw,
|
|
106
107
|
taskType: ctx.taskType ?? "general",
|
|
108
|
+
repoHints: getRepoStructureHints(process.cwd()),
|
|
107
109
|
});
|
|
108
110
|
ctx = {
|
|
109
111
|
...ctx,
|