@tangle-network/agent-runtime 0.56.1 → 0.57.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/agent.d.ts +1 -1
- package/dist/agent.js +1 -1
- package/dist/analyst-loop.d.ts +1 -1
- package/dist/{chunk-6XKXWA7H.js → chunk-6BOIKGSU.js} +2 -2
- package/dist/{chunk-F5XQA43K.js → chunk-NJST5D2G.js} +2 -2
- package/dist/{chunk-4H2FML7G.js → chunk-RLDUT4JL.js} +758 -191
- package/dist/chunk-RLDUT4JL.js.map +1 -0
- package/dist/{chunk-EXIV2C72.js → chunk-TVBXDW7C.js} +59 -79
- package/dist/chunk-TVBXDW7C.js.map +1 -0
- package/dist/{chunk-G4NIVG34.js → chunk-WFEQCRQP.js} +4 -3
- package/dist/chunk-WFEQCRQP.js.map +1 -0
- package/dist/{coder-COuOK8h8.d.ts → coder-CybltHEm.d.ts} +1 -1
- package/dist/{coordination-DWNGqygr.d.ts → coordination-BydGBQCZ.d.ts} +164 -4
- package/dist/{delegates-D9o5_VFj.d.ts → delegates-C94qchkz.d.ts} +8 -2
- package/dist/index.d.ts +6 -6
- package/dist/index.js +4 -4
- package/dist/intelligence.d.ts +1 -1
- package/dist/{loop-runner-bin-CN2Se3jB.d.ts → loop-runner-bin-Noz7P-mS.d.ts} +3 -3
- package/dist/loop-runner-bin.d.ts +4 -4
- package/dist/loop-runner-bin.js +3 -3
- package/dist/loops.d.ts +7 -6
- package/dist/loops.js +13 -1
- package/dist/mcp/bin.js +3 -3
- package/dist/mcp/index.d.ts +20 -82
- package/dist/mcp/index.js +4 -4
- package/dist/{openai-tools-CoeLQ7Uo.d.ts → openai-tools-d4GKwgya.d.ts} +1 -1
- package/dist/profiles.d.ts +2 -2
- package/dist/{run-loop-DluzfJ2h.d.ts → run-loop-CcqfR_gy.d.ts} +1 -1
- package/dist/runtime.d.ts +255 -67
- package/dist/runtime.js +13 -1
- package/dist/{types-C8rNlxfV.d.ts → types-CUzjRFZ3.d.ts} +1 -1
- package/dist/workflow.d.ts +2 -2
- package/dist/workflow.js +1 -1
- package/package.json +2 -2
- package/skills/loop-writer/SKILL.md +1 -1
- package/skills/supervise/SKILL.md +1 -1
- package/dist/chunk-4H2FML7G.js.map +0 -1
- package/dist/chunk-EXIV2C72.js.map +0 -1
- package/dist/chunk-G4NIVG34.js.map +0 -1
- /package/dist/{chunk-6XKXWA7H.js.map → chunk-6BOIKGSU.js.map} +0 -0
- /package/dist/{chunk-F5XQA43K.js.map → chunk-NJST5D2G.js.map} +0 -0
|
@@ -1,15 +1,10 @@
|
|
|
1
1
|
import {
|
|
2
2
|
createFleetWorkspaceExecutor,
|
|
3
3
|
createSiblingSandboxExecutor
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-WFEQCRQP.js";
|
|
5
5
|
import {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
removeWorktree
|
|
9
|
-
} from "./chunk-4H2FML7G.js";
|
|
10
|
-
import {
|
|
11
|
-
runLocalHarness
|
|
12
|
-
} from "./chunk-GLMFBUKT.js";
|
|
6
|
+
runWorktreeHarness
|
|
7
|
+
} from "./chunk-RLDUT4JL.js";
|
|
13
8
|
import {
|
|
14
9
|
buildLoopOtelSpans,
|
|
15
10
|
createOtelExporter
|
|
@@ -21,108 +16,78 @@ var DEFAULT_HARNESS_TIMEOUT_MS = 5 * 60 * 1e3;
|
|
|
21
16
|
var DEFAULT_POSTCHECK_TIMEOUT_MS = 2 * 60 * 1e3;
|
|
22
17
|
function createInProcessExecutor(options) {
|
|
23
18
|
const harnesses = options.harnesses && options.harnesses.length > 0 ? [...options.harnesses] : ["claude"];
|
|
24
|
-
const runHarness = options.runHarness ?? runLocalHarness;
|
|
25
19
|
const runPostCheck = options.runPostCheck ?? defaultRunPostCheck;
|
|
20
|
+
const runCommand = async ({
|
|
21
|
+
command,
|
|
22
|
+
cwd,
|
|
23
|
+
signal
|
|
24
|
+
}) => {
|
|
25
|
+
try {
|
|
26
|
+
const r = await runPostCheck(command, cwd, signal);
|
|
27
|
+
return { exitCode: r.exitCode, output: r.stderr || r.stdout };
|
|
28
|
+
} catch (err) {
|
|
29
|
+
return { exitCode: -1, output: err instanceof Error ? err.message : String(err) };
|
|
30
|
+
}
|
|
31
|
+
};
|
|
26
32
|
let callIndex = 0;
|
|
27
33
|
const client = {
|
|
28
|
-
async create(
|
|
34
|
+
async create(opts) {
|
|
29
35
|
const runId = randomUUID();
|
|
30
36
|
const harness = harnesses[callIndex % harnesses.length];
|
|
31
37
|
callIndex += 1;
|
|
38
|
+
const profile = opts?.backend?.profile ?? { name: `in-process-${harness}` };
|
|
32
39
|
const virtual = {
|
|
33
|
-
// Synthesize the minimum SandboxInstance surface the kernel touches.
|
|
34
|
-
// We CAST through unknown because SandboxInstance is a `declare class`
|
|
35
|
-
// with private fields; we're producing a structural subtype that
|
|
36
|
-
// satisfies the kernel's narrow usage (`box.id`, `box.streamPrompt`).
|
|
37
40
|
id: `in-process-${runId}`,
|
|
38
41
|
__inProcess: { runId, harness },
|
|
39
|
-
// eslint-disable-next-line require-yield
|
|
40
42
|
async *streamPrompt(message, promptOpts) {
|
|
41
43
|
const taskPrompt = typeof message === "string" ? message : message.map(
|
|
42
44
|
(p) => typeof p === "object" && p && "text" in p ? String(p.text) : ""
|
|
43
45
|
).join("\n");
|
|
44
|
-
|
|
46
|
+
const run = await runWorktreeHarness({
|
|
47
|
+
repoRoot: options.repoRoot,
|
|
48
|
+
profile,
|
|
49
|
+
harness,
|
|
50
|
+
taskPrompt,
|
|
51
|
+
runId,
|
|
52
|
+
harnessTimeoutMs: options.harnessTimeoutMs ?? DEFAULT_HARNESS_TIMEOUT_MS,
|
|
53
|
+
checkTimeoutMs: options.postCheckTimeoutMs ?? DEFAULT_POSTCHECK_TIMEOUT_MS,
|
|
54
|
+
...options.testCmd !== void 0 ? { testCmd: options.testCmd } : {},
|
|
55
|
+
...options.typecheckCmd !== void 0 ? { typecheckCmd: options.typecheckCmd } : {},
|
|
56
|
+
...options.runGit ? { runGit: options.runGit } : {},
|
|
57
|
+
...options.runHarness ? { runHarness: options.runHarness } : {},
|
|
58
|
+
runCommand,
|
|
59
|
+
...promptOpts?.signal ? { signal: promptOpts.signal } : {}
|
|
60
|
+
});
|
|
61
|
+
this.__inProcess.worktree = run.worktree;
|
|
45
62
|
try {
|
|
46
|
-
worktree = await createWorktree({
|
|
47
|
-
repoRoot: options.repoRoot,
|
|
48
|
-
runId,
|
|
49
|
-
runGit: options.runGit
|
|
50
|
-
});
|
|
51
|
-
this.__inProcess.worktree = worktree;
|
|
52
63
|
yield {
|
|
53
64
|
type: "in_process.harness.started",
|
|
54
|
-
data: {
|
|
55
|
-
runId,
|
|
56
|
-
harness,
|
|
57
|
-
worktreePath: worktree.path,
|
|
58
|
-
command: harness
|
|
59
|
-
}
|
|
65
|
+
data: { runId, harness, worktreePath: run.worktree.path, command: harness }
|
|
60
66
|
};
|
|
61
|
-
const
|
|
62
|
-
harness,
|
|
63
|
-
cwd: worktree.path,
|
|
64
|
-
taskPrompt,
|
|
65
|
-
timeoutMs: options.harnessTimeoutMs ?? DEFAULT_HARNESS_TIMEOUT_MS,
|
|
66
|
-
signal: promptOpts?.signal
|
|
67
|
-
});
|
|
67
|
+
const h = run.result.harness;
|
|
68
68
|
yield {
|
|
69
69
|
type: "in_process.harness.ended",
|
|
70
70
|
data: {
|
|
71
71
|
runId,
|
|
72
|
-
exitCode:
|
|
73
|
-
durationMs:
|
|
74
|
-
killedBySignal:
|
|
75
|
-
timedOut:
|
|
76
|
-
stdoutBytes:
|
|
77
|
-
stderrBytes:
|
|
72
|
+
exitCode: h.exitCode,
|
|
73
|
+
durationMs: h.durationMs,
|
|
74
|
+
killedBySignal: h.killedBySignal,
|
|
75
|
+
timedOut: h.timedOut,
|
|
76
|
+
stdoutBytes: h.stdout.length,
|
|
77
|
+
stderrBytes: h.stderr.length
|
|
78
78
|
}
|
|
79
79
|
};
|
|
80
|
-
const diff = await captureWorktreeDiff({ worktree, runGit: options.runGit });
|
|
81
|
-
const testCheck = options.testCmd ? await runPostCheck(options.testCmd, worktree.path, promptOpts?.signal).catch(
|
|
82
|
-
(err) => ({
|
|
83
|
-
exitCode: -1,
|
|
84
|
-
stdout: "",
|
|
85
|
-
stderr: err instanceof Error ? err.message : String(err)
|
|
86
|
-
})
|
|
87
|
-
) : { exitCode: 0, stdout: "", stderr: "" };
|
|
88
|
-
const typecheckCheck = options.typecheckCmd ? await runPostCheck(options.typecheckCmd, worktree.path, promptOpts?.signal).catch(
|
|
89
|
-
(err) => ({
|
|
90
|
-
exitCode: -1,
|
|
91
|
-
stdout: "",
|
|
92
|
-
stderr: err instanceof Error ? err.message : String(err)
|
|
93
|
-
})
|
|
94
|
-
) : { exitCode: 0, stdout: "", stderr: "" };
|
|
95
|
-
const coderOutput = {
|
|
96
|
-
branch: worktree.branch,
|
|
97
|
-
patch: diff.patch,
|
|
98
|
-
testResult: {
|
|
99
|
-
passed: !options.testCmd || testCheck.exitCode === 0,
|
|
100
|
-
output: tail(testCheck.stderr || testCheck.stdout, 4e3)
|
|
101
|
-
},
|
|
102
|
-
typecheckResult: {
|
|
103
|
-
passed: !options.typecheckCmd || typecheckCheck.exitCode === 0,
|
|
104
|
-
output: tail(typecheckCheck.stderr || typecheckCheck.stdout, 4e3)
|
|
105
|
-
},
|
|
106
|
-
diffStats: diff.stats,
|
|
107
|
-
reviewerNotes: harnessResult.exitCode === 0 ? void 0 : `harness ${harness} exited ${harnessResult.exitCode}${harnessResult.timedOut ? " (timed out)" : ""}`
|
|
108
|
-
};
|
|
109
80
|
yield {
|
|
110
81
|
type: "result",
|
|
111
82
|
data: {
|
|
112
|
-
result:
|
|
83
|
+
result: toCoderOutput(run.result, harness),
|
|
113
84
|
source: "in-process-executor",
|
|
114
85
|
harness,
|
|
115
86
|
runId
|
|
116
87
|
}
|
|
117
88
|
};
|
|
118
89
|
} finally {
|
|
119
|
-
|
|
120
|
-
await removeWorktree({
|
|
121
|
-
worktree,
|
|
122
|
-
repoRoot: options.repoRoot,
|
|
123
|
-
runGit: options.runGit
|
|
124
|
-
}).catch(() => void 0);
|
|
125
|
-
}
|
|
90
|
+
await run.cleanup();
|
|
126
91
|
}
|
|
127
92
|
}
|
|
128
93
|
};
|
|
@@ -147,6 +112,21 @@ function createInProcessExecutor(options) {
|
|
|
147
112
|
}
|
|
148
113
|
};
|
|
149
114
|
}
|
|
115
|
+
function toCoderOutput(result, harness) {
|
|
116
|
+
const tests = result.checks?.tests;
|
|
117
|
+
const typecheck = result.checks?.typecheck;
|
|
118
|
+
return {
|
|
119
|
+
branch: result.branch,
|
|
120
|
+
patch: result.patch,
|
|
121
|
+
testResult: { passed: tests ? tests.passed : true, output: tail(tests?.output ?? "", 4e3) },
|
|
122
|
+
typecheckResult: {
|
|
123
|
+
passed: typecheck ? typecheck.passed : true,
|
|
124
|
+
output: tail(typecheck?.output ?? "", 4e3)
|
|
125
|
+
},
|
|
126
|
+
diffStats: result.stats,
|
|
127
|
+
reviewerNotes: result.harness.exitCode === 0 ? void 0 : `harness ${harness} exited ${result.harness.exitCode}${result.harness.timedOut ? " (timed out)" : ""}`
|
|
128
|
+
};
|
|
129
|
+
}
|
|
150
130
|
async function defaultRunPostCheck(cmd, cwd, signal) {
|
|
151
131
|
const { spawn } = await import("child_process");
|
|
152
132
|
return new Promise((resolve, reject) => {
|
|
@@ -314,4 +294,4 @@ export {
|
|
|
314
294
|
createPropagatingTraceEmitter,
|
|
315
295
|
traceContextToEnv
|
|
316
296
|
};
|
|
317
|
-
//# sourceMappingURL=chunk-
|
|
297
|
+
//# sourceMappingURL=chunk-TVBXDW7C.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/mcp/in-process-executor.ts","../src/mcp/bin-helpers.ts","../src/mcp/trace-propagation.ts"],"sourcesContent":["/**\n * @experimental\n *\n * In-process delegation executor — when `agent-runtime-mcp` runs inside a sandbox whose image\n * carries the local coding-harness CLIs (claude / codex / opencode), delegations spawn the harness\n * AS A SUBPROCESS against a git worktree on the SAME filesystem instead of provisioning a sibling\n * sandbox. Zero provisioning latency; worker diffs land in-place; multi-harness fanout = N parallel\n * subprocesses in N parallel worktrees (round-robin `harnesses`).\n *\n * This is a THIN adapter over `runWorktreeHarness` (`./worktree-harness`) — the SAME core the\n * `Scope` leaf `createWorktreeCliExecutor` uses. It only adapts the core to the `SandboxClient`\n * port: `create()` reads the authored profile from `CreateSandboxOptions.backend.profile`, and\n * `streamPrompt` runs the core then projects its result into the `CoderOutput`-shaped `result`\n * event the `coderProfile` parser reads. The §1.5 payload (systemPrompt + model) reaches the\n * harness inside the core — the prompt-only path that dropped it is gone.\n */\n\nimport { randomUUID } from 'node:crypto'\nimport type {\n AgentProfile,\n CreateSandboxOptions,\n SandboxEvent,\n SandboxInstance,\n} from '@tangle-network/sandbox'\nimport type { LoopSandboxPlacement, SandboxClient } from '../runtime'\nimport type { DelegationExecutor } from './executor'\nimport type { LocalHarness } from './local-harness'\nimport type { GitRunner, WorktreeHandle } from './worktree'\nimport { runWorktreeHarness, type WorktreeHarnessResult } from './worktree-harness'\n\n/** @experimental */\nexport interface InProcessExecutorOptions {\n /** Absolute path to the git repo (the workspace). Worktrees go under `<repoRoot>/.coder-variants/`. */\n repoRoot: string\n /** Harnesses to round-robin across `create()` calls. One entry = no fanout. Default `['claude']`. */\n harnesses?: ReadonlyArray<LocalHarness>\n /** Optional per-delegation test command run in the worktree after the harness exits. */\n testCmd?: string\n /** Optional per-delegation typecheck command. Same shape as `testCmd`. */\n typecheckCmd?: string\n /** Wall-clock cap per harness subprocess (ms). Default 5min. */\n harnessTimeoutMs?: number\n /** Wall-clock cap per test/typecheck subprocess (ms). Default 2min. */\n postCheckTimeoutMs?: number\n /** Test seam — override the git runner used by the worktree helpers. */\n runGit?: GitRunner\n /** Test seam — override the harness runner (defaults to the real CLI via `runLocalHarness`). */\n runHarness?: typeof import('./local-harness').runLocalHarness\n /** Test seam — override the post-check runner (defaults to a `sh -c` spawn). A throw is folded\n * into a non-fatal `{exitCode:-1}` so a broken check command fails the signal, not the run. */\n runPostCheck?: (\n cmd: string,\n cwd: string,\n signal?: AbortSignal,\n ) => Promise<{ exitCode: number; stdout: string; stderr: string }>\n}\n\n/** @experimental */\nexport interface InProcessExecutorDescribePlacement extends LoopSandboxPlacement {\n /** Worktree path in the parent sandbox's filesystem (set so traces correlate to on-disk artifacts). */\n worktreePath?: string\n /** Which harness handled this delegation. */\n harness?: LocalHarness\n}\n\ninterface VirtualSandbox extends SandboxInstance {\n __inProcess: {\n runId: string\n harness: LocalHarness\n worktree?: WorktreeHandle\n }\n}\n\n/** The `CoderOutput` shape the `coderProfile` event parser reads off `data.result`. */\ninterface CoderOutput {\n branch: string\n patch: string\n testResult: { passed: boolean; output: string }\n typecheckResult: { passed: boolean; output: string }\n diffStats: { filesChanged: number; insertions: number; deletions: number }\n reviewerNotes?: string\n}\n\nconst DEFAULT_HARNESS_TIMEOUT_MS = 5 * 60 * 1000\nconst DEFAULT_POSTCHECK_TIMEOUT_MS = 2 * 60 * 1000\n\n/**\n * Build an in-process executor. Returns a {@link DelegationExecutor} whose `client.create()`\n * returns a minimal virtual `SandboxInstance`; the kernel calls `streamPrompt(msg)` on it, which\n * runs the shared worktree-harness core and emits one `result` event whose `data.result` is a\n * `CoderOutput`. The authored profile (`backend.profile`) threads its systemPrompt + model into\n * the harness via the core.\n *\n * @experimental\n */\nexport function createInProcessExecutor(options: InProcessExecutorOptions): DelegationExecutor {\n const harnesses =\n options.harnesses && options.harnesses.length > 0\n ? [...options.harnesses]\n : (['claude'] as const)\n const runPostCheck = options.runPostCheck ?? defaultRunPostCheck\n // The core speaks one `runCommand` seam ({exitCode, output}); adapt the post-check seam\n // ({exitCode, stdout, stderr}) onto it, folding a throw into a non-fatal failure signal so a\n // broken check command fails the signal rather than aborting the whole delegation.\n const runCommand = async ({\n command,\n cwd,\n signal,\n }: {\n command: string\n cwd: string\n timeoutMs: number\n signal?: AbortSignal\n }): Promise<{ exitCode: number | null; output: string }> => {\n try {\n const r = await runPostCheck(command, cwd, signal)\n return { exitCode: r.exitCode, output: r.stderr || r.stdout }\n } catch (err) {\n return { exitCode: -1, output: err instanceof Error ? err.message : String(err) }\n }\n }\n\n let callIndex = 0\n\n const client: SandboxClient = {\n async create(opts?: CreateSandboxOptions): Promise<SandboxInstance> {\n const runId = randomUUID()\n const harness = harnesses[callIndex % harnesses.length] as LocalHarness\n callIndex += 1\n // §1.5: the authored profile rides in `backend.profile` (set by `buildBackendOptions`).\n // Without one (a direct test `create()`), fall back to a name-only profile → the harness\n // sees the task prompt with no system prepend, the pre-fix behavior.\n const profile =\n ((opts?.backend as { profile?: AgentProfile } | undefined)?.profile as\n | AgentProfile\n | undefined) ?? ({ name: `in-process-${harness}` } as AgentProfile)\n\n const virtual: VirtualSandbox = {\n id: `in-process-${runId}`,\n __inProcess: { runId, harness },\n async *streamPrompt(\n this: VirtualSandbox,\n message: string | unknown[],\n promptOpts?: { signal?: AbortSignal },\n ): AsyncGenerator<SandboxEvent> {\n const taskPrompt =\n typeof message === 'string'\n ? message\n : message\n .map((p) =>\n typeof p === 'object' && p && 'text' in p\n ? String((p as { text: unknown }).text)\n : '',\n )\n .join('\\n')\n\n // The shared worktree-harness core: worktree → profile-aware harness → diff → checks.\n // A throw cleans up inside the core; on success we own the teardown (the finally).\n const run = await runWorktreeHarness({\n repoRoot: options.repoRoot,\n profile,\n harness,\n taskPrompt,\n runId,\n harnessTimeoutMs: options.harnessTimeoutMs ?? DEFAULT_HARNESS_TIMEOUT_MS,\n checkTimeoutMs: options.postCheckTimeoutMs ?? DEFAULT_POSTCHECK_TIMEOUT_MS,\n ...(options.testCmd !== undefined ? { testCmd: options.testCmd } : {}),\n ...(options.typecheckCmd !== undefined ? { typecheckCmd: options.typecheckCmd } : {}),\n ...(options.runGit ? { runGit: options.runGit } : {}),\n ...(options.runHarness ? { runHarness: options.runHarness } : {}),\n runCommand,\n ...(promptOpts?.signal ? { signal: promptOpts.signal } : {}),\n })\n this.__inProcess.worktree = run.worktree\n\n try {\n yield {\n type: 'in_process.harness.started',\n data: { runId, harness, worktreePath: run.worktree.path, command: harness },\n }\n const h = run.result.harness\n yield {\n type: 'in_process.harness.ended',\n data: {\n runId,\n exitCode: h.exitCode,\n durationMs: h.durationMs,\n killedBySignal: h.killedBySignal,\n timedOut: h.timedOut,\n stdoutBytes: h.stdout.length,\n stderrBytes: h.stderr.length,\n },\n }\n yield {\n type: 'result',\n data: {\n result: toCoderOutput(run.result, harness),\n source: 'in-process-executor',\n harness,\n runId,\n },\n }\n } finally {\n await run.cleanup()\n }\n },\n } as unknown as VirtualSandbox\n\n return virtual\n },\n describePlacement(box: SandboxInstance): InProcessExecutorDescribePlacement {\n const sandboxId = (box as unknown as { id?: string }).id\n const meta = (box as VirtualSandbox).__inProcess\n return {\n kind: 'sibling',\n sandboxId,\n worktreePath: meta?.worktree?.path,\n harness: meta?.harness,\n }\n },\n }\n\n return {\n client,\n placement: 'in-process',\n describe(): string {\n return `in-process (repoRoot=${options.repoRoot}, harnesses=[${harnesses.join(',')}]${\n options.testCmd ? `, testCmd=\"${options.testCmd}\"` : ''\n }${options.typecheckCmd ? `, typecheckCmd=\"${options.typecheckCmd}\"` : ''})`\n },\n }\n}\n\n/** Project the canonical worktree-harness result onto the `CoderOutput` the coder parser reads.\n * A check that did not run (no command configured) is treated as passing — the pre-refactor rule. */\nfunction toCoderOutput(result: WorktreeHarnessResult, harness: LocalHarness): CoderOutput {\n const tests = result.checks?.tests\n const typecheck = result.checks?.typecheck\n return {\n branch: result.branch,\n patch: result.patch,\n testResult: { passed: tests ? tests.passed : true, output: tail(tests?.output ?? '', 4000) },\n typecheckResult: {\n passed: typecheck ? typecheck.passed : true,\n output: tail(typecheck?.output ?? '', 4000),\n },\n diffStats: result.stats,\n reviewerNotes:\n result.harness.exitCode === 0\n ? undefined\n : `harness ${harness} exited ${result.harness.exitCode}${result.harness.timedOut ? ' (timed out)' : ''}`,\n }\n}\n\nasync function defaultRunPostCheck(\n cmd: string,\n cwd: string,\n signal?: AbortSignal,\n): Promise<{ exitCode: number; stdout: string; stderr: string }> {\n const { spawn } = await import('node:child_process')\n return new Promise((resolve, reject) => {\n const child = spawn('sh', ['-c', cmd], { cwd, stdio: 'pipe' })\n let stdout = ''\n let stderr = ''\n child.stdout?.on('data', (c) => {\n stdout += String(c)\n })\n child.stderr?.on('data', (c) => {\n stderr += String(c)\n })\n if (signal) {\n const onAbort = () => {\n if (!child.killed) child.kill('SIGTERM')\n }\n if (signal.aborted) onAbort()\n else signal.addEventListener('abort', onAbort, { once: true })\n }\n const killTimer = setTimeout(() => {\n if (!child.killed) child.kill('SIGTERM')\n }, DEFAULT_POSTCHECK_TIMEOUT_MS)\n if (typeof (killTimer as { unref?: () => void }).unref === 'function') {\n ;(killTimer as { unref: () => void }).unref()\n }\n child.on('error', (err) => {\n clearTimeout(killTimer)\n reject(err)\n })\n child.on('close', (code) => {\n clearTimeout(killTimer)\n resolve({ exitCode: code ?? -1, stdout, stderr })\n })\n })\n}\n\nfunction tail(text: string, max: number): string {\n if (text.length <= max) return text\n return text.slice(text.length - max)\n}\n","/**\n * @experimental\n *\n * Helpers extracted from `bin.ts` so the env-detection + executor-selection\n * logic is unit-testable without spawning a subprocess. The bin imports from\n * here; tests import from here directly.\n */\n\nimport type { SandboxClient } from '../runtime'\nimport {\n createFleetWorkspaceExecutor,\n createSiblingSandboxExecutor,\n type DelegationExecutor,\n type FleetHandle,\n} from './executor'\nimport { createInProcessExecutor } from './in-process-executor'\nimport type { LocalHarness } from './local-harness'\n\n/** @experimental */\nexport interface DetectExecutorArgs {\n sandboxClient: SandboxClient\n /** Raw env (defaults to `process.env`). Pass an explicit map for tests. */\n env?: Record<string, string | undefined>\n /**\n * Override how a fleet handle is resolved from the client + fleet id. The\n * default reads `client.fleets.get(fleetId)` and validates the returned\n * shape against the structural `FleetHandle` contract.\n */\n resolveFleet?: (client: SandboxClient, fleetId: string) => Promise<FleetHandle>\n}\n\n/**\n * Pick the right executor for an MCP server invocation based on env vars.\n *\n * - `TANGLE_FLEET_ID` set → fleet-workspace placement; resolves the handle\n * via `sandboxClient.fleets.get(...)`.\n * - Otherwise → sibling-sandbox placement; each delegation creates a fresh\n * sandbox via `sandboxClient.create(...)`.\n *\n * Fails loud (throws) when fleet mode is requested but the SDK shape is\n * incompatible — the operator chose fleet semantics, silently degrading to\n * sibling mode would lie about workspace topology.\n *\n * @experimental\n */\nexport async function detectExecutor(args: DetectExecutorArgs): Promise<DelegationExecutor> {\n const env = args.env ?? process.env\n\n // In-process (Phase 2.8): parent harness sets AGENT_RUNTIME_IN_SANDBOX=1\n // and points us at the workspace root. Highest-priority — when this is\n // set, delegations spawn local harness CLIs on git worktrees in the\n // SAME filesystem instead of provisioning sibling sandboxes.\n if (env.AGENT_RUNTIME_IN_SANDBOX === '1') {\n const repoRoot = env.AGENT_RUNTIME_REPO_ROOT?.trim()\n if (!repoRoot) {\n throw new Error(\n 'agent-runtime-mcp: AGENT_RUNTIME_IN_SANDBOX=1 requires AGENT_RUNTIME_REPO_ROOT to point at the workspace root',\n )\n }\n return createInProcessExecutor({\n repoRoot,\n harnesses: parseHarnesses(env.AGENT_RUNTIME_LOCAL_HARNESSES),\n testCmd: env.AGENT_RUNTIME_TEST_CMD?.trim() || undefined,\n typecheckCmd: env.AGENT_RUNTIME_TYPECHECK_CMD?.trim() || undefined,\n })\n }\n\n const fleetId = parseFleetId(env.TANGLE_FLEET_ID)\n if (!fleetId) {\n return createSiblingSandboxExecutor({ client: args.sandboxClient })\n }\n const resolveFleet = args.resolveFleet ?? defaultResolveFleet\n const fleet = await resolveFleet(args.sandboxClient, fleetId)\n const excludeMachineIds = parseList(env.TANGLE_FLEET_EXCLUDE_MACHINES)\n return createFleetWorkspaceExecutor({\n fleet,\n excludeMachineIds,\n })\n}\n\nconst KNOWN_HARNESSES: ReadonlyArray<LocalHarness> = ['claude', 'codex', 'opencode']\n\nfunction parseHarnesses(raw: string | undefined): ReadonlyArray<LocalHarness> | undefined {\n if (!raw) return undefined\n const parts = raw\n .split(',')\n .map((s) => s.trim())\n .filter(Boolean)\n if (parts.length === 0) return undefined\n for (const part of parts) {\n if (!KNOWN_HARNESSES.includes(part as LocalHarness)) {\n throw new Error(\n `agent-runtime-mcp: AGENT_RUNTIME_LOCAL_HARNESSES contains unknown harness \"${part}\". Expected: ${KNOWN_HARNESSES.join(', ')}.`,\n )\n }\n }\n return parts as LocalHarness[]\n}\n\ninterface FleetsApi {\n get(fleetId: string): Promise<unknown>\n}\n\nasync function defaultResolveFleet(\n sandboxClient: SandboxClient,\n fleetId: string,\n): Promise<FleetHandle> {\n const fleets = (sandboxClient as unknown as { fleets?: FleetsApi }).fleets\n if (!fleets || typeof fleets.get !== 'function') {\n throw new Error(\n 'agent-runtime-mcp: the configured sandbox client does not expose `.fleets.get`; upgrade @tangle-network/sandbox to >= 0.2.1 or unset TANGLE_FLEET_ID.',\n )\n }\n const raw = await fleets.get(fleetId)\n if (!raw || typeof raw !== 'object') {\n throw new Error(`agent-runtime-mcp: fleets.get(${fleetId}) returned no handle`)\n }\n const handle = raw as Partial<FleetHandle>\n if (typeof handle.fleetId !== 'string' || !Array.isArray(handle.ids)) {\n throw new Error(\n `agent-runtime-mcp: fleet handle for ${fleetId} is missing fleetId/ids — incompatible sandbox SDK shape`,\n )\n }\n if (typeof handle.sandbox !== 'function') {\n throw new Error(\n `agent-runtime-mcp: fleet handle for ${fleetId} is missing sandbox(machineId) — incompatible sandbox SDK shape`,\n )\n }\n return handle as FleetHandle\n}\n\nfunction parseFleetId(raw: string | undefined): string | undefined {\n if (typeof raw !== 'string') return undefined\n const trimmed = raw.trim()\n return trimmed.length > 0 ? trimmed : undefined\n}\n\nfunction parseList(raw: string | undefined): string[] | undefined {\n if (!raw) return undefined\n const list = raw\n .split(',')\n .map((entry) => entry.trim())\n .filter(Boolean)\n return list.length > 0 ? list : undefined\n}\n","/**\n * @experimental\n *\n * Trace context propagation for MCP subprocess.\n *\n * When the MCP server is launched as a child process by a sandbox harness,\n * the parent passes trace context via environment variables:\n *\n * TRACE_ID=<current-run-trace-id>\n * PARENT_SPAN_ID=<span-that-dispatched-the-delegation>\n *\n * The MCP server reads these at startup and uses them as the root of its\n * internal trace tree. All spans emitted by `runLoop` invocations inside\n * the MCP are children of the parent's delegation span.\n *\n * When these env vars are absent, the MCP generates a fresh trace root —\n * the server operates standalone without trace joining.\n */\n\nimport type { OtelExporter } from '../otel-export'\nimport { buildLoopOtelSpans, createOtelExporter } from '../otel-export'\nimport type { LoopTraceEmitter, LoopTraceEvent } from '../runtime/types'\n\nexport interface TraceContext {\n /** Trace id inherited from the parent process, or a fresh one. */\n traceId: string\n /** Parent span id from the delegation that launched this MCP server. */\n parentSpanId?: string\n}\n\n/**\n * Read trace context from the process environment.\n * Returns a context with inherited ids or a freshly generated root.\n */\nexport function readTraceContextFromEnv(): TraceContext {\n const traceId = process.env.TRACE_ID || generateTraceId()\n const parentSpanId = process.env.PARENT_SPAN_ID || undefined\n return { traceId, parentSpanId }\n}\n\n/**\n * Create a LoopTraceEmitter that:\n * 1. Parents all spans under the inherited PARENT_SPAN_ID.\n * 2. Exports spans to OTEL when OTEL_EXPORTER_OTLP_ENDPOINT is set.\n *\n * Returns both the emitter and the optional exporter handle for shutdown.\n */\nexport function createPropagatingTraceEmitter(ctx: TraceContext): {\n emitter: LoopTraceEmitter\n exporter: OtelExporter | undefined\n context: TraceContext\n} {\n const exporter = createOtelExporter()\n\n // Buffer events per loop run, then emit the full nested span tree on\n // `loop.ended` so the topology hierarchy (loop → round → branch) reaches the\n // OTLP collector — not a flat list of zero-duration point spans. A run that\n // never reaches `loop.ended` (hard abort) drops its buffer; acceptable for\n // the short-lived MCP subprocess.\n const buffers = new Map<string, LoopTraceEvent[]>()\n\n const emitter: LoopTraceEmitter = {\n emit(event: LoopTraceEvent) {\n if (!exporter) return\n const buf = buffers.get(event.runId)\n if (buf) buf.push(event)\n else buffers.set(event.runId, [event])\n if (event.kind === 'loop.ended') {\n const events = buffers.get(event.runId) ?? [event]\n buffers.delete(event.runId)\n for (const span of buildLoopOtelSpans(events, ctx.traceId, ctx.parentSpanId)) {\n exporter.exportSpan(span)\n }\n }\n },\n }\n\n return { emitter, exporter, context: ctx }\n}\n\n/**\n * Build env vars to pass to a child MCP subprocess so it inherits the\n * current trace context.\n */\nexport function traceContextToEnv(ctx: TraceContext): Record<string, string> {\n const env: Record<string, string> = { TRACE_ID: ctx.traceId }\n if (ctx.parentSpanId) env.PARENT_SPAN_ID = ctx.parentSpanId\n return env\n}\n\nfunction generateTraceId(): string {\n const bytes = new Uint8Array(16)\n if (typeof globalThis.crypto?.getRandomValues === 'function') {\n globalThis.crypto.getRandomValues(bytes)\n } else {\n for (let i = 0; i < 16; i++) bytes[i] = Math.floor(Math.random() * 256)\n }\n return Array.from(bytes)\n .map((b) => b.toString(16).padStart(2, '0'))\n .join('')\n}\n"],"mappings":"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","names":[]}
|
|
@@ -5,7 +5,7 @@ import {
|
|
|
5
5
|
parseDetachedSessionRef,
|
|
6
6
|
runDetachedTurn,
|
|
7
7
|
runLoop
|
|
8
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-RLDUT4JL.js";
|
|
9
9
|
import {
|
|
10
10
|
coderProfile,
|
|
11
11
|
multiHarnessCoderFanout
|
|
@@ -95,7 +95,8 @@ function createDefaultCoderDelegate(options) {
|
|
|
95
95
|
const { agentRunSpec, output, validator } = coderProfile({
|
|
96
96
|
task,
|
|
97
97
|
...options.harness ? { harness: options.harness } : {},
|
|
98
|
-
...options.model ? { model: options.model } : {}
|
|
98
|
+
...options.model ? { model: options.model } : {},
|
|
99
|
+
...options.systemPrompt ? { systemPrompt: options.systemPrompt } : {}
|
|
99
100
|
});
|
|
100
101
|
if (ctx.detachedSessionRef !== void 0 && ctx.updateDetachedSessionRef) {
|
|
101
102
|
const { sessionId } = parseDetachedSessionRef(ctx.detachedSessionRef);
|
|
@@ -278,4 +279,4 @@ export {
|
|
|
278
279
|
coderTaskFromArgs,
|
|
279
280
|
settleDetachedCoderTurn
|
|
280
281
|
};
|
|
281
|
-
//# sourceMappingURL=chunk-
|
|
282
|
+
//# sourceMappingURL=chunk-WFEQCRQP.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/mcp/executor.ts","../src/mcp/delegates.ts"],"sourcesContent":["/**\n * @experimental\n *\n * Delegation executors — the layer between MCP delegates and the sandbox\n * substrate. Each executor exposes a {@link SandboxClient} the kernel\n * consumes plus a placement tag so the trace pipeline can correlate workers\n * with their physical placement.\n *\n * Two implementations ship in-box:\n *\n * - {@link createSiblingSandboxExecutor} — every delegation spawns a fresh\n * sandbox sibling to the caller. Default when the MCP server runs as a\n * standalone CLI mounted outside a fleet.\n *\n * - {@link createFleetWorkspaceExecutor} — delegations dispatch onto machines\n * in the caller's existing fleet so worker diffs land directly on the\n * caller's filesystem (the fleet's shared workspace). Selected when the\n * parent sandbox passes `TANGLE_FLEET_ID` into the MCP server's env.\n */\n\nimport type { CreateSandboxOptions, SandboxInstance } from '@tangle-network/sandbox'\nimport type { LoopSandboxPlacement, SandboxClient } from '../runtime'\n\n/** @experimental */\nexport interface DelegationExecutor {\n /** Sandbox client the kernel calls. Returned with `describePlacement` set. */\n readonly client: SandboxClient\n /** Best-effort one-liner used in stderr boot logs and diagnostics. */\n describe(): string\n /**\n * Where delegated work physically runs. `sibling` and `fleet` placements are\n * session-backed (boxes expose `driveTurn`, so detached dispatch + resume\n * apply); `in-process` spawns local harness CLIs with no sandbox session to\n * detach. Optional so consumer-implemented executors stay source-compatible;\n * absent means \"unknown\" and detached dispatch is not enabled for it.\n */\n readonly placement?: 'sibling' | 'fleet' | 'in-process'\n}\n\n/** @experimental */\nexport interface SiblingSandboxExecutorOptions {\n client: SandboxClient\n}\n\n/**\n * Wrap a raw sandbox SDK client so the kernel emits\n * `loop.iteration.dispatch` events with `{ placement: 'sibling', sandboxId }`.\n *\n * The returned client `.create()` delegates to the underlying client; the\n * only added behavior is a `describePlacement` tag the kernel reads.\n *\n * @experimental\n */\nexport function createSiblingSandboxExecutor(\n options: SiblingSandboxExecutorOptions,\n): DelegationExecutor {\n const underlying = options.client\n const client: SandboxClient = {\n create(opts?: CreateSandboxOptions): Promise<SandboxInstance> {\n return underlying.create(opts)\n },\n describePlacement(box: SandboxInstance): LoopSandboxPlacement {\n return { kind: 'sibling', sandboxId: readId(box) }\n },\n }\n return {\n client,\n placement: 'sibling',\n describe(): string {\n return 'sibling-sandbox (each delegation = fresh sandbox via client.create)'\n },\n }\n}\n\n/**\n * Minimal `SandboxFleet` surface the fleet executor calls. Declared\n * structurally so tests can pass an in-memory stub without instantiating the\n * sandbox SDK.\n *\n * @experimental\n */\nexport interface FleetHandle {\n readonly fleetId: string\n /** Machine ids in dispatch-eligible order. The executor round-robins. */\n readonly ids: ReadonlyArray<string>\n /** Resolve a machine id to its `SandboxInstance` — that machine is mounted\n * on the fleet's shared workspace, so any diff the worker writes lands on\n * every other fleet machine's filesystem too. */\n sandbox(machineId: string): Promise<SandboxInstance>\n}\n\n/** @experimental */\nexport interface FleetWorkspaceExecutorOptions {\n fleet: FleetHandle\n /**\n * Override the machine-selection policy. Default = round-robin across\n * `fleet.ids`, skipping the optional `excludeMachineIds` set (typically the\n * coordinator machine the MCP server is running on).\n */\n selectMachine?: (call: { callIndex: number; ids: ReadonlyArray<string> }) => string\n /**\n * Machine ids to skip during default round-robin. Set to the caller's own\n * machineId so workers don't compete with the orchestrator on the same VM.\n */\n excludeMachineIds?: ReadonlyArray<string>\n}\n\n/**\n * Build an executor that resolves each delegated iteration to an existing\n * machine in `fleet`. The fleet's shared-workspace policy means the worker\n * machine sees the caller's filesystem — diffs land in-place with no\n * cross-sandbox copy step.\n *\n * @experimental\n */\nexport function createFleetWorkspaceExecutor(\n options: FleetWorkspaceExecutorOptions,\n): DelegationExecutor {\n const fleet = options.fleet\n const exclude = new Set(options.excludeMachineIds ?? [])\n let callIndex = 0\n // machineId-by-sandboxId, populated as we resolve machines so\n // `describePlacement` can recover the assignment from the SandboxInstance\n // the kernel hands back.\n const placementBySandboxId = new Map<string, { machineId: string }>()\n\n const client: SandboxClient = {\n async create(): Promise<SandboxInstance> {\n const ids = fleet.ids.filter((id) => !exclude.has(id))\n if (ids.length === 0) {\n throw new Error(\n `agent-runtime: fleet ${fleet.fleetId} has no eligible worker machines (ids=[${fleet.ids.join(',')}], excluded=[${[...exclude].join(',')}])`,\n )\n }\n const selector = options.selectMachine\n const machineId = selector ? selector({ callIndex, ids }) : ids[callIndex % ids.length]\n callIndex += 1\n if (typeof machineId !== 'string' || machineId.length === 0) {\n throw new Error('agent-runtime: fleet executor selectMachine returned an empty machine id')\n }\n const box = await fleet.sandbox(machineId)\n const sandboxId = readId(box)\n if (sandboxId) placementBySandboxId.set(sandboxId, { machineId })\n return box\n },\n describePlacement(box: SandboxInstance): LoopSandboxPlacement {\n const sandboxId = readId(box)\n const recorded = sandboxId ? placementBySandboxId.get(sandboxId) : undefined\n return {\n kind: 'fleet',\n sandboxId,\n fleetId: fleet.fleetId,\n machineId: recorded?.machineId,\n }\n },\n }\n\n return {\n client,\n placement: 'fleet',\n describe(): string {\n const excluded = exclude.size > 0 ? ` (excluded=[${[...exclude].join(',')}])` : ''\n return `fleet-workspace (fleetId=${fleet.fleetId}, machines=[${fleet.ids.join(',')}]${excluded})`\n },\n }\n}\n\nfunction readId(box: SandboxInstance): string | undefined {\n const raw = (box as unknown as { id?: unknown }).id\n return typeof raw === 'string' && raw.length > 0 ? raw : undefined\n}\n","/**\n * @experimental\n *\n * Delegate factories — the layer between MCP tool handlers and the\n * underlying `runLoop` runners.\n *\n * The MCP server is profile-agnostic: it owns the task queue + feedback\n * store + transport. Each `*Delegate` is the closure that the queue\n * invokes when a task runs. Consumers can override either delegate to\n * inject custom drivers, mocks, fleet-aware dispatchers, etc.\n *\n * The default coder delegate is wired here because we own\n * `coderProfile` / `multiHarnessCoderFanout`. The default researcher\n * delegate is **not** wired in this file — `agent-knowledge` cannot be\n * imported from `agent-runtime` without inducing a cycle. Consumers\n * pass `researcherDelegate` explicitly when constructing the server.\n */\n\nimport { type CoderOutput, coderProfile, multiHarnessCoderFanout } from '../profiles/coder'\nimport type { AgentRunSpec, Iteration, LoopTraceEmitter, SandboxClient } from '../runtime'\nimport { runLoop } from '../runtime'\nimport { composeLoopTraceEmitters } from './delegation-trace'\nimport {\n type DetachedTurn,\n detachedTurnEvents,\n formatDetachedSessionRef,\n parseDetachedSessionRef,\n runDetachedTurn,\n} from './detached-turn'\nimport { createSiblingSandboxExecutor, type DelegationExecutor } from './executor'\nimport type {\n CoderTask,\n DelegateCodeArgs,\n DelegateResearchArgs,\n DelegateUiAuditArgs,\n DelegationProgress,\n ResearchOutputShape,\n UiAuditorDelegationOutput,\n} from './types'\n\n/** @experimental */\nexport interface DelegateRunCtx {\n signal: AbortSignal\n report(progress: DelegationProgress): void\n /**\n * Detached-run resume key recorded on the queue record at submit time\n * (`formatDetachedSessionRef`). Present only when the submit path requested\n * detached dispatch — its presence is what routes a session-backed delegate\n * onto the `driveTurn` tick path instead of holding a stream.\n */\n detachedSessionRef?: string\n /** Rebind the record's resume key (e.g. once the sandbox id is known). */\n updateDetachedSessionRef?(ref: string): void\n /**\n * Per-delegation trace sink supplied by the queue — loop events emitted\n * here land on the delegation record as a compact span tree. Delegates\n * compose it with their configured OTEL emitter so both sinks observe\n * the same stream.\n */\n traceEmitter?: LoopTraceEmitter\n}\n\n/** @experimental */\nexport type CoderDelegate = (\n args: DelegateCodeArgs,\n ctx: DelegateRunCtx,\n) => Promise<import('../profiles/coder').CoderOutput>\n\n/** @experimental */\nexport type ResearcherDelegate = (\n args: DelegateResearchArgs,\n ctx: DelegateRunCtx,\n) => Promise<ResearchOutputShape>\n\n/**\n * UI-auditor delegate — fully consumer-injected. agent-runtime ships no\n * default factory because the inputs are workspace path + judge function\n * + (optionally) a `SandboxClient`, and the judge is the consumer's\n * model seam. See `createInProcessUiAuditClient` + `uiAuditorProfile` in\n * `@tangle-network/agent-runtime/profiles` for the canonical wiring.\n *\n * @experimental\n */\nexport type UiAuditorDelegate = (\n args: DelegateUiAuditArgs,\n ctx: DelegateRunCtx,\n) => Promise<UiAuditorDelegationOutput>\n\n/** @experimental Structured review verdict over a coder candidate. */\nexport interface CoderReview {\n /** Gate: only approved candidates are eligible to win. */\n approved: boolean\n /** Reviewer's recommendation — surfaced in traces. */\n recommendation: 'ship' | 'approve-with-nits' | 'changes-requested' | 'reject'\n /** Readiness 0..1, used by the `highest-readiness` winner-selection strategy. */\n readiness: number\n notes?: string\n}\n\n/**\n * @experimental\n *\n * Optional adversarial reviewer over a coder candidate that already passed\n * mechanical validation (tests/typecheck/forbidden/diff/no-op/secrets). Folded\n * from the ai-trading-blueprint delegation MCP: a candidate is only eligible to\n * win if the reviewer approves it. The reviewer is the consumer's seam — an LLM\n * judge, a `pnpm review` command, anything returning a `CoderReview`.\n */\nexport type CoderReviewer = (\n output: import('../profiles/coder').CoderOutput,\n task: CoderTask,\n ctx: { signal: AbortSignal },\n) => Promise<CoderReview> | CoderReview\n\n/**\n * @experimental Winner-selection strategy among validated (+ reviewed)\n * candidates. `highest-readiness` requires a `reviewer`. Default `highest-score`\n * (the kernel's behavior — preserves backward compatibility).\n */\nexport type CoderWinnerSelection =\n | 'highest-score'\n | 'smallest-diff'\n | 'highest-readiness'\n | 'first-approved'\n\n/** @experimental */\nexport interface CreateDefaultCoderDelegateOptions {\n /**\n * Execution placement. Pass a {@link DelegationExecutor} (sibling or fleet)\n * to control where worker iterations land. `sandboxClient` is a\n * convenience shorthand that wraps the client in a sibling executor — pass\n * one or the other, not both.\n */\n executor?: DelegationExecutor\n /**\n * Convenience shorthand for sibling placement. Equivalent to\n * `executor: createSiblingSandboxExecutor({ client: sandboxClient })`.\n */\n sandboxClient?: SandboxClient\n /** Backend harness for the single-coder path. Default comes from `coderProfile`. */\n harness?: string\n /** Model override for the single-coder path. */\n model?: string\n /**\n * The worker's authored system prompt (§1.5). Flows onto `coderProfile`'s\n * `profile.prompt.systemPrompt` → through `runLoop` → the executor's `harnessInvocation`, so the\n * harness runs under this stance, not just the default coder prompt. Omit to keep the default.\n */\n systemPrompt?: string\n /** Default `['claude-code', 'codex', 'opencode/zai-coding-plan/glm-5.1']` when variants > 1. */\n fanoutHarnesses?: string[]\n /** Optional per-harness model override for `variants > 1`. */\n fanoutModels?: (string | undefined)[]\n /** Hard cap on the kernel's per-batch concurrency. Default 4. */\n maxConcurrency?: number\n /**\n * Optional adversarial reviewer. When set, a candidate must pass mechanical\n * validation AND `reviewer.approved` to be eligible to win — empty/secret/\n * test-failing patches are already gone; this catches the \"compiles + passes\n * but wrong/unsafe\" class the deterministic validator can't see.\n */\n reviewer?: CoderReviewer\n /** Winner-selection strategy among eligible candidates. Default `highest-score`. */\n winnerSelection?: CoderWinnerSelection\n /**\n * Loop trace emitter forwarded into every delegated `runLoop`. Wire\n * `createPropagatingTraceEmitter(readTraceContextFromEnv())` here (the bin\n * does) so delegated build-loops export their topology spans to the OTLP /\n * Tangle Intelligence sink when `OTEL_EXPORTER_OTLP_ENDPOINT` is set — and\n * are a cheap no-op when it isn't. Configurable by construction.\n *\n * Detached single-variant turns (taken when `ctx.detachedSessionRef` is set)\n * bypass `runLoop`; `runDetachedTurn` synthesizes a single-iteration loop\n * event stream for them so this emitter observes detached work too.\n */\n traceEmitter?: LoopTraceEmitter\n /** Tick cadence (ms) for the detached single-variant path. Default 5000. */\n detachedTickIntervalMs?: number\n /** Wall-clock cap (ms) forwarded to `driveTurn` for detached turns. */\n detachedWallCapMs?: number\n}\n\n/**\n * Build a coder delegate that drives `runLoop` against the project's\n * sandbox client + coder profile. When `args.variants > 1` it switches\n * to the multi-harness fanout topology.\n *\n * This is the SANDBOX-SESSION coder path: workers run the in-box harness via the\n * `SandboxClient`'s `streamPrompt`, and single-variant turns can dispatch DETACHED\n * (driveTurn ticks) so a durable queue resumes them across an MCP restart — a substrate\n * the recursive worktree-CLI leaf does not yet have a journal-replay equivalent for.\n *\n * For NEW local-repo coding, `worktreeCoderFanout` is a generic alternative (author an\n * `AgentProfile` per harness → `createWorktreeCliExecutor` leaves → `gateOnDeliverable`). This\n * factory remains first-class: it owns the sandbox-session + detached-resume substrate that the\n * worktree-CLI leaf does not yet replicate.\n *\n * @experimental\n */\nexport function createDefaultCoderDelegate(\n options: CreateDefaultCoderDelegateOptions,\n): CoderDelegate {\n const executor = resolveExecutor(options)\n const sandboxClient = executor.client\n const fanoutHarnesses = options.fanoutHarnesses\n const maxConcurrency = options.maxConcurrency ?? 4\n const traceEmitter = options.traceEmitter\n return async (args, ctx) => {\n const task = coderTaskFromArgs(args)\n const variants = Math.max(1, Math.trunc(args.variants ?? 1))\n const loopEmitter = composeLoopTraceEmitters(traceEmitter, ctx.traceEmitter)\n ctx.report({ iteration: 0, phase: 'starting' })\n if (variants <= 1) {\n const { agentRunSpec, output, validator } = coderProfile({\n task,\n ...(options.harness ? { harness: options.harness } : {}),\n ...(options.model ? { model: options.model } : {}),\n ...(options.systemPrompt ? { systemPrompt: options.systemPrompt } : {}),\n })\n // Detached dispatch: one session on one box, driven by `driveTurn` ticks\n // instead of a held stream, so the run survives an MCP-process restart\n // (the resume driver re-attaches via the persisted ref). Only the\n // single-variant path detaches — fanout needs N sessions + winner\n // selection over every candidate, which one resume key cannot express.\n if (ctx.detachedSessionRef !== undefined && ctx.updateDetachedSessionRef) {\n const { sessionId } = parseDetachedSessionRef(ctx.detachedSessionRef)\n const rebind = ctx.updateDetachedSessionRef\n const turn = await runDetachedTurn({\n client: sandboxClient,\n spec: agentRunSpec as AgentRunSpec<unknown>,\n prompt: agentRunSpec.taskToPrompt(task),\n sessionId,\n bindSandbox: (sandboxId) => rebind(formatDetachedSessionRef({ sandboxId, sessionId })),\n signal: ctx.signal,\n report: ctx.report,\n ...(loopEmitter ? { traceEmitter: loopEmitter } : {}),\n ...(executor.placement === 'fleet' ? { placement: 'fleet' as const } : {}),\n ...(options.detachedTickIntervalMs !== undefined\n ? { tickIntervalMs: options.detachedTickIntervalMs }\n : {}),\n ...(options.detachedWallCapMs !== undefined\n ? { wallCapMs: options.detachedWallCapMs }\n : {}),\n })\n const chosen = await settleDetachedCoderTurn(turn, {\n task,\n sessionId,\n signal: ctx.signal,\n ...(options.harness ? { harness: options.harness } : {}),\n ...(options.model ? { model: options.model } : {}),\n ...(options.reviewer ? { reviewer: options.reviewer } : {}),\n })\n ctx.report({ iteration: 1, phase: 'completed' })\n return chosen\n }\n const result = await runLoop({\n driver: singleShotDriver,\n agentRun: agentRunSpec,\n output,\n validator,\n task,\n ctx: {\n sandboxClient,\n signal: ctx.signal,\n ...(loopEmitter ? { traceEmitter: loopEmitter } : {}),\n },\n maxIterations: 1,\n maxConcurrency,\n })\n const chosen = await pickCoderWinner({\n iterations: result.iterations,\n reviewer: options.reviewer,\n selection: options.winnerSelection ?? 'highest-score',\n task,\n signal: ctx.signal,\n })\n if (!chosen) throw new Error(noWinnerMessage(options.reviewer))\n ctx.report({ iteration: 1, phase: 'completed' })\n return chosen\n }\n const fanout = multiHarnessCoderFanout({\n ...(fanoutHarnesses && fanoutHarnesses.length > 0\n ? { harnesses: fanoutHarnesses.slice(0, variants) }\n : {}),\n ...(options.fanoutModels ? { models: options.fanoutModels.slice(0, variants) } : {}),\n })\n const agentRuns = fanout.agentRuns.slice(0, variants)\n const result = await runLoop({\n driver: fanout.driver,\n agentRuns,\n output: fanout.output,\n validator: fanout.validator,\n task,\n ctx: {\n sandboxClient,\n signal: ctx.signal,\n ...(loopEmitter ? { traceEmitter: loopEmitter } : {}),\n },\n maxIterations: variants,\n maxConcurrency: Math.min(maxConcurrency, variants),\n })\n const chosen = await pickCoderWinner({\n iterations: result.iterations,\n reviewer: options.reviewer,\n selection: options.winnerSelection ?? 'highest-score',\n task,\n signal: ctx.signal,\n })\n if (!chosen) throw new Error(noWinnerMessage(options.reviewer))\n ctx.report({ iteration: agentRuns.length, phase: 'completed' })\n return chosen\n }\n}\n\ninterface PickCoderWinnerArgs {\n iterations: ReadonlyArray<Iteration<CoderTask, CoderOutput>>\n reviewer: CoderReviewer | undefined\n selection: CoderWinnerSelection\n task: CoderTask\n signal: AbortSignal\n}\n\ninterface CoderCandidate {\n index: number\n output: CoderOutput\n score: number\n readiness: number\n}\n\n/**\n * Pick the winning coder candidate from a finished loop's iterations:\n * 1. keep only mechanically-VALID candidates (the validator already gated\n * tests/typecheck/forbidden/diff/no-op/secrets),\n * 2. if a `reviewer` is wired, keep only those it APPROVES,\n * 3. select among survivors by the chosen strategy.\n * Returns `undefined` when nothing survives — the delegate fails loud.\n */\nasync function pickCoderWinner(args: PickCoderWinnerArgs): Promise<CoderOutput | undefined> {\n const valid: CoderCandidate[] = []\n for (const iter of args.iterations) {\n if (iter.output === undefined || iter.error || iter.verdict?.valid !== true) continue\n valid.push({\n index: iter.index,\n output: iter.output,\n score: iter.verdict.score ?? 0,\n readiness: iter.verdict.score ?? 0,\n })\n }\n if (valid.length === 0) return undefined\n\n let eligible = valid\n if (args.reviewer) {\n eligible = []\n for (const c of valid) {\n const review = await args.reviewer(c.output, args.task, { signal: args.signal })\n if (review.approved) eligible.push({ ...c, readiness: review.readiness })\n }\n if (eligible.length === 0) return undefined\n }\n\n return selectCoderCandidate(eligible, args.selection).output\n}\n\n/** Apply the winner-selection strategy; ties broken by earliest iteration. */\nfunction selectCoderCandidate(\n candidates: CoderCandidate[],\n selection: CoderWinnerSelection,\n): CoderCandidate {\n const diffLines = (c: CoderCandidate) =>\n c.output.diffStats.insertions + c.output.diffStats.deletions\n const sorted = [...candidates].sort((a, b) => {\n switch (selection) {\n case 'smallest-diff':\n return diffLines(a) - diffLines(b) || a.index - b.index\n case 'highest-readiness':\n return b.readiness - a.readiness || a.index - b.index\n case 'first-approved':\n return a.index - b.index\n default:\n return b.score - a.score || a.index - b.index\n }\n })\n return sorted[0]!\n}\n\nfunction noWinnerMessage(reviewer: CoderReviewer | undefined): string {\n return reviewer\n ? 'coder delegate: no candidate passed validation + review'\n : 'coder delegate: no candidate passed validation'\n}\n\n/**\n * Canonical `DelegateCodeArgs` → `CoderTask` mapping — the single source for\n * the delegate's live dispatch AND the resume driver's settle/message\n * rebuilding, so a resumed record reproduces exactly the task the original\n * process dispatched.\n *\n * @experimental\n */\nexport function coderTaskFromArgs(args: DelegateCodeArgs): CoderTask {\n return {\n goal: buildCoderGoal(args),\n repoRoot: args.repoRoot,\n testCmd: args.config?.testCmd,\n typecheckCmd: args.config?.typecheckCmd,\n forbiddenPaths: args.config?.forbiddenPaths,\n maxDiffLines: args.config?.maxDiffLines,\n }\n}\n\n/** @experimental */\nexport interface SettleDetachedCoderTurnOptions {\n task: CoderTask\n /** Session id of the detached turn — used as the synthesized event id. */\n sessionId: string\n signal: AbortSignal\n harness?: string\n model?: string\n /** Same gate as the streaming path: an unapproved candidate cannot win. */\n reviewer?: CoderReviewer\n}\n\n/**\n * Settle a completed detached coder turn through the same gate the streaming\n * path applies: parse the terminal payload with the coder output adapter,\n * run the mechanical validator (tests/typecheck/forbidden/diff/no-op/secrets),\n * then the optional reviewer. Throws when nothing survives — a resumed or\n * detached run must not return an unvalidated patch.\n *\n * SCOPE NOTE (detached/resume): the detached `driveTurn`-tick + cross-restart resume path is\n * bound to the `runLoop` + sandbox-session substrate. The recursive `Scope`/worktree-CLI leaf has\n * journal→replay but no driveTurn-over-a-detached-sandbox-session equivalent yet, so resume is NOT\n * advertised on the generic `worktreeCoderFanout` path. This helper (with `coderTaskFromArgs` and\n * `createDriveTurnResumeDriver`) stays as the resume seam `bin.ts` wires for in-flight records.\n *\n * @experimental\n */\nexport async function settleDetachedCoderTurn(\n turn: DetachedTurn,\n options: SettleDetachedCoderTurnOptions,\n): Promise<CoderOutput> {\n const { output, validator } = coderProfile({\n task: options.task,\n ...(options.harness ? { harness: options.harness } : {}),\n ...(options.model ? { model: options.model } : {}),\n })\n const parsed = output.parse(detachedTurnEvents(options.sessionId, turn))\n const verdict = await validator.validate(parsed, { iteration: 0, signal: options.signal })\n if (verdict.valid !== true) throw new Error(noWinnerMessage(options.reviewer))\n if (options.reviewer) {\n const review = await options.reviewer(parsed, options.task, { signal: options.signal })\n if (!review.approved) throw new Error(noWinnerMessage(options.reviewer))\n }\n return parsed\n}\n\nfunction buildCoderGoal(args: DelegateCodeArgs): string {\n if (!args.contextHint) return args.goal\n return [args.goal, '', '## Context', args.contextHint].join('\\n')\n}\n\nfunction resolveExecutor(options: CreateDefaultCoderDelegateOptions): DelegationExecutor {\n if (options.executor && options.sandboxClient) {\n throw new Error('createDefaultCoderDelegate: pass exactly one of `executor` or `sandboxClient`')\n }\n if (options.executor) return options.executor\n if (options.sandboxClient) {\n return createSiblingSandboxExecutor({ client: options.sandboxClient })\n }\n throw new Error('createDefaultCoderDelegate: `executor` or `sandboxClient` is required')\n}\n\n/**\n * Single-shot driver — plan one task on iteration 0, stop after one\n * iteration. Used by the coder delegate when `variants <= 1`. Keeps the\n * runLoop kernel-level accounting (timing, cost, trace emission) while\n * skipping fanout/refine topology overhead.\n */\nconst singleShotDriver = {\n name: 'mcp-single-shot',\n async plan<Task>(task: Task, history: ReadonlyArray<unknown>): Promise<Task[]> {\n return history.length === 0 ? [task] : []\n },\n decide(history: ReadonlyArray<unknown>): 'pick-winner' | 'fail' {\n return history.length > 0 ? 'pick-winner' : 'fail'\n },\n}\n"],"mappings":"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","names":["chosen","result"]}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { AgentProfile } from '@tangle-network/sandbox';
|
|
2
|
-
import { O as OutputAdapter, V as Validator, A as AgentRunSpec, D as Driver } from './types-
|
|
2
|
+
import { O as OutputAdapter, V as Validator, A as AgentRunSpec, D as Driver } from './types-CUzjRFZ3.js';
|
|
3
3
|
import { DefaultVerdict } from '@tangle-network/agent-eval';
|
|
4
4
|
|
|
5
5
|
/**
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { DefaultVerdict } from '@tangle-network/agent-eval';
|
|
2
2
|
import { AgentProfile, BackendType } from '@tangle-network/sandbox';
|
|
3
3
|
import { R as RuntimeHooks } from './runtime-hooks-C7JwKb9E.js';
|
|
4
|
-
import {
|
|
5
|
-
import { b as DelegateFeedbackArgs, c as DelegationFeedbackSnapshot, d as DelegationTaskQueue, e as CoderDelegate, R as ResearcherDelegate, U as UiAuditorDelegate, T as TraceContext } from './delegates-
|
|
4
|
+
import { e as LoopTokenUsage } from './types-CUzjRFZ3.js';
|
|
5
|
+
import { b as DelegateFeedbackArgs, c as DelegationFeedbackSnapshot, d as DelegationTaskQueue, e as CoderDelegate, R as ResearcherDelegate, U as UiAuditorDelegate, T as TraceContext } from './delegates-C94qchkz.js';
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
8
|
* @experimental
|
|
@@ -98,6 +98,15 @@ interface Executor<Out> {
|
|
|
98
98
|
verdict?: DefaultVerdict;
|
|
99
99
|
spent: Spend;
|
|
100
100
|
};
|
|
101
|
+
/**
|
|
102
|
+
* A driver-executor's OWN-inference subtree total (rolled up from its nested tree's `metered`
|
|
103
|
+
* events) — the parent scope journals it as a `metered` event for this node on settle, on BOTH
|
|
104
|
+
* the done AND the down/crash paths, so a crashed sub-driver's partial inference still re-homes
|
|
105
|
+
* (the pool already debited it via `observe`; the journal must match). NOT reconciled, so it never
|
|
106
|
+
* trips the reservation clamp. Read on settle, valid after `execute` resolves OR throws. Leaf
|
|
107
|
+
* executors omit it (returns `undefined`).
|
|
108
|
+
*/
|
|
109
|
+
metered?(): Spend | undefined;
|
|
101
110
|
}
|
|
102
111
|
/** Terminal artifact of a one-shot `Executor.execute`. */
|
|
103
112
|
interface ExecutorResult<Out> {
|
|
@@ -275,12 +284,30 @@ interface Scope<Out> {
|
|
|
275
284
|
* is a direct call; the sandbox/Agent-Bus transports surface the SAME verb as an MCP tool.
|
|
276
285
|
*/
|
|
277
286
|
send(nodeId: NodeId, msg: unknown): boolean;
|
|
287
|
+
/** This scope's abort signal — aborted when the run is cancelled, a breaker trips, the pool
|
|
288
|
+
* is exhausted, or a parent scope cascades. A long-running driver `act` over this scope reads
|
|
289
|
+
* it to break promptly (the conserved pool + driver-stop are the other bounds). A nested
|
|
290
|
+
* scope carries its own signal, chained off its driver child's abort. */
|
|
291
|
+
readonly signal: AbortSignal;
|
|
292
|
+
/**
|
|
293
|
+
* Meter the driver's OWN compute against the conserved pool — its inference turns, which are
|
|
294
|
+
* real tokens/usd but not a spawned child (no reserve/reconcile). A direct `free → committed`
|
|
295
|
+
* debit, so equal-k counts the driver's tokens AND the in-loop budget guard (`budget.tokensLeft`)
|
|
296
|
+
* halts a driver that thinks the pool dry. `detail` rides an `agent.turn` trace event for live
|
|
297
|
+
* observability (turn index, tool calls, cumulative spend). It also journals a `metered` event —
|
|
298
|
+
* the durable twin of the pool debit (as `settled` is the twin of `reconcile`) — so every
|
|
299
|
+
* journal-based cost reader (`spentFromJournal`, `trajectoryReport`) sums driver inference
|
|
300
|
+
* automatically. A leaf never calls this; a driver meters each chat turn and awaits it (the
|
|
301
|
+
* metered event is cost-critical, so it lands before the join-barrier roll-up).
|
|
302
|
+
*/
|
|
303
|
+
meter(spend: Spend, detail?: Record<string, unknown>): Promise<void>;
|
|
278
304
|
/** The live tree — reads the in-memory nursery, not the journal. */
|
|
279
305
|
readonly view: TreeView;
|
|
280
306
|
/** Conserved-pool readouts (post-reservation). */
|
|
281
307
|
readonly budget: Readonly<{
|
|
282
308
|
tokensLeft: number;
|
|
283
309
|
usdLeft: number;
|
|
310
|
+
usdCapped: boolean;
|
|
284
311
|
deadlineMs: number;
|
|
285
312
|
reservedTokens: number;
|
|
286
313
|
}>;
|
|
@@ -332,6 +359,19 @@ type SpawnEvent = {
|
|
|
332
359
|
reason: string;
|
|
333
360
|
seq: number;
|
|
334
361
|
at: string;
|
|
362
|
+
} | {
|
|
363
|
+
/** A driver's OWN inference spend, journaled separately from spawned-child work — the journal
|
|
364
|
+
* TWIN of `BudgetPool.observe`, exactly as `settled` is the twin of `reconcile`. So every
|
|
365
|
+
* journal-based cost reader sums it automatically — the journal is the single cost ledger.
|
|
366
|
+
* It carries spend only and is NOT a settlement: replay + `materializeTreeView` skip it for
|
|
367
|
+
* structure, and its `seq` lives outside the cursor-uniqueness namespace. A
|
|
368
|
+
* driver re-homes its nested subtree's metered total up to its parent (like settled spend),
|
|
369
|
+
* so summing any sub-tree root yields that sub-tree's true driver-inference cost. */
|
|
370
|
+
kind: 'metered';
|
|
371
|
+
id: NodeId;
|
|
372
|
+
spend: Spend;
|
|
373
|
+
seq: number;
|
|
374
|
+
at: string;
|
|
335
375
|
};
|
|
336
376
|
/**
|
|
337
377
|
* The spawn-tree event source (mirrors `ConversationJournal`'s begin/append/load shape).
|
|
@@ -392,6 +432,13 @@ type SupervisedResult<Out> = {
|
|
|
392
432
|
verdict?: DefaultVerdict;
|
|
393
433
|
tree: TreeView;
|
|
394
434
|
spentTotal: Spend;
|
|
435
|
+
/** Where `spentTotal` went: `driverInference` = the drivers' own chat turns (metered via
|
|
436
|
+
* `Scope.meter`); `childWork` = every spawned child's reconciled spend (the journal sum).
|
|
437
|
+
* `driverInference + childWork === spentTotal`. Present whenever any driver metered. */
|
|
438
|
+
spentBreakdown?: {
|
|
439
|
+
driverInference: Spend;
|
|
440
|
+
childWork: Spend;
|
|
441
|
+
};
|
|
395
442
|
} | {
|
|
396
443
|
kind: 'no-winner';
|
|
397
444
|
reason: 'all-children-down' | 'budget-exhausted' | 'aborted';
|
|
@@ -515,6 +562,75 @@ declare function captureWorktreeDiff(options: DiffOptions): Promise<DiffResult>;
|
|
|
515
562
|
/** @experimental */
|
|
516
563
|
declare function removeWorktree(options: RemoveWorktreeOptions): Promise<void>;
|
|
517
564
|
|
|
565
|
+
/**
|
|
566
|
+
* @experimental
|
|
567
|
+
*
|
|
568
|
+
* The child→parent message bus: the ONE pipe carrying every message a worker, sub-driver, or
|
|
569
|
+
* analyst sends up to the driver — settled outputs, questions, and trace-analyst findings. It
|
|
570
|
+
* unifies channels that were ad-hoc before (the settled-worker cursor, the ask-parent question
|
|
571
|
+
* channel, and analyst results) into a single typed primitive with two lanes:
|
|
572
|
+
*
|
|
573
|
+
* - PASS-THROUGH (`subscribe`): every published event reaches subscribers immediately — the
|
|
574
|
+
* express lane for online steering and live observation (a UI, a hook, the parent's box).
|
|
575
|
+
* - STANDBY (`pull`): events also queue so the driver consumes them on its own cadence. The queue
|
|
576
|
+
* is PRIORITY-ordered: a higher-`priority` event (a blocking question) is bumped ahead of
|
|
577
|
+
* queued settles/findings so the driver sees it first; ties resolve FIFO by publish order.
|
|
578
|
+
*
|
|
579
|
+
* Observability is first-class (A++): every event is stamped with a monotonic `seq` and wall-clock
|
|
580
|
+
* `at`, the full ordered `history()` is retained as an audit/replay trail, and `stats()` exposes
|
|
581
|
+
* published/pulled counts by kind. Subscribers receive the stamped record, not a bare event.
|
|
582
|
+
*
|
|
583
|
+
* The interface is transport-agnostic on purpose. Same box → this in-process queue. Cross box →
|
|
584
|
+
* the SAME publish/pull/subscribe surface backed by a durable mailbox on the parent's box (children
|
|
585
|
+
* POST events with at-least-once retry; payloads are blob refs so the event stays small). Consumers
|
|
586
|
+
* depend only on this interface, so distribution is a transport swap, never an architecture change.
|
|
587
|
+
*/
|
|
588
|
+
/** Every bus event is a discriminated union member keyed by `type`. */
|
|
589
|
+
interface BusEvent {
|
|
590
|
+
readonly type: string;
|
|
591
|
+
}
|
|
592
|
+
/** A published event stamped for ordering and observability. `seq` is the monotonic publish index;
|
|
593
|
+
* `priority` drives pull order (higher = bumped ahead); `at` is the wall-clock publish time (ms). */
|
|
594
|
+
interface BusRecord<E extends BusEvent> {
|
|
595
|
+
readonly seq: number;
|
|
596
|
+
readonly at: number;
|
|
597
|
+
readonly priority: number;
|
|
598
|
+
readonly event: E;
|
|
599
|
+
}
|
|
600
|
+
interface PublishOptions {
|
|
601
|
+
/** Higher = pulled ahead of lower-priority queued events (default 0). A blocking question sets
|
|
602
|
+
* this so it bumps to the front of the driver's inbox. */
|
|
603
|
+
readonly priority?: number;
|
|
604
|
+
/** Whether the event enters the pull queue (default true). Set `false` for record-only events —
|
|
605
|
+
* the parent→child down-leg (steer / answer / resume): they belong in `history()` and reach
|
|
606
|
+
* `subscribe` observers, but the parent must never `pull` its own outbound message back. */
|
|
607
|
+
readonly queue?: boolean;
|
|
608
|
+
}
|
|
609
|
+
interface BusStats {
|
|
610
|
+
readonly published: number;
|
|
611
|
+
readonly pulled: number;
|
|
612
|
+
/** Count published per event `type`. */
|
|
613
|
+
readonly byKind: Readonly<Record<string, number>>;
|
|
614
|
+
}
|
|
615
|
+
interface EventBus<E extends BusEvent> {
|
|
616
|
+
/** Stamp + queue the event, then deliver the stamped record to every subscriber in order.
|
|
617
|
+
* Returns the stamped record. */
|
|
618
|
+
publish(event: E, opts?: PublishOptions): Promise<BusRecord<E>>;
|
|
619
|
+
/** Remove and return the highest-priority QUEUED event whose type is in `kinds` (any if omitted),
|
|
620
|
+
* ties broken FIFO by `seq`; `undefined` when nothing matches. */
|
|
621
|
+
pull(kinds?: ReadonlyArray<E['type']>): E | undefined;
|
|
622
|
+
/** Register a pass-through handler; it receives the stamped record of every event published after
|
|
623
|
+
* registration. Returns an unsubscribe fn. */
|
|
624
|
+
subscribe(handler: (record: BusRecord<E>) => void | Promise<void>): () => void;
|
|
625
|
+
/** Count of queued, not-yet-pulled events (filtered by `kinds` when given). */
|
|
626
|
+
pending(kinds?: ReadonlyArray<E['type']>): number;
|
|
627
|
+
/** The full ordered log of every event ever published (the audit/replay trail). */
|
|
628
|
+
history(): ReadonlyArray<BusRecord<E>>;
|
|
629
|
+
/** Throughput counters for observability dashboards. */
|
|
630
|
+
stats(): BusStats;
|
|
631
|
+
}
|
|
632
|
+
declare function createEventBus<E extends BusEvent>(now?: () => number): EventBus<E>;
|
|
633
|
+
|
|
518
634
|
/**
|
|
519
635
|
* @experimental
|
|
520
636
|
*
|
|
@@ -706,7 +822,7 @@ declare function createInProcessTransport(): {
|
|
|
706
822
|
* them into any UI/report envelope it needs.
|
|
707
823
|
*/
|
|
708
824
|
|
|
709
|
-
/** A worker the driver has drained via `
|
|
825
|
+
/** A worker the driver has drained via `await_event`. */
|
|
710
826
|
interface SettledWorker {
|
|
711
827
|
readonly id: string;
|
|
712
828
|
readonly status: 'done' | 'down';
|
|
@@ -756,9 +872,38 @@ interface AnalystRegistry {
|
|
|
756
872
|
}>;
|
|
757
873
|
readonly run: (kindId: string, trace: unknown) => Promise<unknown>;
|
|
758
874
|
}
|
|
875
|
+
/** A trace-analyst result re-entered as a message on the bus (the `finding` event kind). */
|
|
876
|
+
interface AnalystFindingEvent {
|
|
877
|
+
readonly fromWorker: string;
|
|
878
|
+
readonly analyst: string;
|
|
879
|
+
readonly findings: unknown;
|
|
880
|
+
}
|
|
881
|
+
/** A parent→child message (the down-leg): recorded for observability, delivered via the child inbox,
|
|
882
|
+
* never pulled back by the parent. `delivered` mirrors whether the live child accepted it. */
|
|
883
|
+
interface DownMessageEvent {
|
|
884
|
+
readonly toWorker: string;
|
|
885
|
+
readonly instruction: string;
|
|
886
|
+
readonly delivered: boolean;
|
|
887
|
+
}
|
|
888
|
+
/** Every message on the one typed pipe. UP (child→parent): question / settled / finding — queued for
|
|
889
|
+
* the driver to `pull`. DOWN (parent→child): steer / answer — record-only (history + subscribers),
|
|
890
|
+
* routed to the child inbox. New kinds are additive. */
|
|
759
891
|
type CoordinationEvent = {
|
|
760
892
|
readonly type: 'question';
|
|
761
893
|
readonly question: QuestionRecord;
|
|
894
|
+
} | {
|
|
895
|
+
readonly type: 'settled';
|
|
896
|
+
readonly worker: SettledWorker;
|
|
897
|
+
} | {
|
|
898
|
+
readonly type: 'finding';
|
|
899
|
+
readonly finding: AnalystFindingEvent;
|
|
900
|
+
} | {
|
|
901
|
+
readonly type: 'steer';
|
|
902
|
+
readonly down: DownMessageEvent;
|
|
903
|
+
} | {
|
|
904
|
+
readonly type: 'answer';
|
|
905
|
+
readonly down: DownMessageEvent;
|
|
906
|
+
readonly questionId: string;
|
|
762
907
|
};
|
|
763
908
|
type MakeWorkerAgent = (profile: unknown) => Agent<unknown, unknown>;
|
|
764
909
|
interface CoordinationToolsOptions {
|
|
@@ -769,6 +914,11 @@ interface CoordinationToolsOptions {
|
|
|
769
914
|
readonly analysts?: AnalystRegistry;
|
|
770
915
|
readonly onEvent?: (event: CoordinationEvent) => void | Promise<void>;
|
|
771
916
|
readonly questionPolicy?: QuestionPolicy;
|
|
917
|
+
/** Analyst kind ids to run AUTOMATICALLY when a worker settles `done` (the analyst-on-settle
|
|
918
|
+
* hook). Each result is published as a `finding` event on the bus — pass-through to subscribers
|
|
919
|
+
* and queued for the driver to pull via `await_event`. Omit/empty = no auto-analysis (default;
|
|
920
|
+
* the driver can still run lenses on demand via `run_analyst`). Requires `analysts`. */
|
|
921
|
+
readonly analyzeOnSettle?: ReadonlyArray<string>;
|
|
772
922
|
}
|
|
773
923
|
interface CoordinationTools {
|
|
774
924
|
readonly tools: McpToolDescriptor[];
|
|
@@ -776,8 +926,18 @@ interface CoordinationTools {
|
|
|
776
926
|
stopReason(): string | undefined;
|
|
777
927
|
settled(): ReadonlyArray<SettledWorker>;
|
|
778
928
|
questions(): ReadonlyArray<QuestionRecord>;
|
|
929
|
+
/** The full ordered log of every bus event — UP (settled / question / finding) and DOWN
|
|
930
|
+
* (steer / answer) — the observability audit + replay trail. Each record carries seq,
|
|
931
|
+
* timestamp, and priority. */
|
|
932
|
+
history(): ReadonlyArray<BusRecord<CoordinationEvent>>;
|
|
933
|
+
/** Bus throughput counters (published / pulled / by-kind) for live dashboards. */
|
|
934
|
+
stats(): BusStats;
|
|
935
|
+
/** Raise a `finding` on the bus from outside the settle hook — the seam an ONLINE detector
|
|
936
|
+
* (mid-run, on the worker pipe) uses to tell the driver "this worker is looping/erroring" the
|
|
937
|
+
* moment it happens, instead of only at settle. Queued for `await_event` + pass-through. */
|
|
938
|
+
raiseFinding(finding: AnalystFindingEvent): Promise<void>;
|
|
779
939
|
}
|
|
780
940
|
/** Build the driver's MCP tools over a live scope. */
|
|
781
941
|
declare function createCoordinationTools(opts: CoordinationToolsOptions): CoordinationTools;
|
|
782
942
|
|
|
783
|
-
export { type
|
|
943
|
+
export { type EventBus as $, type AnalystRegistry as A, type Budget as B, type CoordinationEvent as C, type DiffOptions as D, type ExecutorRegistry as E, type FeedbackStore as F, type GitRunner as G, type RootHandle as H, InMemoryFeedbackStore as I, type JsonRpcMessage as J, type SupervisedResult as K, type Spend as L, type MakeWorkerAgent as M, type NodeId as N, type Scope as O, type ExecutorFactory as P, type Question as Q, type RemoveWorktreeOptions as R, type SettledWorker as S, type TreeView as T, type Executor as U, type UsageEvent as V, type WorktreeHandle as W, type Supervisor as X, type BusEvent as Y, type BusRecord as Z, type BusStats as _, type CoordinationTools as a, type ExecutorContext as a0, type ExecutorResult as a1, type Handle as a2, type NodeSnapshot as a3, type NodeStatus as a4, type PublishOptions as a5, type Restart as a6, type RootSignal as a7, type Runtime as a8, type SpawnOpts as a9, type SupervisorOpts as aa, type WidenGate as ab, createEventBus as ac, type CoordinationToolsOptions as b, type CreateWorktreeOptions as c, type DiffResult as d, type FeedbackEvent as e, type JsonRpcResponse as f, type McpServer as g, type McpServerOptions as h, type McpToolDescriptor as i, type McpTransport as j, type QuestionDecision as k, type QuestionPolicy as l, type QuestionRecord as m, captureWorktreeDiff as n, createCoordinationTools as o, createInProcessTransport as p, createMcpServer as q, createWorktree as r, eventToSnapshot as s, removeWorktree as t, type ResultBlobStore as u, type SpawnJournal as v, type SpawnEvent as w, type Settled as x, type AgentSpec as y, type Agent as z };
|