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
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* src/utils/loop-profiler.ts
|
|
3
|
+
*
|
|
4
|
+
* Rolling V8 CPU profiler — the instrument that names the culprit when the
|
|
5
|
+
* event loop blocks (see `event-loop-monitor.ts` for the incident this exists
|
|
6
|
+
* for).
|
|
7
|
+
*
|
|
8
|
+
* Why a CPU profile and not a stack dump: while the JS thread is blocked, NO
|
|
9
|
+
* JS can run — you cannot ask the process what it is doing, because asking is
|
|
10
|
+
* itself JS. V8's sampling profiler runs on its OWN thread and samples the JS
|
|
11
|
+
* thread regardless of whether that thread is stuck, so the profile taken
|
|
12
|
+
* across a block contains the blocking frames. Verified under Bun 1.3.13: a
|
|
13
|
+
* deliberate 400ms synchronous block yielded 54 samples.
|
|
14
|
+
*
|
|
15
|
+
* Rolling segments keep the retained profile small without losing the block:
|
|
16
|
+
* rotation is loop-driven, so it CANNOT run during a block — whichever segment
|
|
17
|
+
* is in flight when the loop freezes stays in flight for the whole freeze, and
|
|
18
|
+
* is still open when the monitor detects the block right after. So `capture()`
|
|
19
|
+
* always writes a profile containing the entire block, however long it ran.
|
|
20
|
+
*
|
|
21
|
+
* Off by default (`MUONROI_LOOP_PROFILE=1` to arm) — continuous profiling has a
|
|
22
|
+
* real, if small, cost and this is a diagnostic, not a feature.
|
|
23
|
+
*/
|
|
24
|
+
/** Minimal shape of the V8 CPU profile we consume (`Profiler.stop` result). */
|
|
25
|
+
interface CpuProfileNode {
|
|
26
|
+
id: number;
|
|
27
|
+
callFrame: {
|
|
28
|
+
functionName?: string;
|
|
29
|
+
url?: string;
|
|
30
|
+
lineNumber?: number;
|
|
31
|
+
};
|
|
32
|
+
children?: number[];
|
|
33
|
+
}
|
|
34
|
+
interface CpuProfile {
|
|
35
|
+
nodes: CpuProfileNode[];
|
|
36
|
+
samples?: number[];
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Reduce a CPU profile to the single hottest stack, leaf-first.
|
|
40
|
+
*
|
|
41
|
+
* Why not just the top frame: samples land on the LEAF, which for a blocking
|
|
42
|
+
* loop is whatever primitive it calls (`Date.now`, a regex step, a syscall) —
|
|
43
|
+
* true but useless. Verified on a synthetic 3s block: the leaf was `now` with
|
|
44
|
+
* 188 samples while the actual culprit `theGuiltyFunction` had 4. The CALLERS
|
|
45
|
+
* are the answer, so we walk up from the hottest leaf and return the chain.
|
|
46
|
+
*
|
|
47
|
+
* This is what makes the profile readable from debug.log alone, without
|
|
48
|
+
* requiring anyone to open Chrome DevTools.
|
|
49
|
+
*
|
|
50
|
+
* Pure — unit-testable with a hand-built profile.
|
|
51
|
+
*
|
|
52
|
+
* @param maxFrames how many frames of the chain to return, leaf-first
|
|
53
|
+
* @returns frames like `theGuiltyFunction (file.ts:12)`, or [] if unusable
|
|
54
|
+
*/
|
|
55
|
+
export declare function summarizeHotStack(profile: CpuProfile, maxFrames?: number): string[];
|
|
56
|
+
/** What a successful {@link LoopProfiler.capture} produced. */
|
|
57
|
+
export interface CaptureResult {
|
|
58
|
+
/** Path of the written `.cpuprofile` (open in Chrome DevTools for detail). */
|
|
59
|
+
file: string;
|
|
60
|
+
/** Hottest stack, leaf-first — readable without DevTools. See {@link summarizeHotStack}. */
|
|
61
|
+
hotStack: string[];
|
|
62
|
+
}
|
|
63
|
+
/** A rolling CPU profiler. All methods are best-effort and never throw. */
|
|
64
|
+
export interface LoopProfiler {
|
|
65
|
+
/**
|
|
66
|
+
* Discard the current segment and start a fresh one. Call periodically from
|
|
67
|
+
* the event loop to bound profile size — it is a no-op during a block, which
|
|
68
|
+
* is exactly what preserves the evidence.
|
|
69
|
+
*/
|
|
70
|
+
rotate(): void;
|
|
71
|
+
/**
|
|
72
|
+
* Stop the current segment, write it to disk, and start a fresh one.
|
|
73
|
+
*
|
|
74
|
+
* Async because the inspector delivers `Profiler.stop` on a later tick
|
|
75
|
+
* (measured under Bun 1.3.13 — the callback does NOT run synchronously). The
|
|
76
|
+
* loop is already free by the time this is called, so awaiting is safe.
|
|
77
|
+
*
|
|
78
|
+
* @returns the profile path plus the hottest stack, or null on failure.
|
|
79
|
+
*/
|
|
80
|
+
capture(reason: string): Promise<CaptureResult | null>;
|
|
81
|
+
/** Stop profiling and disconnect. Idempotent. */
|
|
82
|
+
dispose(): void;
|
|
83
|
+
}
|
|
84
|
+
/** Options for {@link createLoopProfiler}. */
|
|
85
|
+
export interface LoopProfilerOpts {
|
|
86
|
+
/**
|
|
87
|
+
* V8 sampling interval in microseconds. 10_000 (10ms) is ~100 samples/sec —
|
|
88
|
+
* plenty to identify a multi-second blocking frame, at a fraction of the cost
|
|
89
|
+
* of the 1ms default.
|
|
90
|
+
*/
|
|
91
|
+
samplingIntervalUs?: number;
|
|
92
|
+
/** Directory for `.cpuprofile` files. Defaults to `~/.muonroi-cli/profiles`. */
|
|
93
|
+
dir?: string;
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Create and arm a rolling CPU profiler.
|
|
97
|
+
*
|
|
98
|
+
* Returns null when the inspector is unavailable (some runtimes/builds) — the
|
|
99
|
+
* caller keeps the block detector, just without stacks. Never throws.
|
|
100
|
+
*/
|
|
101
|
+
export declare function createLoopProfiler(opts?: LoopProfilerOpts): Promise<LoopProfiler | null>;
|
|
102
|
+
export {};
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* src/utils/loop-profiler.ts
|
|
3
|
+
*
|
|
4
|
+
* Rolling V8 CPU profiler — the instrument that names the culprit when the
|
|
5
|
+
* event loop blocks (see `event-loop-monitor.ts` for the incident this exists
|
|
6
|
+
* for).
|
|
7
|
+
*
|
|
8
|
+
* Why a CPU profile and not a stack dump: while the JS thread is blocked, NO
|
|
9
|
+
* JS can run — you cannot ask the process what it is doing, because asking is
|
|
10
|
+
* itself JS. V8's sampling profiler runs on its OWN thread and samples the JS
|
|
11
|
+
* thread regardless of whether that thread is stuck, so the profile taken
|
|
12
|
+
* across a block contains the blocking frames. Verified under Bun 1.3.13: a
|
|
13
|
+
* deliberate 400ms synchronous block yielded 54 samples.
|
|
14
|
+
*
|
|
15
|
+
* Rolling segments keep the retained profile small without losing the block:
|
|
16
|
+
* rotation is loop-driven, so it CANNOT run during a block — whichever segment
|
|
17
|
+
* is in flight when the loop freezes stays in flight for the whole freeze, and
|
|
18
|
+
* is still open when the monitor detects the block right after. So `capture()`
|
|
19
|
+
* always writes a profile containing the entire block, however long it ran.
|
|
20
|
+
*
|
|
21
|
+
* Off by default (`MUONROI_LOOP_PROFILE=1` to arm) — continuous profiling has a
|
|
22
|
+
* real, if small, cost and this is a diagnostic, not a feature.
|
|
23
|
+
*/
|
|
24
|
+
import fs from "node:fs";
|
|
25
|
+
import os from "node:os";
|
|
26
|
+
import path from "node:path";
|
|
27
|
+
import { logger } from "./logger.js";
|
|
28
|
+
/** Frames V8 synthesises; naming one as the culprit tells nobody anything. */
|
|
29
|
+
const SYNTHETIC_FRAMES = new Set(["(root)", "(program)", "(idle)", "(garbage collector)", "(anonymous)", ""]);
|
|
30
|
+
/**
|
|
31
|
+
* Reduce a CPU profile to the single hottest stack, leaf-first.
|
|
32
|
+
*
|
|
33
|
+
* Why not just the top frame: samples land on the LEAF, which for a blocking
|
|
34
|
+
* loop is whatever primitive it calls (`Date.now`, a regex step, a syscall) —
|
|
35
|
+
* true but useless. Verified on a synthetic 3s block: the leaf was `now` with
|
|
36
|
+
* 188 samples while the actual culprit `theGuiltyFunction` had 4. The CALLERS
|
|
37
|
+
* are the answer, so we walk up from the hottest leaf and return the chain.
|
|
38
|
+
*
|
|
39
|
+
* This is what makes the profile readable from debug.log alone, without
|
|
40
|
+
* requiring anyone to open Chrome DevTools.
|
|
41
|
+
*
|
|
42
|
+
* Pure — unit-testable with a hand-built profile.
|
|
43
|
+
*
|
|
44
|
+
* @param maxFrames how many frames of the chain to return, leaf-first
|
|
45
|
+
* @returns frames like `theGuiltyFunction (file.ts:12)`, or [] if unusable
|
|
46
|
+
*/
|
|
47
|
+
export function summarizeHotStack(profile, maxFrames = 8) {
|
|
48
|
+
const samples = profile?.samples;
|
|
49
|
+
const nodes = profile?.nodes;
|
|
50
|
+
if (!Array.isArray(samples) || !Array.isArray(nodes) || samples.length === 0)
|
|
51
|
+
return [];
|
|
52
|
+
const byId = new Map();
|
|
53
|
+
const parentOf = new Map();
|
|
54
|
+
for (const n of nodes) {
|
|
55
|
+
byId.set(n.id, n);
|
|
56
|
+
for (const c of n.children ?? [])
|
|
57
|
+
parentOf.set(c, n.id);
|
|
58
|
+
}
|
|
59
|
+
const selfCounts = new Map();
|
|
60
|
+
for (const s of samples)
|
|
61
|
+
selfCounts.set(s, (selfCounts.get(s) ?? 0) + 1);
|
|
62
|
+
let hottest = -1;
|
|
63
|
+
let best = -1;
|
|
64
|
+
for (const [id, count] of selfCounts) {
|
|
65
|
+
if (count > best) {
|
|
66
|
+
best = count;
|
|
67
|
+
hottest = id;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
if (hottest < 0)
|
|
71
|
+
return [];
|
|
72
|
+
const frames = [];
|
|
73
|
+
const seen = new Set();
|
|
74
|
+
let cur = hottest;
|
|
75
|
+
while (cur !== undefined && !seen.has(cur) && frames.length < maxFrames) {
|
|
76
|
+
seen.add(cur);
|
|
77
|
+
const node = byId.get(cur);
|
|
78
|
+
if (!node)
|
|
79
|
+
break;
|
|
80
|
+
const name = node.callFrame?.functionName ?? "";
|
|
81
|
+
if (!SYNTHETIC_FRAMES.has(name)) {
|
|
82
|
+
const url = node.callFrame?.url ?? "";
|
|
83
|
+
const short = url ? `${url.split(/[\\/]/).pop()}:${(node.callFrame?.lineNumber ?? 0) + 1}` : "native";
|
|
84
|
+
frames.push(`${name} (${short})`);
|
|
85
|
+
}
|
|
86
|
+
cur = parentOf.get(cur);
|
|
87
|
+
}
|
|
88
|
+
return frames;
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Create and arm a rolling CPU profiler.
|
|
92
|
+
*
|
|
93
|
+
* Returns null when the inspector is unavailable (some runtimes/builds) — the
|
|
94
|
+
* caller keeps the block detector, just without stacks. Never throws.
|
|
95
|
+
*/
|
|
96
|
+
export async function createLoopProfiler(opts = {}) {
|
|
97
|
+
const samplingIntervalUs = opts.samplingIntervalUs ?? 10_000;
|
|
98
|
+
const dir = opts.dir ?? path.join(os.homedir(), ".muonroi-cli", "profiles");
|
|
99
|
+
let session;
|
|
100
|
+
try {
|
|
101
|
+
const inspector = await import("node:inspector");
|
|
102
|
+
session = new inspector.Session();
|
|
103
|
+
session.connect();
|
|
104
|
+
}
|
|
105
|
+
catch (err) {
|
|
106
|
+
logger.warn("cli", `[loop-profiler] inspector unavailable — block reports will have no stacks: ${err?.message}`, { error: err });
|
|
107
|
+
return null;
|
|
108
|
+
}
|
|
109
|
+
/** Promise-free post: the profiler is driven from timer callbacks. */
|
|
110
|
+
const post = (method, params) => {
|
|
111
|
+
try {
|
|
112
|
+
session.post(method, params, (err) => {
|
|
113
|
+
if (err) {
|
|
114
|
+
logger.warn("cli", `[loop-profiler] ${method} failed: ${err.message}`, { error: err });
|
|
115
|
+
}
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
catch (err) {
|
|
119
|
+
logger.warn("cli", `[loop-profiler] ${method} threw: ${err?.message}`, { error: err });
|
|
120
|
+
}
|
|
121
|
+
};
|
|
122
|
+
let disposed = false;
|
|
123
|
+
const start = () => {
|
|
124
|
+
post("Profiler.setSamplingInterval", { interval: samplingIntervalUs });
|
|
125
|
+
post("Profiler.start");
|
|
126
|
+
};
|
|
127
|
+
try {
|
|
128
|
+
post("Profiler.enable");
|
|
129
|
+
start();
|
|
130
|
+
}
|
|
131
|
+
catch (err) {
|
|
132
|
+
logger.warn("cli", `[loop-profiler] failed to arm: ${err?.message}`, { error: err });
|
|
133
|
+
return null;
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* Stop the current segment and resolve with its profile (null on failure).
|
|
137
|
+
* Restarts a fresh segment unless disposing.
|
|
138
|
+
*/
|
|
139
|
+
const stopSegment = (restart) => new Promise((resolve) => {
|
|
140
|
+
try {
|
|
141
|
+
session.post("Profiler.stop", (err, res) => {
|
|
142
|
+
if (err) {
|
|
143
|
+
logger.warn("cli", `[loop-profiler] Profiler.stop failed: ${err.message}`, { error: err });
|
|
144
|
+
resolve(null);
|
|
145
|
+
}
|
|
146
|
+
else {
|
|
147
|
+
resolve(res?.profile ?? null);
|
|
148
|
+
}
|
|
149
|
+
if (restart && !disposed)
|
|
150
|
+
start();
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
catch (err) {
|
|
154
|
+
logger.warn("cli", `[loop-profiler] Profiler.stop threw: ${err?.message}`, { error: err });
|
|
155
|
+
resolve(null);
|
|
156
|
+
}
|
|
157
|
+
});
|
|
158
|
+
return {
|
|
159
|
+
rotate() {
|
|
160
|
+
if (disposed)
|
|
161
|
+
return;
|
|
162
|
+
// Drop the segment on the floor — it contained no block worth keeping.
|
|
163
|
+
void stopSegment(true);
|
|
164
|
+
},
|
|
165
|
+
async capture(reason) {
|
|
166
|
+
if (disposed)
|
|
167
|
+
return null;
|
|
168
|
+
const profile = await stopSegment(true);
|
|
169
|
+
if (!profile)
|
|
170
|
+
return null;
|
|
171
|
+
try {
|
|
172
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
173
|
+
const stamp = new Date().toISOString().replace(/[:.]/g, "-");
|
|
174
|
+
const safeReason = reason.replace(/[^a-z0-9_-]/gi, "_").slice(0, 40);
|
|
175
|
+
const file = path.join(dir, `loop-block-${stamp}-${safeReason}.cpuprofile`);
|
|
176
|
+
fs.writeFileSync(file, JSON.stringify(profile), "utf8");
|
|
177
|
+
return { file, hotStack: summarizeHotStack(profile) };
|
|
178
|
+
}
|
|
179
|
+
catch (err) {
|
|
180
|
+
logger.error("cli", `[loop-profiler] failed to write profile: ${err?.message}`, {
|
|
181
|
+
error: err,
|
|
182
|
+
dir,
|
|
183
|
+
});
|
|
184
|
+
return null;
|
|
185
|
+
}
|
|
186
|
+
},
|
|
187
|
+
dispose() {
|
|
188
|
+
if (disposed)
|
|
189
|
+
return;
|
|
190
|
+
disposed = true;
|
|
191
|
+
void stopSegment(false);
|
|
192
|
+
try {
|
|
193
|
+
post("Profiler.disable");
|
|
194
|
+
session.disconnect();
|
|
195
|
+
}
|
|
196
|
+
catch (err) {
|
|
197
|
+
logger.warn("cli", `[loop-profiler] disconnect failed: ${err?.message}`, { error: err });
|
|
198
|
+
}
|
|
199
|
+
},
|
|
200
|
+
};
|
|
201
|
+
}
|
|
202
|
+
//# sourceMappingURL=loop-profiler.js.map
|
|
@@ -78,6 +78,12 @@ export interface ProviderKeyConfig {
|
|
|
78
78
|
}
|
|
79
79
|
export interface UserSettings {
|
|
80
80
|
apiKey?: string;
|
|
81
|
+
/**
|
|
82
|
+
* One-time guard: true once legacy keychain/settings API keys have been
|
|
83
|
+
* migrated into the env-store (`~/.muonroi-cli/.env`). See
|
|
84
|
+
* `migrateLegacyKeysToEnv` in src/providers/keychain.ts.
|
|
85
|
+
*/
|
|
86
|
+
keysMigratedToEnv?: boolean;
|
|
81
87
|
defaultModel?: string;
|
|
82
88
|
/**
|
|
83
89
|
* Preferred provider. When set, the splash/config UI hides the model
|
|
@@ -384,6 +390,27 @@ export declare function getProviderStallTimeoutMs(): number;
|
|
|
384
390
|
* override: MUONROI_PROVIDER_PROGRESS_TIMEOUT_MS.
|
|
385
391
|
*/
|
|
386
392
|
export declare function getProviderProgressTimeoutMs(): number;
|
|
393
|
+
/**
|
|
394
|
+
* Event-loop block reporting threshold (ms). The monitor
|
|
395
|
+
* (`src/utils/event-loop-monitor.ts`) reports a block when its own tick runs at
|
|
396
|
+
* least this late — evidence that the JS thread was stuck and NO timer could
|
|
397
|
+
* fire, which is why the provider stall watchdogs above cannot see this class
|
|
398
|
+
* of freeze (measured live 2026-07-16: a 304.5s TUI freeze that the 120s stall
|
|
399
|
+
* watchdog only rescued 6.7s AFTER the loop recovered).
|
|
400
|
+
*
|
|
401
|
+
* 2s is well clear of ordinary GC pauses and heavy renders while still catching
|
|
402
|
+
* anything a user would call a freeze. Range 250–60_000; 0 disables.
|
|
403
|
+
* Default 2_000. Env override: MUONROI_LOOP_BLOCK_THRESHOLD_MS.
|
|
404
|
+
*/
|
|
405
|
+
export declare function getLoopBlockThresholdMs(): number;
|
|
406
|
+
/**
|
|
407
|
+
* Whether to run the rolling CPU profiler (`src/utils/loop-profiler.ts`) so a
|
|
408
|
+
* reported event-loop block comes with the culprit's stack rather than just a
|
|
409
|
+
* duration. OFF by default: continuous V8 sampling costs a little on every
|
|
410
|
+
* session, and this is a diagnostic for a sporadic bug, not a feature. Arm it
|
|
411
|
+
* when hunting a freeze. Env: MUONROI_LOOP_PROFILE=1 (0/unset disables).
|
|
412
|
+
*/
|
|
413
|
+
export declare function isLoopProfileEnabled(): boolean;
|
|
387
414
|
/**
|
|
388
415
|
* Number of times to AUTOMATICALLY re-issue a streaming model call after the
|
|
389
416
|
* stall watchdog fires WITHOUT any chunk having arrived (a time-to-first-byte
|
|
@@ -275,10 +275,12 @@ export function saveProjectSettings(partial) {
|
|
|
275
275
|
}
|
|
276
276
|
export function getApiKey() {
|
|
277
277
|
// Test escape hatch (api-key harness spec): suppress all key sources so the
|
|
278
|
-
// boot flow reaches the
|
|
278
|
+
// boot flow reaches the provider picker. See src/index.ts resolveKeyForModel.
|
|
279
279
|
if (process.env.MUONROI_TEST_NO_KEYCHAIN === "1")
|
|
280
280
|
return undefined;
|
|
281
|
-
|
|
281
|
+
// Env-only: the legacy plaintext `settings.apiKey` is migrated to the
|
|
282
|
+
// env-store on first run (migrateLegacyKeysToEnv) and no longer read here.
|
|
283
|
+
return process.env.MUONROI_API_KEY || undefined;
|
|
282
284
|
}
|
|
283
285
|
export function getBaseURL(provider) {
|
|
284
286
|
if (process.env.MUONROI_BASE_URL)
|
|
@@ -662,6 +664,40 @@ export function getProviderProgressTimeoutMs() {
|
|
|
662
664
|
}
|
|
663
665
|
return 300_000;
|
|
664
666
|
}
|
|
667
|
+
/**
|
|
668
|
+
* Event-loop block reporting threshold (ms). The monitor
|
|
669
|
+
* (`src/utils/event-loop-monitor.ts`) reports a block when its own tick runs at
|
|
670
|
+
* least this late — evidence that the JS thread was stuck and NO timer could
|
|
671
|
+
* fire, which is why the provider stall watchdogs above cannot see this class
|
|
672
|
+
* of freeze (measured live 2026-07-16: a 304.5s TUI freeze that the 120s stall
|
|
673
|
+
* watchdog only rescued 6.7s AFTER the loop recovered).
|
|
674
|
+
*
|
|
675
|
+
* 2s is well clear of ordinary GC pauses and heavy renders while still catching
|
|
676
|
+
* anything a user would call a freeze. Range 250–60_000; 0 disables.
|
|
677
|
+
* Default 2_000. Env override: MUONROI_LOOP_BLOCK_THRESHOLD_MS.
|
|
678
|
+
*/
|
|
679
|
+
export function getLoopBlockThresholdMs() {
|
|
680
|
+
const envRaw = process.env.MUONROI_LOOP_BLOCK_THRESHOLD_MS;
|
|
681
|
+
if (envRaw !== undefined && envRaw !== "") {
|
|
682
|
+
const n = Number(envRaw);
|
|
683
|
+
if (Number.isFinite(n) && n === 0)
|
|
684
|
+
return 0; // explicit disable
|
|
685
|
+
if (Number.isFinite(n) && n >= 250 && n <= 60_000)
|
|
686
|
+
return Math.floor(n);
|
|
687
|
+
}
|
|
688
|
+
return 2_000;
|
|
689
|
+
}
|
|
690
|
+
/**
|
|
691
|
+
* Whether to run the rolling CPU profiler (`src/utils/loop-profiler.ts`) so a
|
|
692
|
+
* reported event-loop block comes with the culprit's stack rather than just a
|
|
693
|
+
* duration. OFF by default: continuous V8 sampling costs a little on every
|
|
694
|
+
* session, and this is a diagnostic for a sporadic bug, not a feature. Arm it
|
|
695
|
+
* when hunting a freeze. Env: MUONROI_LOOP_PROFILE=1 (0/unset disables).
|
|
696
|
+
*/
|
|
697
|
+
export function isLoopProfileEnabled() {
|
|
698
|
+
const raw = process.env.MUONROI_LOOP_PROFILE?.trim().toLowerCase();
|
|
699
|
+
return raw === "1" || raw === "true" || raw === "yes" || raw === "on";
|
|
700
|
+
}
|
|
665
701
|
/**
|
|
666
702
|
* Number of times to AUTOMATICALLY re-issue a streaming model call after the
|
|
667
703
|
* stall watchdog fires WITHOUT any chunk having arrived (a time-to-first-byte
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import type { ProviderFactory as LegacyProvider } from "../providers/runtime.js";
|
|
2
1
|
export interface SideQuestionResult {
|
|
3
2
|
response: string;
|
|
4
3
|
usage?: {
|
|
@@ -7,4 +6,4 @@ export interface SideQuestionResult {
|
|
|
7
6
|
outputTokens?: number;
|
|
8
7
|
};
|
|
9
8
|
}
|
|
10
|
-
export declare function runSideQuestion(question: string,
|
|
9
|
+
export declare function runSideQuestion(question: string, modelId: string, conversationContext: string, signal?: AbortSignal): Promise<SideQuestionResult>;
|
|
@@ -3,8 +3,8 @@ import { resolveModelRuntime } from "../providers/runtime.js";
|
|
|
3
3
|
const SIDE_QUESTION_SYSTEM = `You are a helpful coding assistant answering a quick side question. The user is in the middle of a coding session and needs a fast, concise answer. Keep your response short and focused — this is a side question, not the main task.
|
|
4
4
|
|
|
5
5
|
If conversation context is provided below, use it to give a more relevant answer.`;
|
|
6
|
-
export async function runSideQuestion(question,
|
|
7
|
-
const runtime = resolveModelRuntime(
|
|
6
|
+
export async function runSideQuestion(question, modelId, conversationContext, signal) {
|
|
7
|
+
const runtime = resolveModelRuntime(modelId);
|
|
8
8
|
const system = conversationContext
|
|
9
9
|
? `${SIDE_QUESTION_SYSTEM}\n\n<conversation_context>\n${conversationContext}\n</conversation_context>`
|
|
10
10
|
: SIDE_QUESTION_SYSTEM;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { VERIFY_FAIL_MARKER, VERIFY_PASS_MARKER } from "../product-loop/verify-result.js";
|
|
1
2
|
import { ensureVerifyCheckpoint } from "./checkpoint.js";
|
|
2
3
|
import { loadVerifyEnvironment } from "./environment.js";
|
|
3
4
|
import { buildBrowserGuidance, buildEvidenceGuidance, buildReadinessGuidance } from "./evidence.js";
|
|
@@ -69,6 +70,51 @@ export function buildVerifyTaskPrompt(cwd, settings, recipeOverride, sandboxMode
|
|
|
69
70
|
"Phase 1 — Setup:",
|
|
70
71
|
"- Probe the host for runtimes. If missing, attempt to install them or report as blockers.",
|
|
71
72
|
];
|
|
73
|
+
// Does this project actually expose a long-running app to start + smoke-test in
|
|
74
|
+
// a browser? Only then are Phase 3 (start app) / Phase 4 (browser QA) applicable.
|
|
75
|
+
// A CLI/library/script project (pytest-only Python, a Go/Rust binary, a plain
|
|
76
|
+
// script) has smokeKind "cli"/"none" and no startCommand — forcing "Start the
|
|
77
|
+
// app (REQUIRED)" + "browser QA (REQUIRED)" there is contradictory: the sub-agent
|
|
78
|
+
// cannot satisfy them, so it emits NEITHER verdict marker → parseVerifyResult →
|
|
79
|
+
// UNKNOWN → engineering-floor fails → the sprint retries forever on a passing
|
|
80
|
+
// build. Gate the runtime/browser phases and the verdict rule on this flag.
|
|
81
|
+
const hasRuntimeSmoke = profile.recipe.smokeKind === "http" && Boolean(profile.recipe.startCommand);
|
|
82
|
+
const runtimePhases = hasRuntimeSmoke
|
|
83
|
+
? [
|
|
84
|
+
"Phase 3 — Start the app (REQUIRED, do not skip):",
|
|
85
|
+
"- Start the app using startCommand from the recipe, running it in the background.",
|
|
86
|
+
"- Wait for the app to be ready: use a curl readiness loop or `agent-browser wait --load networkidle`.",
|
|
87
|
+
"- If the app fails to start, report the error but still attempt to capture evidence (logs, screenshots).",
|
|
88
|
+
"",
|
|
89
|
+
"Phase 4 — Browser QA testing (REQUIRED, do not skip):",
|
|
90
|
+
"- You are a QA tester. Open the app in the browser and test it like a human would.",
|
|
91
|
+
`- agent-browser commands run on the HOST${sandboxMode === "shuru" ? ", not the sandbox" : ""}. They WILL work. Do not skip them.`,
|
|
92
|
+
"- Record a video of the entire browser session.",
|
|
93
|
+
"- Navigate the app: click links, buttons, menus. Verify pages load correctly.",
|
|
94
|
+
"- Check for JavaScript console errors.",
|
|
95
|
+
"- Spend 3-5 interactions testing the critical path. Take screenshots after each.",
|
|
96
|
+
"- This is the most important phase. Build/lint passing means nothing if the app doesn't actually work.",
|
|
97
|
+
"",
|
|
98
|
+
"Phase 5 — Teardown:",
|
|
99
|
+
"- Stop recording, close browser, THEN stop the dev server.",
|
|
100
|
+
]
|
|
101
|
+
: [
|
|
102
|
+
"Phase 3 — Runtime smoke (CLI / library / script — there is NO long-running app to start):",
|
|
103
|
+
'- This project exposes no HTTP server or startable app (smokeKind is not "http"), so there is NOTHING to start in the background and NO browser QA to run.',
|
|
104
|
+
"- Do the equivalent CLI smoke instead: run the built binary / script (or the recipe's smoke command) once and confirm it produces the expected output and a zero exit code.",
|
|
105
|
+
"- Capture the exact command and its output as evidence.",
|
|
106
|
+
"- Browser and app-start phases are NOT APPLICABLE here — skipping them is CORRECT and is NOT a failure. Do NOT treat their absence as a blocker.",
|
|
107
|
+
];
|
|
108
|
+
const verdictRule = hasRuntimeSmoke
|
|
109
|
+
? [
|
|
110
|
+
`- After the report, emit the verdict on its own final line: exactly \`${VERIFY_PASS_MARKER}\` if install/build/test and the smoke/QA phases all succeeded, otherwise exactly \`${VERIFY_FAIL_MARKER}\`.`,
|
|
111
|
+
`- Emit \`${VERIFY_FAIL_MARKER}\` on ANY failed or skipped required phase (build error, failing test, app did not start, blocking console error). Do NOT emit \`${VERIFY_PASS_MARKER}\` if you could not actually run the recipe.`,
|
|
112
|
+
]
|
|
113
|
+
: [
|
|
114
|
+
`- After the report, emit the verdict on its own final line: exactly \`${VERIFY_PASS_MARKER}\` if install/build/test (and the CLI smoke, if any) all succeeded, otherwise exactly \`${VERIFY_FAIL_MARKER}\`.`,
|
|
115
|
+
`- This is a CLI/library/script project with no app to start: the browser and app-start phases are N/A, NOT failures. Do NOT withhold \`${VERIFY_PASS_MARKER}\` merely because they were not run.`,
|
|
116
|
+
`- Emit \`${VERIFY_FAIL_MARKER}\` ONLY on a real failure: an install/build error, a failing test, or a CLI smoke that produced the wrong result. Do NOT emit \`${VERIFY_PASS_MARKER}\` if you could not actually run the recipe's tests.`,
|
|
117
|
+
];
|
|
72
118
|
return [
|
|
73
119
|
"Run a local verification pass for the current workspace.",
|
|
74
120
|
"",
|
|
@@ -90,22 +136,7 @@ export function buildVerifyTaskPrompt(cwd, settings, recipeOverride, sandboxMode
|
|
|
90
136
|
"Phase 2 — Build and test:",
|
|
91
137
|
"- Run installCommands, buildCommands, and testCommands from the recipe.",
|
|
92
138
|
"",
|
|
93
|
-
|
|
94
|
-
"- Start the app using startCommand from the recipe, running it in the background.",
|
|
95
|
-
"- Wait for the app to be ready: use a curl readiness loop or `agent-browser wait --load networkidle`.",
|
|
96
|
-
"- If the app fails to start, report the error but still attempt to capture evidence (logs, screenshots).",
|
|
97
|
-
"",
|
|
98
|
-
"Phase 4 — Browser QA testing (REQUIRED, do not skip):",
|
|
99
|
-
"- You are a QA tester. Open the app in the browser and test it like a human would.",
|
|
100
|
-
`- agent-browser commands run on the HOST${sandboxMode === "shuru" ? ", not the sandbox" : ""}. They WILL work. Do not skip them.`,
|
|
101
|
-
"- Record a video of the entire browser session.",
|
|
102
|
-
"- Navigate the app: click links, buttons, menus. Verify pages load correctly.",
|
|
103
|
-
"- Check for JavaScript console errors.",
|
|
104
|
-
"- Spend 3-5 interactions testing the critical path. Take screenshots after each.",
|
|
105
|
-
"- This is the most important phase. Build/lint passing means nothing if the app doesn't actually work.",
|
|
106
|
-
"",
|
|
107
|
-
"Phase 5 — Teardown:",
|
|
108
|
-
"- Stop recording, close browser, THEN stop the dev server.",
|
|
139
|
+
...runtimePhases,
|
|
109
140
|
...buildReadinessGuidance(profile),
|
|
110
141
|
...buildBrowserGuidance(profile),
|
|
111
142
|
...buildRetryGuidance(profile),
|
|
@@ -122,6 +153,10 @@ export function buildVerifyTaskPrompt(cwd, settings, recipeOverride, sandboxMode
|
|
|
122
153
|
"- The Summary must say what recipe/source of truth you used and whether you changed the inferred/default one.",
|
|
123
154
|
"- Evidence is mandatory even on failure. If you captured screenshots, video, or logs, include their exact workspace-relative file paths in the Evidence section.",
|
|
124
155
|
"- Use markdown links for artifact paths when practical, otherwise include the plain relative paths.",
|
|
156
|
+
"",
|
|
157
|
+
"Verdict marker (MANDATORY — the loop parses this, an absent marker scores the sprint 0.00):",
|
|
158
|
+
...verdictRule,
|
|
159
|
+
"- The marker is the last line, nothing after it.",
|
|
125
160
|
].join("\n");
|
|
126
161
|
}
|
|
127
162
|
export function createVerifyTaskRequest(cwd, settings, recipeOverride, sandboxMode = "shuru") {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { TaskRequest, ToolResult, VerifyRecipe } from "../types/index.js";
|
|
2
|
-
import type
|
|
2
|
+
import { type SandboxSettings } from "../utils/settings.js";
|
|
3
3
|
import { type PreparedVerifyCheckpoint } from "./checkpoint.js";
|
|
4
4
|
import { type VerifyProjectProfile } from "./recipes.js";
|
|
5
5
|
export interface VerifyAgentLike {
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { getCurrentSandboxMode } from "../utils/settings.js";
|
|
1
2
|
import { ensureVerifyCheckpoint } from "./checkpoint.js";
|
|
2
3
|
import { buildVerifyTaskPrompt } from "./entrypoint.js";
|
|
3
4
|
import { loadVerifyEnvironment, saveVerifyEnvironment } from "./environment.js";
|
|
@@ -39,8 +40,21 @@ export async function prepareVerifyRun(agent, options = {}) {
|
|
|
39
40
|
}
|
|
40
41
|
}
|
|
41
42
|
const sandboxSettings = buildRuntimeSandboxSettings(profile);
|
|
42
|
-
|
|
43
|
-
|
|
43
|
+
// Sandbox "off" → run the recipe directly on the host; do NOT bootstrap a
|
|
44
|
+
// `shuru` checkpoint. ensureVerifyCheckpoint spawns `shuru checkpoint …`
|
|
45
|
+
// whenever the recipe has installCommands, regardless of mode — on a host
|
|
46
|
+
// without shuru installed that throws "Executable not found in $PATH: shuru",
|
|
47
|
+
// which parseVerifyResult maps to ERROR (never PASS), pinning the sprint score
|
|
48
|
+
// at 0.00. Skip it when the sole source of truth (getCurrentSandboxMode) is off.
|
|
49
|
+
const checkpoint = getCurrentSandboxMode() === "off"
|
|
50
|
+
? { created: false }
|
|
51
|
+
: await (async () => {
|
|
52
|
+
options.onProgress?.("Preparing verify checkpoint");
|
|
53
|
+
return ensureVerifyCheckpoint(cwd, profile, sandboxSettings);
|
|
54
|
+
})();
|
|
55
|
+
if (getCurrentSandboxMode() === "off") {
|
|
56
|
+
options.onProgress?.("Sandbox off — running verify on host (no shuru checkpoint)");
|
|
57
|
+
}
|
|
44
58
|
if (checkpoint.checkpointName) {
|
|
45
59
|
sandboxSettings.from = checkpoint.checkpointName;
|
|
46
60
|
if (checkpoint.guestWorkdir) {
|
|
@@ -57,7 +71,10 @@ export async function prepareVerifyRun(agent, options = {}) {
|
|
|
57
71
|
const taskRequest = {
|
|
58
72
|
agent: "verify",
|
|
59
73
|
description: "Run local verification",
|
|
60
|
-
prompt
|
|
74
|
+
// Thread the resolved mode so the prompt tells the sub-agent to run "on the
|
|
75
|
+
// host" instead of "inside the active Shuru sandbox" when sandbox is off
|
|
76
|
+
// (the param defaults to "shuru", which was wrong for an off host).
|
|
77
|
+
prompt: buildVerifyTaskPrompt(cwd, sandboxSettings, profile.recipe, getCurrentSandboxMode()),
|
|
61
78
|
};
|
|
62
79
|
return {
|
|
63
80
|
profile,
|
package/package.json
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"workspaces": [
|
|
4
4
|
"packages/*"
|
|
5
5
|
],
|
|
6
|
-
"version": "1.
|
|
6
|
+
"version": "1.9.0",
|
|
7
7
|
"description": "BYOK AI coding agent with multi-model council debate, role-based routing, and auto-compact.",
|
|
8
8
|
"repository": {
|
|
9
9
|
"type": "git",
|
|
@@ -98,7 +98,6 @@
|
|
|
98
98
|
"diff": "^8.0.3",
|
|
99
99
|
"fast-xml-parser": "^4.4.1",
|
|
100
100
|
"gpt-tokenizer": "^3.4.0",
|
|
101
|
-
"keytar": "7.9.0",
|
|
102
101
|
"ollama-ai-provider-v2": "1.5.5",
|
|
103
102
|
"proper-lockfile": "^4.1.2",
|
|
104
103
|
"react": "19.2.5",
|
|
@@ -1,55 +0,0 @@
|
|
|
1
|
-
import { type SpawnSyncReturns } from "node:child_process";
|
|
2
|
-
/**
|
|
3
|
-
* Thin wrapper around the Bitwarden CLI for writing secrets into the user's
|
|
4
|
-
* vault. Mirrors the assumptions of `runKeysImportBw`: the secret lives in the
|
|
5
|
-
* `notes` field of a Secure Note item named `<prefix><id>`.
|
|
6
|
-
*
|
|
7
|
-
* Auth contract: caller must have `bw` in PATH and `BW_SESSION` exported.
|
|
8
|
-
* On any error the returned object has `ok: false` so callers can degrade
|
|
9
|
-
* gracefully (e.g. fall back to writing only the OS keychain).
|
|
10
|
-
*/
|
|
11
|
-
export interface BwWriteResult {
|
|
12
|
-
ok: boolean;
|
|
13
|
-
action?: "created" | "updated";
|
|
14
|
-
error?: string;
|
|
15
|
-
}
|
|
16
|
-
type Runner = (cmd: string, args: string[], input?: string) => SpawnSyncReturns<string>;
|
|
17
|
-
/**
|
|
18
|
-
* Write `notes` into a vault item named `name`. Creates a Secure Note if
|
|
19
|
-
* absent; updates the notes field if present. Calls `bw sync` after a write
|
|
20
|
-
* so subsequent `bw get` calls see the new value.
|
|
21
|
-
*/
|
|
22
|
-
export declare function writeBwSecureNote(name: string, notes: string, options?: {
|
|
23
|
-
runner?: Runner;
|
|
24
|
-
}): Promise<BwWriteResult>;
|
|
25
|
-
export interface BwUnlockResult {
|
|
26
|
-
ok: boolean;
|
|
27
|
-
session?: string;
|
|
28
|
-
error?: string;
|
|
29
|
-
}
|
|
30
|
-
/**
|
|
31
|
-
* Unlock the BW vault with a master password and return the session token
|
|
32
|
-
* in-memory only. Caller passes the returned session to subsequent calls;
|
|
33
|
-
* we never write to BW_SESSION env or persist it to disk.
|
|
34
|
-
*/
|
|
35
|
-
export declare function unlockWithPassword(password: string, options?: {
|
|
36
|
-
runner?: Runner;
|
|
37
|
-
}): Promise<BwUnlockResult>;
|
|
38
|
-
export interface BwListedNote {
|
|
39
|
-
name: string;
|
|
40
|
-
notes: string;
|
|
41
|
-
}
|
|
42
|
-
/**
|
|
43
|
-
* List Secure Notes whose name starts with `prefix`. Used by the in-TUI
|
|
44
|
-
* sync flow to discover which providers have keys stored in the vault.
|
|
45
|
-
*/
|
|
46
|
-
export declare function listSecureNotesByPrefix(session: string, prefix: string, options?: {
|
|
47
|
-
runner?: Runner;
|
|
48
|
-
}): Promise<{
|
|
49
|
-
ok: true;
|
|
50
|
-
items: BwListedNote[];
|
|
51
|
-
} | {
|
|
52
|
-
ok: false;
|
|
53
|
-
error: string;
|
|
54
|
-
}>;
|
|
55
|
-
export {};
|