@tangle-network/agent-runtime 0.71.1 → 0.73.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/README.md +8 -8
- package/dist/agent.d.ts +27 -17
- package/dist/agent.js +21 -9
- package/dist/agent.js.map +1 -1
- package/dist/analyst-loop.d.ts +1 -1
- package/dist/analyst-loop.js +3 -2
- package/dist/{chunk-4KGQHS7U.js → chunk-7ODB76J5.js} +2 -18
- package/dist/chunk-7ODB76J5.js.map +1 -0
- package/dist/chunk-HPYWEFVY.js +4039 -0
- package/dist/chunk-HPYWEFVY.js.map +1 -0
- package/dist/{chunk-P5OKDSLB.js → chunk-IODKUOBA.js} +5 -3
- package/dist/{chunk-P5OKDSLB.js.map → chunk-IODKUOBA.js.map} +1 -1
- package/dist/chunk-NBV35BR6.js +68 -0
- package/dist/chunk-NBV35BR6.js.map +1 -0
- package/dist/{chunk-5ISW5JUF.js → chunk-NCH4XUZ7.js} +2004 -6551
- package/dist/chunk-NCH4XUZ7.js.map +1 -0
- package/dist/chunk-PBE35ULD.js +52 -0
- package/dist/chunk-PBE35ULD.js.map +1 -0
- package/dist/{chunk-VLF5RHEQ.js → chunk-T2HVQVB4.js} +1 -66
- package/dist/chunk-T2HVQVB4.js.map +1 -0
- package/dist/{chunk-K3RM4MPM.js → chunk-U56XGKVY.js} +9 -24
- package/dist/chunk-U56XGKVY.js.map +1 -0
- package/dist/chunk-UZ5SODU7.js +29 -0
- package/dist/chunk-UZ5SODU7.js.map +1 -0
- package/dist/{delegates-CsXJPZDH.d.ts → coordination-DU0saWeg.d.ts} +875 -1099
- package/dist/index.d.ts +7 -8
- package/dist/index.js +10 -12
- package/dist/index.js.map +1 -1
- package/dist/intelligence.d.ts +1 -1
- package/dist/lifecycle.d.ts +281 -0
- package/dist/lifecycle.js +174 -0
- package/dist/lifecycle.js.map +1 -0
- package/dist/{loop-runner-bin-DLM_bVQO.d.ts → loop-runner-bin-eD3m0rHW.d.ts} +10 -27
- package/dist/loop-runner-bin.d.ts +5 -6
- package/dist/loop-runner-bin.js +6 -6
- package/dist/loops.d.ts +47 -10
- package/dist/loops.js +47 -42
- package/dist/mcp/bin.js +17 -329
- package/dist/mcp/bin.js.map +1 -1
- package/dist/mcp/index.d.ts +10 -190
- package/dist/mcp/index.js +949 -47
- package/dist/mcp/index.js.map +1 -1
- package/dist/{openai-tools-kdCS-T12.d.ts → openai-tools-CBurv8Cu.d.ts} +11 -13
- package/dist/otel-export-BKmNwiCb.d.ts +180 -0
- package/dist/profiles.d.ts +3 -3
- package/dist/profiles.js +1 -5
- package/dist/profiles.js.map +1 -1
- package/dist/{substrate-BoRXgvka.d.ts → substrate-rNj6TDc3.d.ts} +7 -23
- package/dist/{types-CdnEAE3U.d.ts → types-JufmXF2a.d.ts} +80 -1
- package/dist/worktree-DaxOvw-C.d.ts +702 -0
- package/dist/{worktree-fanout-CK2ypmEm.d.ts → worktree-fanout-DIffZohV.d.ts} +15 -5
- package/package.json +6 -1
- package/dist/chunk-4KGQHS7U.js.map +0 -1
- package/dist/chunk-5ISW5JUF.js.map +0 -1
- package/dist/chunk-74UAWZXE.js +0 -278
- package/dist/chunk-74UAWZXE.js.map +0 -1
- package/dist/chunk-DVQGYDN5.js +0 -59
- package/dist/chunk-DVQGYDN5.js.map +0 -1
- package/dist/chunk-INXDNX2W.js +0 -475
- package/dist/chunk-INXDNX2W.js.map +0 -1
- package/dist/chunk-K3RM4MPM.js.map +0 -1
- package/dist/chunk-VLF5RHEQ.js.map +0 -1
- package/dist/chunk-XRYEZPR6.js +0 -88
- package/dist/chunk-XRYEZPR6.js.map +0 -1
- package/dist/coordination-BPQmuwv8.d.ts +0 -666
- package/dist/delegation-profile-Bvfro2m1.d.ts +0 -99
package/dist/mcp/index.js
CHANGED
|
@@ -1,41 +1,26 @@
|
|
|
1
1
|
import {
|
|
2
|
-
createInProcessExecutor,
|
|
3
2
|
createPropagatingTraceEmitter,
|
|
4
|
-
detectExecutor,
|
|
5
3
|
readTraceContextFromEnv,
|
|
6
4
|
traceContextToEnv
|
|
7
|
-
} from "../chunk-
|
|
5
|
+
} from "../chunk-PBE35ULD.js";
|
|
8
6
|
import {
|
|
9
7
|
mcpToolsForRuntimeMcp,
|
|
10
8
|
mcpToolsForRuntimeMcpSubset
|
|
11
|
-
} from "../chunk-
|
|
9
|
+
} from "../chunk-7ODB76J5.js";
|
|
12
10
|
import {
|
|
13
11
|
createKbGate
|
|
14
12
|
} from "../chunk-FNMGYYSS.js";
|
|
15
13
|
import {
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
settleDetachedCoderTurn
|
|
21
|
-
} from "../chunk-INXDNX2W.js";
|
|
22
|
-
import {
|
|
23
|
-
DELEGATION_MCP_SERVER_KEY,
|
|
24
|
-
buildDelegationMcpServer,
|
|
25
|
-
composeProductionAgentProfile
|
|
26
|
-
} from "../chunk-XRYEZPR6.js";
|
|
14
|
+
assertTraceDerivedFindings,
|
|
15
|
+
runCoderChecks,
|
|
16
|
+
selectValidWinner
|
|
17
|
+
} from "../chunk-HPYWEFVY.js";
|
|
27
18
|
import {
|
|
28
|
-
DELEGATE_CODE_DESCRIPTION,
|
|
29
|
-
DELEGATE_CODE_INPUT_SCHEMA,
|
|
30
|
-
DELEGATE_CODE_TOOL_NAME,
|
|
31
19
|
DELEGATE_DESCRIPTION,
|
|
32
20
|
DELEGATE_FEEDBACK_DESCRIPTION,
|
|
33
21
|
DELEGATE_FEEDBACK_INPUT_SCHEMA,
|
|
34
22
|
DELEGATE_FEEDBACK_TOOL_NAME,
|
|
35
23
|
DELEGATE_INPUT_SCHEMA,
|
|
36
|
-
DELEGATE_RESEARCH_DESCRIPTION,
|
|
37
|
-
DELEGATE_RESEARCH_INPUT_SCHEMA,
|
|
38
|
-
DELEGATE_RESEARCH_TOOL_NAME,
|
|
39
24
|
DELEGATE_TOOL_NAME,
|
|
40
25
|
DELEGATE_UI_AUDIT_DESCRIPTION,
|
|
41
26
|
DELEGATE_UI_AUDIT_INPUT_SCHEMA,
|
|
@@ -54,46 +39,976 @@ import {
|
|
|
54
39
|
FileDelegationStore,
|
|
55
40
|
InMemoryDelegationStore,
|
|
56
41
|
InMemoryFeedbackStore,
|
|
57
|
-
assertTraceDerivedFindings,
|
|
58
42
|
buildDelegationTraceSpans,
|
|
59
43
|
capDelegationTrace,
|
|
60
44
|
captureWorktreeDiff,
|
|
61
45
|
composeLoopTraceEmitters,
|
|
62
46
|
createCoordinationTools,
|
|
63
|
-
createDelegateCodeHandler,
|
|
64
47
|
createDelegateFeedbackHandler,
|
|
65
48
|
createDelegateHandler,
|
|
66
|
-
createDelegateResearchHandler,
|
|
67
49
|
createDelegateUiAuditHandler,
|
|
68
50
|
createDelegationHistoryHandler,
|
|
69
51
|
createDelegationStatusHandler,
|
|
70
52
|
createDelegationTraceCollector,
|
|
71
|
-
createDetachedTurnResumeDriver,
|
|
72
53
|
createInProcessTransport,
|
|
73
54
|
createMcpServer,
|
|
55
|
+
createSandboxForSpec,
|
|
74
56
|
createWorktree,
|
|
75
|
-
|
|
57
|
+
deleteBoxSafe,
|
|
76
58
|
eventToSnapshot,
|
|
77
|
-
formatDetachedSessionRef,
|
|
78
59
|
hashIdempotencyInput,
|
|
79
|
-
parseDetachedSessionRef,
|
|
80
60
|
removeWorktree,
|
|
81
|
-
runDetachedTurn,
|
|
82
61
|
runLocalHarness,
|
|
62
|
+
runLoop,
|
|
63
|
+
runWorktreeHarness,
|
|
64
|
+
sleep,
|
|
65
|
+
throwAbort,
|
|
66
|
+
throwIfAborted,
|
|
83
67
|
validateDelegateArgs,
|
|
84
|
-
validateDelegateCodeArgs,
|
|
85
68
|
validateDelegateFeedbackArgs,
|
|
86
|
-
validateDelegateResearchArgs,
|
|
87
69
|
validateDelegateUiAuditArgs,
|
|
88
70
|
validateDelegationHistoryArgs,
|
|
89
71
|
validateDelegationStatusArgs
|
|
90
|
-
} from "../chunk-
|
|
72
|
+
} from "../chunk-NCH4XUZ7.js";
|
|
91
73
|
import "../chunk-VMNEQHJR.js";
|
|
92
|
-
import "../chunk-
|
|
93
|
-
import
|
|
74
|
+
import "../chunk-T2HVQVB4.js";
|
|
75
|
+
import {
|
|
76
|
+
ValidationError
|
|
77
|
+
} from "../chunk-NBV35BR6.js";
|
|
78
|
+
import {
|
|
79
|
+
coderTaskToPrompt
|
|
80
|
+
} from "../chunk-UZ5SODU7.js";
|
|
94
81
|
import "../chunk-WIR4HOOJ.js";
|
|
95
82
|
import "../chunk-DGUM43GV.js";
|
|
96
83
|
|
|
84
|
+
// src/mcp/executor.ts
|
|
85
|
+
function createSiblingSandboxExecutor(options) {
|
|
86
|
+
const underlying = options.client;
|
|
87
|
+
const client = {
|
|
88
|
+
create(opts) {
|
|
89
|
+
return underlying.create(opts);
|
|
90
|
+
},
|
|
91
|
+
describePlacement(box) {
|
|
92
|
+
return { kind: "sibling", sandboxId: readId(box) };
|
|
93
|
+
}
|
|
94
|
+
};
|
|
95
|
+
return {
|
|
96
|
+
client,
|
|
97
|
+
placement: "sibling",
|
|
98
|
+
describe() {
|
|
99
|
+
return "sibling-sandbox (each delegation = fresh sandbox via client.create)";
|
|
100
|
+
}
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
function createFleetWorkspaceExecutor(options) {
|
|
104
|
+
const fleet = options.fleet;
|
|
105
|
+
const exclude = new Set(options.excludeMachineIds ?? []);
|
|
106
|
+
let callIndex = 0;
|
|
107
|
+
const placementBySandboxId = /* @__PURE__ */ new Map();
|
|
108
|
+
const client = {
|
|
109
|
+
async create() {
|
|
110
|
+
const ids = fleet.ids.filter((id) => !exclude.has(id));
|
|
111
|
+
if (ids.length === 0) {
|
|
112
|
+
throw new Error(
|
|
113
|
+
`agent-runtime: fleet ${fleet.fleetId} has no eligible worker machines (ids=[${fleet.ids.join(",")}], excluded=[${[...exclude].join(",")}])`
|
|
114
|
+
);
|
|
115
|
+
}
|
|
116
|
+
const selector = options.selectMachine;
|
|
117
|
+
const machineId = selector ? selector({ callIndex, ids }) : ids[callIndex % ids.length];
|
|
118
|
+
callIndex += 1;
|
|
119
|
+
if (typeof machineId !== "string" || machineId.length === 0) {
|
|
120
|
+
throw new Error("agent-runtime: fleet executor selectMachine returned an empty machine id");
|
|
121
|
+
}
|
|
122
|
+
const box = await fleet.sandbox(machineId);
|
|
123
|
+
const sandboxId = readId(box);
|
|
124
|
+
if (sandboxId) placementBySandboxId.set(sandboxId, { machineId });
|
|
125
|
+
return box;
|
|
126
|
+
},
|
|
127
|
+
describePlacement(box) {
|
|
128
|
+
const sandboxId = readId(box);
|
|
129
|
+
const recorded = sandboxId ? placementBySandboxId.get(sandboxId) : void 0;
|
|
130
|
+
return {
|
|
131
|
+
kind: "fleet",
|
|
132
|
+
sandboxId,
|
|
133
|
+
fleetId: fleet.fleetId,
|
|
134
|
+
machineId: recorded?.machineId
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
};
|
|
138
|
+
return {
|
|
139
|
+
client,
|
|
140
|
+
placement: "fleet",
|
|
141
|
+
describe() {
|
|
142
|
+
const excluded = exclude.size > 0 ? ` (excluded=[${[...exclude].join(",")}])` : "";
|
|
143
|
+
return `fleet-workspace (fleetId=${fleet.fleetId}, machines=[${fleet.ids.join(",")}]${excluded})`;
|
|
144
|
+
}
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
function readId(box) {
|
|
148
|
+
const raw = box.id;
|
|
149
|
+
return typeof raw === "string" && raw.length > 0 ? raw : void 0;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// src/mcp/in-process-executor.ts
|
|
153
|
+
import { randomUUID } from "crypto";
|
|
154
|
+
var DEFAULT_HARNESS_TIMEOUT_MS = 5 * 60 * 1e3;
|
|
155
|
+
var DEFAULT_POSTCHECK_TIMEOUT_MS = 2 * 60 * 1e3;
|
|
156
|
+
function createInProcessExecutor(options) {
|
|
157
|
+
const harnesses = options.harnesses && options.harnesses.length > 0 ? [...options.harnesses] : ["claude"];
|
|
158
|
+
const runPostCheck = options.runPostCheck ?? defaultRunPostCheck;
|
|
159
|
+
const runCommand = async ({
|
|
160
|
+
command,
|
|
161
|
+
cwd,
|
|
162
|
+
signal
|
|
163
|
+
}) => {
|
|
164
|
+
try {
|
|
165
|
+
const r = await runPostCheck(command, cwd, signal);
|
|
166
|
+
return { exitCode: r.exitCode, output: r.stderr || r.stdout };
|
|
167
|
+
} catch (err) {
|
|
168
|
+
return { exitCode: -1, output: err instanceof Error ? err.message : String(err) };
|
|
169
|
+
}
|
|
170
|
+
};
|
|
171
|
+
let callIndex = 0;
|
|
172
|
+
const client = {
|
|
173
|
+
async create(opts) {
|
|
174
|
+
const runId = randomUUID();
|
|
175
|
+
const harness = harnesses[callIndex % harnesses.length];
|
|
176
|
+
callIndex += 1;
|
|
177
|
+
const profile = opts?.backend?.profile ?? { name: `in-process-${harness}` };
|
|
178
|
+
const virtual = {
|
|
179
|
+
id: `in-process-${runId}`,
|
|
180
|
+
__inProcess: { runId, harness },
|
|
181
|
+
async *streamPrompt(message, promptOpts) {
|
|
182
|
+
const taskPrompt = typeof message === "string" ? message : message.map(
|
|
183
|
+
(p) => typeof p === "object" && p && "text" in p ? String(p.text) : ""
|
|
184
|
+
).join("\n");
|
|
185
|
+
const run = await runWorktreeHarness({
|
|
186
|
+
repoRoot: options.repoRoot,
|
|
187
|
+
profile,
|
|
188
|
+
harness,
|
|
189
|
+
taskPrompt,
|
|
190
|
+
runId,
|
|
191
|
+
harnessTimeoutMs: options.harnessTimeoutMs ?? DEFAULT_HARNESS_TIMEOUT_MS,
|
|
192
|
+
checkTimeoutMs: options.postCheckTimeoutMs ?? DEFAULT_POSTCHECK_TIMEOUT_MS,
|
|
193
|
+
...options.testCmd !== void 0 ? { testCmd: options.testCmd } : {},
|
|
194
|
+
...options.typecheckCmd !== void 0 ? { typecheckCmd: options.typecheckCmd } : {},
|
|
195
|
+
...options.runGit ? { runGit: options.runGit } : {},
|
|
196
|
+
...options.runHarness ? { runHarness: options.runHarness } : {},
|
|
197
|
+
runCommand,
|
|
198
|
+
...promptOpts?.signal ? { signal: promptOpts.signal } : {}
|
|
199
|
+
});
|
|
200
|
+
this.__inProcess.worktree = run.worktree;
|
|
201
|
+
try {
|
|
202
|
+
yield {
|
|
203
|
+
type: "in_process.harness.started",
|
|
204
|
+
data: { runId, harness, worktreePath: run.worktree.path, command: harness }
|
|
205
|
+
};
|
|
206
|
+
const h = run.result.harness;
|
|
207
|
+
yield {
|
|
208
|
+
type: "in_process.harness.ended",
|
|
209
|
+
data: {
|
|
210
|
+
runId,
|
|
211
|
+
exitCode: h.exitCode,
|
|
212
|
+
durationMs: h.durationMs,
|
|
213
|
+
killedBySignal: h.killedBySignal,
|
|
214
|
+
timedOut: h.timedOut,
|
|
215
|
+
stdoutBytes: h.stdout.length,
|
|
216
|
+
stderrBytes: h.stderr.length
|
|
217
|
+
}
|
|
218
|
+
};
|
|
219
|
+
yield {
|
|
220
|
+
type: "result",
|
|
221
|
+
data: {
|
|
222
|
+
result: run.result,
|
|
223
|
+
source: "in-process-executor",
|
|
224
|
+
harness,
|
|
225
|
+
runId
|
|
226
|
+
}
|
|
227
|
+
};
|
|
228
|
+
} finally {
|
|
229
|
+
await run.cleanup();
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
};
|
|
233
|
+
return virtual;
|
|
234
|
+
},
|
|
235
|
+
describePlacement(box) {
|
|
236
|
+
const sandboxId = box.id;
|
|
237
|
+
const meta = box.__inProcess;
|
|
238
|
+
return {
|
|
239
|
+
kind: "sibling",
|
|
240
|
+
sandboxId,
|
|
241
|
+
worktreePath: meta?.worktree?.path,
|
|
242
|
+
harness: meta?.harness
|
|
243
|
+
};
|
|
244
|
+
}
|
|
245
|
+
};
|
|
246
|
+
return {
|
|
247
|
+
client,
|
|
248
|
+
placement: "in-process",
|
|
249
|
+
describe() {
|
|
250
|
+
return `in-process (repoRoot=${options.repoRoot}, harnesses=[${harnesses.join(",")}]${options.testCmd ? `, testCmd="${options.testCmd}"` : ""}${options.typecheckCmd ? `, typecheckCmd="${options.typecheckCmd}"` : ""})`;
|
|
251
|
+
}
|
|
252
|
+
};
|
|
253
|
+
}
|
|
254
|
+
async function defaultRunPostCheck(cmd, cwd, signal) {
|
|
255
|
+
const { spawn } = await import("child_process");
|
|
256
|
+
return new Promise((resolve, reject) => {
|
|
257
|
+
const child = spawn("sh", ["-c", cmd], { cwd, stdio: "pipe" });
|
|
258
|
+
let stdout = "";
|
|
259
|
+
let stderr = "";
|
|
260
|
+
child.stdout?.on("data", (c) => {
|
|
261
|
+
stdout += String(c);
|
|
262
|
+
});
|
|
263
|
+
child.stderr?.on("data", (c) => {
|
|
264
|
+
stderr += String(c);
|
|
265
|
+
});
|
|
266
|
+
if (signal) {
|
|
267
|
+
const onAbort = () => {
|
|
268
|
+
if (!child.killed) child.kill("SIGTERM");
|
|
269
|
+
};
|
|
270
|
+
if (signal.aborted) onAbort();
|
|
271
|
+
else signal.addEventListener("abort", onAbort, { once: true });
|
|
272
|
+
}
|
|
273
|
+
const killTimer = setTimeout(() => {
|
|
274
|
+
if (!child.killed) child.kill("SIGTERM");
|
|
275
|
+
}, DEFAULT_POSTCHECK_TIMEOUT_MS);
|
|
276
|
+
if (typeof killTimer.unref === "function") {
|
|
277
|
+
;
|
|
278
|
+
killTimer.unref();
|
|
279
|
+
}
|
|
280
|
+
child.on("error", (err) => {
|
|
281
|
+
clearTimeout(killTimer);
|
|
282
|
+
reject(err);
|
|
283
|
+
});
|
|
284
|
+
child.on("close", (code) => {
|
|
285
|
+
clearTimeout(killTimer);
|
|
286
|
+
resolve({ exitCode: code ?? -1, stdout, stderr });
|
|
287
|
+
});
|
|
288
|
+
});
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
// src/mcp/bin-helpers.ts
|
|
292
|
+
async function detectExecutor(args) {
|
|
293
|
+
const env = args.env ?? process.env;
|
|
294
|
+
if (env.AGENT_RUNTIME_IN_SANDBOX === "1") {
|
|
295
|
+
const repoRoot = env.AGENT_RUNTIME_REPO_ROOT?.trim();
|
|
296
|
+
if (!repoRoot) {
|
|
297
|
+
throw new Error(
|
|
298
|
+
"agent-runtime-mcp: AGENT_RUNTIME_IN_SANDBOX=1 requires AGENT_RUNTIME_REPO_ROOT to point at the workspace root"
|
|
299
|
+
);
|
|
300
|
+
}
|
|
301
|
+
return createInProcessExecutor({
|
|
302
|
+
repoRoot,
|
|
303
|
+
harnesses: parseHarnesses(env.AGENT_RUNTIME_LOCAL_HARNESSES),
|
|
304
|
+
testCmd: env.AGENT_RUNTIME_TEST_CMD?.trim() || void 0,
|
|
305
|
+
typecheckCmd: env.AGENT_RUNTIME_TYPECHECK_CMD?.trim() || void 0
|
|
306
|
+
});
|
|
307
|
+
}
|
|
308
|
+
const fleetId = parseFleetId(env.TANGLE_FLEET_ID);
|
|
309
|
+
if (!fleetId) {
|
|
310
|
+
return createSiblingSandboxExecutor({ client: args.sandboxClient });
|
|
311
|
+
}
|
|
312
|
+
const resolveFleet = args.resolveFleet ?? defaultResolveFleet;
|
|
313
|
+
const fleet = await resolveFleet(args.sandboxClient, fleetId);
|
|
314
|
+
const excludeMachineIds = parseList(env.TANGLE_FLEET_EXCLUDE_MACHINES);
|
|
315
|
+
return createFleetWorkspaceExecutor({
|
|
316
|
+
fleet,
|
|
317
|
+
excludeMachineIds
|
|
318
|
+
});
|
|
319
|
+
}
|
|
320
|
+
var KNOWN_HARNESSES = ["claude", "codex", "opencode"];
|
|
321
|
+
function parseHarnesses(raw) {
|
|
322
|
+
if (!raw) return void 0;
|
|
323
|
+
const parts = raw.split(",").map((s) => s.trim()).filter(Boolean);
|
|
324
|
+
if (parts.length === 0) return void 0;
|
|
325
|
+
for (const part of parts) {
|
|
326
|
+
if (!KNOWN_HARNESSES.includes(part)) {
|
|
327
|
+
throw new Error(
|
|
328
|
+
`agent-runtime-mcp: AGENT_RUNTIME_LOCAL_HARNESSES contains unknown harness "${part}". Expected: ${KNOWN_HARNESSES.join(", ")}.`
|
|
329
|
+
);
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
return parts;
|
|
333
|
+
}
|
|
334
|
+
async function defaultResolveFleet(sandboxClient, fleetId) {
|
|
335
|
+
const fleets = sandboxClient.fleets;
|
|
336
|
+
if (!fleets || typeof fleets.get !== "function") {
|
|
337
|
+
throw new Error(
|
|
338
|
+
"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."
|
|
339
|
+
);
|
|
340
|
+
}
|
|
341
|
+
const raw = await fleets.get(fleetId);
|
|
342
|
+
if (!raw || typeof raw !== "object") {
|
|
343
|
+
throw new Error(`agent-runtime-mcp: fleets.get(${fleetId}) returned no handle`);
|
|
344
|
+
}
|
|
345
|
+
const handle = raw;
|
|
346
|
+
if (typeof handle.fleetId !== "string" || !Array.isArray(handle.ids)) {
|
|
347
|
+
throw new Error(
|
|
348
|
+
`agent-runtime-mcp: fleet handle for ${fleetId} is missing fleetId/ids \u2014 incompatible sandbox SDK shape`
|
|
349
|
+
);
|
|
350
|
+
}
|
|
351
|
+
if (typeof handle.sandbox !== "function") {
|
|
352
|
+
throw new Error(
|
|
353
|
+
`agent-runtime-mcp: fleet handle for ${fleetId} is missing sandbox(machineId) \u2014 incompatible sandbox SDK shape`
|
|
354
|
+
);
|
|
355
|
+
}
|
|
356
|
+
return handle;
|
|
357
|
+
}
|
|
358
|
+
function parseFleetId(raw) {
|
|
359
|
+
if (typeof raw !== "string") return void 0;
|
|
360
|
+
const trimmed = raw.trim();
|
|
361
|
+
return trimmed.length > 0 ? trimmed : void 0;
|
|
362
|
+
}
|
|
363
|
+
function parseList(raw) {
|
|
364
|
+
if (!raw) return void 0;
|
|
365
|
+
const list = raw.split(",").map((entry) => entry.trim()).filter(Boolean);
|
|
366
|
+
return list.length > 0 ? list : void 0;
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
// src/mcp/detached-coder.ts
|
|
370
|
+
var DEFAULT_MAX_DIFF_LINES = 400;
|
|
371
|
+
function minimalCoderProfile() {
|
|
372
|
+
return { name: "coder" };
|
|
373
|
+
}
|
|
374
|
+
function coderRunProfile(options) {
|
|
375
|
+
const harness = options.harness ?? "claude-code";
|
|
376
|
+
const name = options.name ?? `coder-${harness}`;
|
|
377
|
+
const base = options.profile ?? minimalCoderProfile();
|
|
378
|
+
return {
|
|
379
|
+
...base,
|
|
380
|
+
name,
|
|
381
|
+
...options.systemPrompt ? { prompt: { systemPrompt: options.systemPrompt } } : {},
|
|
382
|
+
model: options.model ? { default: options.model } : base.model,
|
|
383
|
+
metadata: { ...base.metadata, backendType: harness }
|
|
384
|
+
};
|
|
385
|
+
}
|
|
386
|
+
function coderRunSpec(options = {}) {
|
|
387
|
+
return {
|
|
388
|
+
name: options.name ?? `coder-${options.harness ?? "claude-code"}`,
|
|
389
|
+
profile: coderRunProfile(options),
|
|
390
|
+
taskToPrompt: coderTaskToPrompt
|
|
391
|
+
};
|
|
392
|
+
}
|
|
393
|
+
var coderOutputAdapter = { parse: parseCoderEvents };
|
|
394
|
+
function multiHarnessCoderFanout(options = {}) {
|
|
395
|
+
const harnesses = options.harnesses && options.harnesses.length > 0 ? options.harnesses : ["claude-code", "codex", "opencode/zai-coding-plan/glm-5.1"];
|
|
396
|
+
const models = options.models ?? [];
|
|
397
|
+
const agentRuns = harnesses.map(
|
|
398
|
+
(harness, i) => coderRunSpec({
|
|
399
|
+
...options.profile ? { profile: options.profile } : {},
|
|
400
|
+
harness,
|
|
401
|
+
model: models[i]
|
|
402
|
+
})
|
|
403
|
+
);
|
|
404
|
+
const driver = {
|
|
405
|
+
name: "fanout",
|
|
406
|
+
plan: async (task, history) => history.length === 0 ? agentRuns.map(() => task) : [],
|
|
407
|
+
decide: (history) => history.some((i) => i.verdict?.valid === true) ? "pick-winner" : "fail"
|
|
408
|
+
};
|
|
409
|
+
return { agentRuns, output: coderOutputAdapter, validator: defaultCoderValidator(), driver };
|
|
410
|
+
}
|
|
411
|
+
function createCoderValidator(task) {
|
|
412
|
+
const constraints = {
|
|
413
|
+
maxDiffLines: task.maxDiffLines ?? DEFAULT_MAX_DIFF_LINES,
|
|
414
|
+
forbiddenPaths: task.forbiddenPaths ?? []
|
|
415
|
+
};
|
|
416
|
+
return {
|
|
417
|
+
async validate(output) {
|
|
418
|
+
return runCoderChecks(
|
|
419
|
+
{
|
|
420
|
+
patch: output.patch,
|
|
421
|
+
testsPassed: output.testResult.passed,
|
|
422
|
+
typecheckPassed: output.typecheckResult.passed
|
|
423
|
+
},
|
|
424
|
+
constraints
|
|
425
|
+
);
|
|
426
|
+
}
|
|
427
|
+
};
|
|
428
|
+
}
|
|
429
|
+
function defaultCoderValidator() {
|
|
430
|
+
return createCoderValidator({
|
|
431
|
+
goal: "",
|
|
432
|
+
repoRoot: "",
|
|
433
|
+
forbiddenPaths: [],
|
|
434
|
+
maxDiffLines: DEFAULT_MAX_DIFF_LINES
|
|
435
|
+
});
|
|
436
|
+
}
|
|
437
|
+
function parseCoderEvents(events) {
|
|
438
|
+
for (let i = events.length - 1; i >= 0; i -= 1) {
|
|
439
|
+
const event = events[i];
|
|
440
|
+
if (!event) continue;
|
|
441
|
+
const type = String(event.type ?? "");
|
|
442
|
+
const data = isRecord(event.data) ? event.data : {};
|
|
443
|
+
if (type === "result" || type === "final" || type === "coder.result") {
|
|
444
|
+
const payload = data.result ?? data.output ?? data;
|
|
445
|
+
const projected = projectWorktreeArtifact(payload);
|
|
446
|
+
if (projected) return projected;
|
|
447
|
+
const direct = coerceCoderOutput(payload);
|
|
448
|
+
if (direct) return direct;
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
const transcript = collectAssistantText(events);
|
|
452
|
+
for (const candidate of fencedJsonBlocks(transcript)) {
|
|
453
|
+
const coerced = coerceCoderOutput(candidate);
|
|
454
|
+
if (coerced) return coerced;
|
|
455
|
+
}
|
|
456
|
+
return {
|
|
457
|
+
branch: "",
|
|
458
|
+
patch: "",
|
|
459
|
+
testResult: { passed: false, output: "" },
|
|
460
|
+
typecheckResult: { passed: false, output: "" },
|
|
461
|
+
diffStats: { filesChanged: 0, insertions: 0, deletions: 0 }
|
|
462
|
+
};
|
|
463
|
+
}
|
|
464
|
+
function projectWorktreeArtifact(value) {
|
|
465
|
+
if (!isRecord(value)) return void 0;
|
|
466
|
+
const stats = value.stats;
|
|
467
|
+
if (!isRecord(stats)) return void 0;
|
|
468
|
+
const branch = pickString(value.branch) ?? "";
|
|
469
|
+
const patch = pickString(value.patch) ?? "";
|
|
470
|
+
const checks = isRecord(value.checks) ? value.checks : {};
|
|
471
|
+
const tests = isRecord(checks.tests) ? checks.tests : void 0;
|
|
472
|
+
const typecheck = isRecord(checks.typecheck) ? checks.typecheck : void 0;
|
|
473
|
+
const harness = isRecord(value.harness) ? value.harness : void 0;
|
|
474
|
+
const exitCode = harness ? toFiniteInt(harness.exitCode) : 0;
|
|
475
|
+
const timedOut = harness?.timedOut === true;
|
|
476
|
+
const harnessName = harness ? pickString(harness.name) ?? "harness" : "harness";
|
|
477
|
+
return {
|
|
478
|
+
branch,
|
|
479
|
+
patch,
|
|
480
|
+
testResult: {
|
|
481
|
+
passed: tests ? tests.passed === true : true,
|
|
482
|
+
output: tail(pickString(tests?.output) ?? "", 4e3)
|
|
483
|
+
},
|
|
484
|
+
typecheckResult: {
|
|
485
|
+
passed: typecheck ? typecheck.passed === true : true,
|
|
486
|
+
output: tail(pickString(typecheck?.output) ?? "", 4e3)
|
|
487
|
+
},
|
|
488
|
+
diffStats: {
|
|
489
|
+
filesChanged: toFiniteInt(stats.filesChanged),
|
|
490
|
+
insertions: toFiniteInt(stats.insertions),
|
|
491
|
+
deletions: toFiniteInt(stats.deletions)
|
|
492
|
+
},
|
|
493
|
+
...exitCode !== 0 ? {
|
|
494
|
+
reviewerNotes: `harness ${harnessName} exited ${exitCode}${timedOut ? " (timed out)" : ""}`
|
|
495
|
+
} : {}
|
|
496
|
+
};
|
|
497
|
+
}
|
|
498
|
+
function isRecord(value) {
|
|
499
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
500
|
+
}
|
|
501
|
+
function tail(text, max) {
|
|
502
|
+
return text.length <= max ? text : text.slice(text.length - max);
|
|
503
|
+
}
|
|
504
|
+
function pickString(value) {
|
|
505
|
+
return typeof value === "string" && value.length > 0 ? value : void 0;
|
|
506
|
+
}
|
|
507
|
+
function collectAssistantText(events) {
|
|
508
|
+
const chunks = [];
|
|
509
|
+
for (const event of events) {
|
|
510
|
+
if (!event) continue;
|
|
511
|
+
const data = isRecord(event.data) ? event.data : {};
|
|
512
|
+
if (String(event.type ?? "") === "message.part.updated") {
|
|
513
|
+
const part = isRecord(data.part) ? data.part : {};
|
|
514
|
+
const partType = String(part.type ?? "");
|
|
515
|
+
if (partType !== "text" && partType !== "") continue;
|
|
516
|
+
const text2 = pickString(data.delta) ?? pickString(part.text);
|
|
517
|
+
if (text2) chunks.push(text2);
|
|
518
|
+
continue;
|
|
519
|
+
}
|
|
520
|
+
const text = pickString(data.text) ?? pickString(data.delta);
|
|
521
|
+
if (text) chunks.push(text);
|
|
522
|
+
}
|
|
523
|
+
return chunks.join("");
|
|
524
|
+
}
|
|
525
|
+
function fencedJsonBlocks(text) {
|
|
526
|
+
const out = [];
|
|
527
|
+
const matches = [...text.matchAll(/```(?:json)?\s*([\s\S]*?)```/gi)];
|
|
528
|
+
for (let i = matches.length - 1; i >= 0; i -= 1) {
|
|
529
|
+
const body = (matches[i]?.[1] ?? "").trim();
|
|
530
|
+
if (!body) continue;
|
|
531
|
+
try {
|
|
532
|
+
out.push(JSON.parse(body));
|
|
533
|
+
} catch {
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
return out;
|
|
537
|
+
}
|
|
538
|
+
function coerceCoderOutput(value) {
|
|
539
|
+
if (!isRecord(value)) return void 0;
|
|
540
|
+
const branch = pickString(value.branch);
|
|
541
|
+
const patch = pickString(value.patch) ?? "";
|
|
542
|
+
if (branch === void 0) return void 0;
|
|
543
|
+
return {
|
|
544
|
+
branch,
|
|
545
|
+
patch,
|
|
546
|
+
testResult: coerceCmdResult(value.testResult),
|
|
547
|
+
typecheckResult: coerceCmdResult(value.typecheckResult),
|
|
548
|
+
diffStats: coerceDiffStats(value.diffStats),
|
|
549
|
+
reviewerNotes: pickString(value.reviewerNotes)
|
|
550
|
+
};
|
|
551
|
+
}
|
|
552
|
+
function coerceCmdResult(value) {
|
|
553
|
+
if (!isRecord(value)) return { passed: false, output: "" };
|
|
554
|
+
return { passed: value.passed === true, output: pickString(value.output) ?? "" };
|
|
555
|
+
}
|
|
556
|
+
function coerceDiffStats(value) {
|
|
557
|
+
if (!isRecord(value)) return { filesChanged: 0, insertions: 0, deletions: 0 };
|
|
558
|
+
return {
|
|
559
|
+
filesChanged: toFiniteInt(value.filesChanged),
|
|
560
|
+
insertions: toFiniteInt(value.insertions),
|
|
561
|
+
deletions: toFiniteInt(value.deletions)
|
|
562
|
+
};
|
|
563
|
+
}
|
|
564
|
+
function toFiniteInt(value) {
|
|
565
|
+
if (typeof value !== "number") return 0;
|
|
566
|
+
if (!Number.isFinite(value)) return 0;
|
|
567
|
+
return Math.max(0, Math.trunc(value));
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
// src/mcp/detached-turn.ts
|
|
571
|
+
var DEFAULT_TICK_INTERVAL_MS = 5e3;
|
|
572
|
+
function formatDetachedSessionRef(parts) {
|
|
573
|
+
assertRefComponent("sessionId", parts.sessionId);
|
|
574
|
+
if (parts.sandboxId === void 0) return `session=${parts.sessionId}`;
|
|
575
|
+
assertRefComponent("sandboxId", parts.sandboxId);
|
|
576
|
+
return `sandbox=${parts.sandboxId};session=${parts.sessionId}`;
|
|
577
|
+
}
|
|
578
|
+
function parseDetachedSessionRef(raw) {
|
|
579
|
+
const fields = /* @__PURE__ */ new Map();
|
|
580
|
+
for (const pair of raw.split(";")) {
|
|
581
|
+
const eq = pair.indexOf("=");
|
|
582
|
+
const key = eq === -1 ? "" : pair.slice(0, eq);
|
|
583
|
+
const value = eq === -1 ? "" : pair.slice(eq + 1);
|
|
584
|
+
if (key !== "session" && key !== "sandbox" || value.length === 0 || fields.has(key)) {
|
|
585
|
+
throw new ValidationError(
|
|
586
|
+
`parseDetachedSessionRef: malformed detachedSessionRef ${JSON.stringify(raw)} \u2014 expected "session=<id>" or "sandbox=<id>;session=<id>"`
|
|
587
|
+
);
|
|
588
|
+
}
|
|
589
|
+
fields.set(key, value);
|
|
590
|
+
}
|
|
591
|
+
const sessionId = fields.get("session");
|
|
592
|
+
if (!sessionId) {
|
|
593
|
+
throw new ValidationError(
|
|
594
|
+
`parseDetachedSessionRef: detachedSessionRef ${JSON.stringify(raw)} carries no session id`
|
|
595
|
+
);
|
|
596
|
+
}
|
|
597
|
+
const sandboxId = fields.get("sandbox");
|
|
598
|
+
return { sessionId, ...sandboxId !== void 0 ? { sandboxId } : {} };
|
|
599
|
+
}
|
|
600
|
+
function assertRefComponent(name, value) {
|
|
601
|
+
if (value.length === 0 || value.includes(";") || value.includes("=")) {
|
|
602
|
+
throw new ValidationError(
|
|
603
|
+
`formatDetachedSessionRef: ${name} ${JSON.stringify(value)} must be non-empty and free of ";" / "="`
|
|
604
|
+
);
|
|
605
|
+
}
|
|
606
|
+
}
|
|
607
|
+
function detachedTurnEvents(sessionId, turn) {
|
|
608
|
+
return [
|
|
609
|
+
{
|
|
610
|
+
type: "result",
|
|
611
|
+
id: sessionId,
|
|
612
|
+
data: {
|
|
613
|
+
text: turn.text,
|
|
614
|
+
finalText: turn.text,
|
|
615
|
+
success: true,
|
|
616
|
+
result: turn.result
|
|
617
|
+
}
|
|
618
|
+
}
|
|
619
|
+
];
|
|
620
|
+
}
|
|
621
|
+
async function runDetachedTurn(options) {
|
|
622
|
+
const intervalMs = options.tickIntervalMs ?? DEFAULT_TICK_INTERVAL_MS;
|
|
623
|
+
const trace = createDetachedTurnTrace(options);
|
|
624
|
+
trace.started();
|
|
625
|
+
const box = await createSandboxForSpec(options.client, options.spec, options.signal).catch(
|
|
626
|
+
(err) => {
|
|
627
|
+
trace.ended(err instanceof Error ? err.message : String(err));
|
|
628
|
+
throw err;
|
|
629
|
+
}
|
|
630
|
+
);
|
|
631
|
+
const drive = box;
|
|
632
|
+
const onAbort = () => {
|
|
633
|
+
void drive._sessionCancel?.(options.sessionId).catch(() => {
|
|
634
|
+
});
|
|
635
|
+
};
|
|
636
|
+
try {
|
|
637
|
+
if (typeof drive.driveTurn !== "function") {
|
|
638
|
+
throw new ValidationError(
|
|
639
|
+
"runDetachedTurn: the acquired sandbox exposes no driveTurn(message, { sessionId }) \u2014 detached dispatch requires @tangle-network/sandbox >= 0.6 and a session-backed placement (sibling/fleet); disable detached dispatch for this executor."
|
|
640
|
+
);
|
|
641
|
+
}
|
|
642
|
+
const sandboxId = box.id;
|
|
643
|
+
if (typeof sandboxId !== "string" || sandboxId.length === 0) {
|
|
644
|
+
throw new ValidationError(
|
|
645
|
+
"runDetachedTurn: the acquired sandbox carries no id \u2014 without it the detached run cannot be resumed after a restart, so refusing to dispatch detached."
|
|
646
|
+
);
|
|
647
|
+
}
|
|
648
|
+
options.bindSandbox(sandboxId);
|
|
649
|
+
trace.dispatched(sandboxId);
|
|
650
|
+
options.signal.addEventListener("abort", onAbort, { once: true });
|
|
651
|
+
for (; ; ) {
|
|
652
|
+
throwIfAborted(options.signal);
|
|
653
|
+
const tick = await drive.driveTurn(options.prompt, {
|
|
654
|
+
sessionId: options.sessionId,
|
|
655
|
+
turnId: options.sessionId,
|
|
656
|
+
...options.wallCapMs !== void 0 ? { wallCapMs: options.wallCapMs } : {}
|
|
657
|
+
});
|
|
658
|
+
throwIfAborted(options.signal);
|
|
659
|
+
if (tick.state === "completed") {
|
|
660
|
+
trace.ended();
|
|
661
|
+
return { text: tick.text, result: tick.result };
|
|
662
|
+
}
|
|
663
|
+
if (tick.state === "failed") {
|
|
664
|
+
throw new Error(`detached turn ${options.sessionId} failed: ${tick.error}`);
|
|
665
|
+
}
|
|
666
|
+
options.report({ iteration: 0, phase: detachedRunningPhase(tick.elapsedMs) });
|
|
667
|
+
await sleep(intervalMs, options.signal);
|
|
668
|
+
}
|
|
669
|
+
} catch (err) {
|
|
670
|
+
trace.ended(err instanceof Error ? err.message : String(err));
|
|
671
|
+
throw err;
|
|
672
|
+
} finally {
|
|
673
|
+
options.signal.removeEventListener("abort", onAbort);
|
|
674
|
+
if (options.signal.aborted) onAbort();
|
|
675
|
+
await deleteBoxSafe(box);
|
|
676
|
+
}
|
|
677
|
+
}
|
|
678
|
+
function createDetachedTurnTrace(options) {
|
|
679
|
+
const emitter = options.traceEmitter;
|
|
680
|
+
if (!emitter) {
|
|
681
|
+
return { started() {
|
|
682
|
+
}, dispatched() {
|
|
683
|
+
}, ended() {
|
|
684
|
+
} };
|
|
685
|
+
}
|
|
686
|
+
const runId = options.sessionId;
|
|
687
|
+
const agentRunName = options.spec.name ?? options.spec.profile.name ?? "detached-turn";
|
|
688
|
+
const startMs = Date.now();
|
|
689
|
+
let done = false;
|
|
690
|
+
const emit = (event) => {
|
|
691
|
+
void emitter.emit(event);
|
|
692
|
+
};
|
|
693
|
+
return {
|
|
694
|
+
started() {
|
|
695
|
+
emit({
|
|
696
|
+
kind: "loop.started",
|
|
697
|
+
runId,
|
|
698
|
+
timestamp: startMs,
|
|
699
|
+
payload: {
|
|
700
|
+
driver: "detached-turn",
|
|
701
|
+
agentRunNames: [agentRunName],
|
|
702
|
+
maxIterations: 1,
|
|
703
|
+
maxConcurrency: 1
|
|
704
|
+
}
|
|
705
|
+
});
|
|
706
|
+
emit({
|
|
707
|
+
kind: "loop.iteration.started",
|
|
708
|
+
runId,
|
|
709
|
+
timestamp: startMs,
|
|
710
|
+
payload: { iterationIndex: 0, agentRunName, taskHash: options.sessionId }
|
|
711
|
+
});
|
|
712
|
+
},
|
|
713
|
+
dispatched(sandboxId) {
|
|
714
|
+
emit({
|
|
715
|
+
kind: "loop.iteration.dispatch",
|
|
716
|
+
runId,
|
|
717
|
+
timestamp: Date.now(),
|
|
718
|
+
payload: {
|
|
719
|
+
iterationIndex: 0,
|
|
720
|
+
agentRunName,
|
|
721
|
+
placement: options.placement ?? "sibling",
|
|
722
|
+
sandboxId
|
|
723
|
+
}
|
|
724
|
+
});
|
|
725
|
+
},
|
|
726
|
+
ended(error) {
|
|
727
|
+
if (done) return;
|
|
728
|
+
done = true;
|
|
729
|
+
const endMs = Date.now();
|
|
730
|
+
emit({
|
|
731
|
+
kind: "loop.iteration.ended",
|
|
732
|
+
runId,
|
|
733
|
+
timestamp: endMs,
|
|
734
|
+
payload: {
|
|
735
|
+
iterationIndex: 0,
|
|
736
|
+
agentRunName,
|
|
737
|
+
costUsd: 0,
|
|
738
|
+
durationMs: endMs - startMs,
|
|
739
|
+
...error !== void 0 ? { error } : {}
|
|
740
|
+
}
|
|
741
|
+
});
|
|
742
|
+
emit({
|
|
743
|
+
kind: "loop.ended",
|
|
744
|
+
runId,
|
|
745
|
+
timestamp: endMs,
|
|
746
|
+
payload: {
|
|
747
|
+
...error === void 0 ? { winnerIterationIndex: 0 } : {},
|
|
748
|
+
totalCostUsd: 0,
|
|
749
|
+
durationMs: endMs - startMs,
|
|
750
|
+
iterations: 1
|
|
751
|
+
}
|
|
752
|
+
});
|
|
753
|
+
}
|
|
754
|
+
};
|
|
755
|
+
}
|
|
756
|
+
function detachedRunningPhase(elapsedMs) {
|
|
757
|
+
return elapsedMs === void 0 ? "detached-running" : `detached-running ${Math.round(elapsedMs / 1e3)}s`;
|
|
758
|
+
}
|
|
759
|
+
function createDetachedTurnResumeDriver(options) {
|
|
760
|
+
const cancelHooked = /* @__PURE__ */ new Set();
|
|
761
|
+
return {
|
|
762
|
+
intervalMs: options.intervalMs ?? DEFAULT_TICK_INTERVAL_MS,
|
|
763
|
+
async tick({ record, detachedSessionRef }, ctx) {
|
|
764
|
+
const ref = parseDetachedSessionRef(detachedSessionRef);
|
|
765
|
+
if (ref.sandboxId === void 0) {
|
|
766
|
+
return {
|
|
767
|
+
state: "failed",
|
|
768
|
+
error: {
|
|
769
|
+
message: `detached session "${ref.sessionId}" was never bound to a sandbox \u2014 the previous process died before the box was acquired, so the turn was never dispatched and cannot be resumed`,
|
|
770
|
+
kind: "DetachedSessionUnboundError"
|
|
771
|
+
}
|
|
772
|
+
};
|
|
773
|
+
}
|
|
774
|
+
const box = await options.resolveSandbox(ref.sandboxId);
|
|
775
|
+
if (!cancelHooked.has(record.taskId)) {
|
|
776
|
+
cancelHooked.add(record.taskId);
|
|
777
|
+
ctx.signal.addEventListener(
|
|
778
|
+
"abort",
|
|
779
|
+
() => {
|
|
780
|
+
void box._sessionCancel?.(ref.sessionId).catch(() => {
|
|
781
|
+
});
|
|
782
|
+
},
|
|
783
|
+
{ once: true }
|
|
784
|
+
);
|
|
785
|
+
}
|
|
786
|
+
if (ctx.signal.aborted) throwAbort();
|
|
787
|
+
const tick = await box.driveTurn(options.buildMessage(record), {
|
|
788
|
+
sessionId: ref.sessionId,
|
|
789
|
+
turnId: ref.sessionId,
|
|
790
|
+
...options.wallCapMs !== void 0 ? { wallCapMs: options.wallCapMs } : {}
|
|
791
|
+
});
|
|
792
|
+
if (tick.state === "completed") {
|
|
793
|
+
const output = await options.settleOutput(
|
|
794
|
+
{ text: tick.text, result: tick.result },
|
|
795
|
+
record,
|
|
796
|
+
{
|
|
797
|
+
signal: ctx.signal
|
|
798
|
+
}
|
|
799
|
+
);
|
|
800
|
+
return { state: "completed", output };
|
|
801
|
+
}
|
|
802
|
+
if (tick.state === "failed") {
|
|
803
|
+
return {
|
|
804
|
+
state: "failed",
|
|
805
|
+
error: {
|
|
806
|
+
message: `detached turn ${ref.sessionId} failed: ${tick.error}`,
|
|
807
|
+
kind: "DetachedTurnFailedError"
|
|
808
|
+
}
|
|
809
|
+
};
|
|
810
|
+
}
|
|
811
|
+
ctx.report({ iteration: 0, phase: detachedRunningPhase(tick.elapsedMs) });
|
|
812
|
+
return { state: "running" };
|
|
813
|
+
}
|
|
814
|
+
};
|
|
815
|
+
}
|
|
816
|
+
|
|
817
|
+
// src/mcp/delegates.ts
|
|
818
|
+
function detachedSessionDelegate(options) {
|
|
819
|
+
const executor = resolveExecutor(options);
|
|
820
|
+
const sandboxClient = executor.client;
|
|
821
|
+
const fanoutHarnesses = options.fanoutHarnesses;
|
|
822
|
+
const maxConcurrency = options.maxConcurrency ?? 4;
|
|
823
|
+
const traceEmitter = options.traceEmitter;
|
|
824
|
+
return async (args, ctx) => {
|
|
825
|
+
const task = coderTaskFromArgs(args);
|
|
826
|
+
const variants = Math.max(1, Math.trunc(args.variants ?? 1));
|
|
827
|
+
const loopEmitter = composeLoopTraceEmitters(traceEmitter, ctx.traceEmitter);
|
|
828
|
+
ctx.report({ iteration: 0, phase: "starting" });
|
|
829
|
+
if (variants <= 1) {
|
|
830
|
+
const agentRunSpec = coderRunSpec({
|
|
831
|
+
...options.workerProfile ? { profile: options.workerProfile } : {},
|
|
832
|
+
...options.harness ? { harness: options.harness } : {},
|
|
833
|
+
...options.model ? { model: options.model } : {},
|
|
834
|
+
...options.systemPrompt ? { systemPrompt: options.systemPrompt } : {}
|
|
835
|
+
});
|
|
836
|
+
const output = coderOutputAdapter;
|
|
837
|
+
const validator = createCoderValidator(task);
|
|
838
|
+
if (ctx.detachedSessionRef !== void 0 && ctx.updateDetachedSessionRef) {
|
|
839
|
+
const { sessionId } = parseDetachedSessionRef(ctx.detachedSessionRef);
|
|
840
|
+
const rebind = ctx.updateDetachedSessionRef;
|
|
841
|
+
const turn = await runDetachedTurn({
|
|
842
|
+
client: sandboxClient,
|
|
843
|
+
spec: agentRunSpec,
|
|
844
|
+
prompt: agentRunSpec.taskToPrompt(task),
|
|
845
|
+
sessionId,
|
|
846
|
+
bindSandbox: (sandboxId) => rebind(formatDetachedSessionRef({ sandboxId, sessionId })),
|
|
847
|
+
signal: ctx.signal,
|
|
848
|
+
report: ctx.report,
|
|
849
|
+
...loopEmitter ? { traceEmitter: loopEmitter } : {},
|
|
850
|
+
...executor.placement === "fleet" ? { placement: "fleet" } : {},
|
|
851
|
+
...options.detachedTickIntervalMs !== void 0 ? { tickIntervalMs: options.detachedTickIntervalMs } : {},
|
|
852
|
+
...options.detachedWallCapMs !== void 0 ? { wallCapMs: options.detachedWallCapMs } : {}
|
|
853
|
+
});
|
|
854
|
+
const chosen3 = await settleDetachedCoderTurn(turn, {
|
|
855
|
+
task,
|
|
856
|
+
sessionId,
|
|
857
|
+
signal: ctx.signal,
|
|
858
|
+
...options.harness ? { harness: options.harness } : {},
|
|
859
|
+
...options.model ? { model: options.model } : {},
|
|
860
|
+
...options.reviewer ? { reviewer: options.reviewer } : {}
|
|
861
|
+
});
|
|
862
|
+
ctx.report({ iteration: 1, phase: "completed" });
|
|
863
|
+
return chosen3;
|
|
864
|
+
}
|
|
865
|
+
const result2 = await runLoop({
|
|
866
|
+
driver: singleShotDriver,
|
|
867
|
+
agentRun: agentRunSpec,
|
|
868
|
+
output,
|
|
869
|
+
validator,
|
|
870
|
+
task,
|
|
871
|
+
ctx: {
|
|
872
|
+
sandboxClient,
|
|
873
|
+
signal: ctx.signal,
|
|
874
|
+
...loopEmitter ? { traceEmitter: loopEmitter } : {}
|
|
875
|
+
},
|
|
876
|
+
maxIterations: 1,
|
|
877
|
+
maxConcurrency
|
|
878
|
+
});
|
|
879
|
+
const chosen2 = await pickCoderWinner({
|
|
880
|
+
iterations: result2.iterations,
|
|
881
|
+
reviewer: options.reviewer,
|
|
882
|
+
selection: options.winnerSelection ?? "highest-score",
|
|
883
|
+
task,
|
|
884
|
+
signal: ctx.signal
|
|
885
|
+
});
|
|
886
|
+
if (!chosen2) throw new Error(noWinnerMessage(options.reviewer));
|
|
887
|
+
ctx.report({ iteration: 1, phase: "completed" });
|
|
888
|
+
return chosen2;
|
|
889
|
+
}
|
|
890
|
+
const fanout = multiHarnessCoderFanout({
|
|
891
|
+
...options.workerProfile ? { profile: options.workerProfile } : {},
|
|
892
|
+
...fanoutHarnesses && fanoutHarnesses.length > 0 ? { harnesses: fanoutHarnesses.slice(0, variants) } : {},
|
|
893
|
+
...options.fanoutModels ? { models: options.fanoutModels.slice(0, variants) } : {}
|
|
894
|
+
});
|
|
895
|
+
const agentRuns = fanout.agentRuns.slice(0, variants);
|
|
896
|
+
const result = await runLoop({
|
|
897
|
+
driver: fanout.driver,
|
|
898
|
+
agentRuns,
|
|
899
|
+
output: fanout.output,
|
|
900
|
+
validator: fanout.validator,
|
|
901
|
+
task,
|
|
902
|
+
ctx: {
|
|
903
|
+
sandboxClient,
|
|
904
|
+
signal: ctx.signal,
|
|
905
|
+
...loopEmitter ? { traceEmitter: loopEmitter } : {}
|
|
906
|
+
},
|
|
907
|
+
maxIterations: variants,
|
|
908
|
+
maxConcurrency: Math.min(maxConcurrency, variants)
|
|
909
|
+
});
|
|
910
|
+
const chosen = await pickCoderWinner({
|
|
911
|
+
iterations: result.iterations,
|
|
912
|
+
reviewer: options.reviewer,
|
|
913
|
+
selection: options.winnerSelection ?? "highest-score",
|
|
914
|
+
task,
|
|
915
|
+
signal: ctx.signal
|
|
916
|
+
});
|
|
917
|
+
if (!chosen) throw new Error(noWinnerMessage(options.reviewer));
|
|
918
|
+
ctx.report({ iteration: agentRuns.length, phase: "completed" });
|
|
919
|
+
return chosen;
|
|
920
|
+
};
|
|
921
|
+
}
|
|
922
|
+
async function pickCoderWinner(args) {
|
|
923
|
+
const eligible = [];
|
|
924
|
+
for (const iter of args.iterations) {
|
|
925
|
+
if (iter.output === void 0 || iter.error || iter.verdict?.valid !== true) continue;
|
|
926
|
+
const readiness = iter.verdict.score ?? 0;
|
|
927
|
+
if (args.reviewer) {
|
|
928
|
+
const review = await args.reviewer(iter.output, args.task, { signal: args.signal });
|
|
929
|
+
if (!review.approved) continue;
|
|
930
|
+
eligible.push({ iter, readiness: review.readiness });
|
|
931
|
+
} else {
|
|
932
|
+
eligible.push({ iter, readiness });
|
|
933
|
+
}
|
|
934
|
+
}
|
|
935
|
+
if (eligible.length === 0) return void 0;
|
|
936
|
+
if (args.selection === "highest-readiness") {
|
|
937
|
+
const sorted = [...eligible].sort(
|
|
938
|
+
(a, b) => b.readiness - a.readiness || a.iter.index - b.iter.index
|
|
939
|
+
);
|
|
940
|
+
return sorted[0].iter.output;
|
|
941
|
+
}
|
|
942
|
+
const wrapped = eligible.map(({ iter }) => ({
|
|
943
|
+
...iter,
|
|
944
|
+
output: { kind: "done", deliverable: iter.output }
|
|
945
|
+
}));
|
|
946
|
+
const winner = selectValidWinner({
|
|
947
|
+
strategy: baseStrategy(args.selection),
|
|
948
|
+
sizeOf: (o) => o.diffStats.insertions + o.diffStats.deletions
|
|
949
|
+
})(wrapped);
|
|
950
|
+
const out = winner?.output;
|
|
951
|
+
if (!out || out.kind !== "done") return void 0;
|
|
952
|
+
return out.deliverable;
|
|
953
|
+
}
|
|
954
|
+
function baseStrategy(selection) {
|
|
955
|
+
switch (selection) {
|
|
956
|
+
case "smallest-diff":
|
|
957
|
+
return "smallest-artifact";
|
|
958
|
+
case "first-approved":
|
|
959
|
+
return "first-valid";
|
|
960
|
+
default:
|
|
961
|
+
return "highest-score";
|
|
962
|
+
}
|
|
963
|
+
}
|
|
964
|
+
function noWinnerMessage(reviewer) {
|
|
965
|
+
return reviewer ? "coder delegate: no candidate passed validation + review" : "coder delegate: no candidate passed validation";
|
|
966
|
+
}
|
|
967
|
+
function coderTaskFromArgs(args) {
|
|
968
|
+
return {
|
|
969
|
+
goal: buildCoderGoal(args),
|
|
970
|
+
repoRoot: args.repoRoot,
|
|
971
|
+
testCmd: args.config?.testCmd,
|
|
972
|
+
typecheckCmd: args.config?.typecheckCmd,
|
|
973
|
+
forbiddenPaths: args.config?.forbiddenPaths,
|
|
974
|
+
maxDiffLines: args.config?.maxDiffLines
|
|
975
|
+
};
|
|
976
|
+
}
|
|
977
|
+
async function settleDetachedCoderTurn(turn, options) {
|
|
978
|
+
const parsed = coderOutputAdapter.parse(detachedTurnEvents(options.sessionId, turn));
|
|
979
|
+
const validator = createCoderValidator(options.task);
|
|
980
|
+
const verdict = await validator.validate(parsed, { iteration: 0, signal: options.signal });
|
|
981
|
+
if (verdict.valid !== true) throw new Error(noWinnerMessage(options.reviewer));
|
|
982
|
+
if (options.reviewer) {
|
|
983
|
+
const review = await options.reviewer(parsed, options.task, { signal: options.signal });
|
|
984
|
+
if (!review.approved) throw new Error(noWinnerMessage(options.reviewer));
|
|
985
|
+
}
|
|
986
|
+
return parsed;
|
|
987
|
+
}
|
|
988
|
+
function buildCoderGoal(args) {
|
|
989
|
+
if (!args.contextHint) return args.goal;
|
|
990
|
+
return [args.goal, "", "## Context", args.contextHint].join("\n");
|
|
991
|
+
}
|
|
992
|
+
function resolveExecutor(options) {
|
|
993
|
+
if (options.executor && options.sandboxClient) {
|
|
994
|
+
throw new Error("detachedSessionDelegate: pass exactly one of `executor` or `sandboxClient`");
|
|
995
|
+
}
|
|
996
|
+
if (options.executor) return options.executor;
|
|
997
|
+
if (options.sandboxClient) {
|
|
998
|
+
return createSiblingSandboxExecutor({ client: options.sandboxClient });
|
|
999
|
+
}
|
|
1000
|
+
throw new Error("detachedSessionDelegate: `executor` or `sandboxClient` is required");
|
|
1001
|
+
}
|
|
1002
|
+
var singleShotDriver = {
|
|
1003
|
+
name: "mcp-single-shot",
|
|
1004
|
+
async plan(task, history) {
|
|
1005
|
+
return history.length === 0 ? [task] : [];
|
|
1006
|
+
},
|
|
1007
|
+
decide(history) {
|
|
1008
|
+
return history.length > 0 ? "pick-winner" : "fail";
|
|
1009
|
+
}
|
|
1010
|
+
};
|
|
1011
|
+
|
|
97
1012
|
// src/mcp/tools/checks.ts
|
|
98
1013
|
import {
|
|
99
1014
|
makeFinding
|
|
@@ -261,17 +1176,11 @@ function makeCheckRunner(kinds, opts) {
|
|
|
261
1176
|
};
|
|
262
1177
|
}
|
|
263
1178
|
export {
|
|
264
|
-
DELEGATE_CODE_DESCRIPTION,
|
|
265
|
-
DELEGATE_CODE_INPUT_SCHEMA,
|
|
266
|
-
DELEGATE_CODE_TOOL_NAME,
|
|
267
1179
|
DELEGATE_DESCRIPTION,
|
|
268
1180
|
DELEGATE_FEEDBACK_DESCRIPTION,
|
|
269
1181
|
DELEGATE_FEEDBACK_INPUT_SCHEMA,
|
|
270
1182
|
DELEGATE_FEEDBACK_TOOL_NAME,
|
|
271
1183
|
DELEGATE_INPUT_SCHEMA,
|
|
272
|
-
DELEGATE_RESEARCH_DESCRIPTION,
|
|
273
|
-
DELEGATE_RESEARCH_INPUT_SCHEMA,
|
|
274
|
-
DELEGATE_RESEARCH_TOOL_NAME,
|
|
275
1184
|
DELEGATE_TOOL_NAME,
|
|
276
1185
|
DELEGATE_UI_AUDIT_DESCRIPTION,
|
|
277
1186
|
DELEGATE_UI_AUDIT_INPUT_SCHEMA,
|
|
@@ -279,7 +1188,6 @@ export {
|
|
|
279
1188
|
DELEGATION_HISTORY_DESCRIPTION,
|
|
280
1189
|
DELEGATION_HISTORY_INPUT_SCHEMA,
|
|
281
1190
|
DELEGATION_HISTORY_TOOL_NAME,
|
|
282
|
-
DELEGATION_MCP_SERVER_KEY,
|
|
283
1191
|
DELEGATION_STATUS_DESCRIPTION,
|
|
284
1192
|
DELEGATION_STATUS_INPUT_SCHEMA,
|
|
285
1193
|
DELEGATION_STATUS_TOOL_NAME,
|
|
@@ -291,18 +1199,14 @@ export {
|
|
|
291
1199
|
FileDelegationStore,
|
|
292
1200
|
InMemoryDelegationStore,
|
|
293
1201
|
InMemoryFeedbackStore,
|
|
294
|
-
buildDelegationMcpServer,
|
|
295
1202
|
buildDelegationTraceSpans,
|
|
296
1203
|
capDelegationTrace,
|
|
297
1204
|
captureWorktreeDiff,
|
|
298
1205
|
coderTaskFromArgs,
|
|
299
1206
|
composeLoopTraceEmitters,
|
|
300
|
-
composeProductionAgentProfile,
|
|
301
1207
|
createCoordinationTools,
|
|
302
|
-
createDelegateCodeHandler,
|
|
303
1208
|
createDelegateFeedbackHandler,
|
|
304
1209
|
createDelegateHandler,
|
|
305
|
-
createDelegateResearchHandler,
|
|
306
1210
|
createDelegateUiAuditHandler,
|
|
307
1211
|
createDelegationHistoryHandler,
|
|
308
1212
|
createDelegationStatusHandler,
|
|
@@ -337,9 +1241,7 @@ export {
|
|
|
337
1241
|
settleDetachedCoderTurn,
|
|
338
1242
|
traceContextToEnv,
|
|
339
1243
|
validateDelegateArgs,
|
|
340
|
-
validateDelegateCodeArgs,
|
|
341
1244
|
validateDelegateFeedbackArgs,
|
|
342
|
-
validateDelegateResearchArgs,
|
|
343
1245
|
validateDelegateUiAuditArgs,
|
|
344
1246
|
validateDelegationHistoryArgs,
|
|
345
1247
|
validateDelegationStatusArgs
|