@tangle-network/agent-runtime 0.52.0 → 0.54.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 +27 -16
- package/dist/agent.d.ts +2 -2
- package/dist/agent.js +5 -3
- package/dist/agent.js.map +1 -1
- package/dist/analyst-loop.d.ts +1 -1
- package/dist/analyst-loop.js +2 -2
- package/dist/{chunk-COAVO6QB.js → chunk-2BDXWZUC.js} +5 -34
- package/dist/chunk-2BDXWZUC.js.map +1 -0
- package/dist/chunk-A73RADPB.js +281 -0
- package/dist/chunk-A73RADPB.js.map +1 -0
- package/dist/chunk-FRBWCJLP.js +396 -0
- package/dist/chunk-FRBWCJLP.js.map +1 -0
- package/dist/chunk-GRAGM4MC.js +72 -0
- package/dist/chunk-GRAGM4MC.js.map +1 -0
- package/dist/{chunk-2OU7ZQPD.js → chunk-JTH2FPCK.js} +3248 -456
- package/dist/chunk-JTH2FPCK.js.map +1 -0
- package/dist/{chunk-V2K35HF2.js → chunk-KJH62YEK.js} +2 -2
- package/dist/{chunk-HNUXAZIJ.js → chunk-P5OKDSLB.js} +2 -2
- package/dist/{chunk-GSUO5QS6.js → chunk-VLF5RHEQ.js} +2 -5
- package/dist/{chunk-GSUO5QS6.js.map → chunk-VLF5RHEQ.js.map} +1 -1
- package/dist/{chunk-7JITYN6T.js → chunk-XRYEZPR6.js} +18 -2
- package/dist/chunk-XRYEZPR6.js.map +1 -0
- package/dist/{coder-_YCf3BAK.d.ts → coder-CEkUFv8h.d.ts} +1 -1
- package/dist/{types-5MGt5KTY.d.ts → coordination-BMzskrUR.d.ts} +268 -2
- package/dist/{kb-gate-CHAyt4aI.d.ts → delegates-BPLIl8EC.d.ts} +491 -566
- package/dist/{delegation-profile-1GbW5yA3.d.ts → delegation-profile-Bvfro2m1.d.ts} +28 -2
- package/dist/index.d.ts +7 -7
- package/dist/index.js +25 -80
- package/dist/index.js.map +1 -1
- package/dist/intelligence.d.ts +475 -5
- package/dist/intelligence.js +547 -3
- package/dist/intelligence.js.map +1 -1
- package/dist/kb-gate-CuzMYGYM.d.ts +76 -0
- package/dist/{loop-runner-bin-DFUNgpeK.d.ts → loop-runner-bin-BGpVVyXp.d.ts} +6 -27
- package/dist/loop-runner-bin.d.ts +5 -5
- package/dist/loop-runner-bin.js +6 -5
- package/dist/loops.d.ts +8 -5
- package/dist/loops.js +32 -8
- package/dist/mcp/bin.js +94 -30
- package/dist/mcp/bin.js.map +1 -1
- package/dist/mcp/index.d.ts +10 -273
- package/dist/mcp/index.js +45 -365
- package/dist/mcp/index.js.map +1 -1
- package/dist/{openai-tools-D4HLDWgw.d.ts → openai-tools-CoeLQ7Uo.d.ts} +1 -1
- package/dist/profiles.d.ts +2 -2
- package/dist/profiles.js +4 -4
- package/dist/router-client-30Y_pca8.d.ts +120 -0
- package/dist/{run-loop-BIineL1T.d.ts → run-loop-DluzfJ2h.d.ts} +1 -1
- package/dist/runtime.d.ts +429 -131
- package/dist/runtime.js +32 -8
- package/dist/topology.d.ts +54 -1
- package/dist/topology.js +222 -0
- package/dist/topology.js.map +1 -1
- package/dist/{types-BEQsBhOE.d.ts → types-C8rNlxfV.d.ts} +6 -5
- package/dist/workflow.d.ts +2 -2
- package/dist/workflow.js +4 -2
- package/dist/workflow.js.map +1 -1
- package/package.json +5 -5
- package/skills/agent-runtime-adoption/SKILL.md +54 -46
- package/skills/build-with-agent-runtime/SKILL.md +12 -8
- package/skills/loop-writer/SKILL.md +9 -1
- package/skills/supervise/SKILL.md +24 -0
- package/dist/chunk-2OU7ZQPD.js.map +0 -1
- package/dist/chunk-4JI4BCBI.js +0 -623
- package/dist/chunk-4JI4BCBI.js.map +0 -1
- package/dist/chunk-7JITYN6T.js.map +0 -1
- package/dist/chunk-7SP2OVYZ.js +0 -908
- package/dist/chunk-7SP2OVYZ.js.map +0 -1
- package/dist/chunk-BERLUBAP.js +0 -1356
- package/dist/chunk-BERLUBAP.js.map +0 -1
- package/dist/chunk-COAVO6QB.js.map +0 -1
- package/dist/driver-DLI1io57.d.ts +0 -221
- /package/dist/{chunk-V2K35HF2.js.map → chunk-KJH62YEK.js.map} +0 -0
- /package/dist/{chunk-HNUXAZIJ.js.map → chunk-P5OKDSLB.js.map} +0 -0
package/dist/chunk-BERLUBAP.js
DELETED
|
@@ -1,1356 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
capDelegationTrace,
|
|
3
|
-
createDelegationTraceCollector,
|
|
4
|
-
formatDetachedSessionRef,
|
|
5
|
-
generateDelegationSpanId
|
|
6
|
-
} from "./chunk-4JI4BCBI.js";
|
|
7
|
-
import {
|
|
8
|
-
AgentEvalError,
|
|
9
|
-
NotFoundError,
|
|
10
|
-
ValidationError
|
|
11
|
-
} from "./chunk-GSUO5QS6.js";
|
|
12
|
-
|
|
13
|
-
// src/mcp/delegation-store.ts
|
|
14
|
-
import { mkdir, readFile, rename, writeFile } from "fs/promises";
|
|
15
|
-
import { dirname } from "path";
|
|
16
|
-
var DelegationStateCorruptError = class extends AgentEvalError {
|
|
17
|
-
constructor(message, options) {
|
|
18
|
-
super("validation", message, options);
|
|
19
|
-
}
|
|
20
|
-
};
|
|
21
|
-
var DelegationPersistenceError = class extends AgentEvalError {
|
|
22
|
-
constructor(message, options) {
|
|
23
|
-
super("config", message, options);
|
|
24
|
-
}
|
|
25
|
-
};
|
|
26
|
-
var InMemoryDelegationStore = class {
|
|
27
|
-
records = /* @__PURE__ */ new Map();
|
|
28
|
-
async loadAll() {
|
|
29
|
-
return [...this.records.values()].map(cloneRecord);
|
|
30
|
-
}
|
|
31
|
-
async upsert(record) {
|
|
32
|
-
this.records.set(record.taskId, cloneRecord(record));
|
|
33
|
-
}
|
|
34
|
-
async lookupIdempotencyKey(key) {
|
|
35
|
-
for (const record of this.records.values()) {
|
|
36
|
-
if (record.idempotencyKey === key) return record.taskId;
|
|
37
|
-
}
|
|
38
|
-
return void 0;
|
|
39
|
-
}
|
|
40
|
-
async remove(taskIds) {
|
|
41
|
-
for (const taskId of taskIds) this.records.delete(taskId);
|
|
42
|
-
}
|
|
43
|
-
};
|
|
44
|
-
var STATE_FORMAT_VERSION = 1;
|
|
45
|
-
var FileDelegationStore = class {
|
|
46
|
-
filePath;
|
|
47
|
-
recoverCorrupt;
|
|
48
|
-
records = /* @__PURE__ */ new Map();
|
|
49
|
-
loaded = false;
|
|
50
|
-
writeTail = Promise.resolve();
|
|
51
|
-
tmpSeq = 0;
|
|
52
|
-
constructor(options) {
|
|
53
|
-
this.filePath = options.filePath;
|
|
54
|
-
this.recoverCorrupt = options.recoverCorrupt ?? false;
|
|
55
|
-
}
|
|
56
|
-
async loadAll() {
|
|
57
|
-
let raw;
|
|
58
|
-
try {
|
|
59
|
-
raw = await readFile(this.filePath, "utf8");
|
|
60
|
-
} catch (err) {
|
|
61
|
-
if (err.code === "ENOENT") {
|
|
62
|
-
this.loaded = true;
|
|
63
|
-
return [];
|
|
64
|
-
}
|
|
65
|
-
throw new DelegationPersistenceError(
|
|
66
|
-
`FileDelegationStore: failed to read ${this.filePath}: ${errorMessage(err)}`,
|
|
67
|
-
{ cause: err }
|
|
68
|
-
);
|
|
69
|
-
}
|
|
70
|
-
let state;
|
|
71
|
-
try {
|
|
72
|
-
state = parsePersistedState(raw);
|
|
73
|
-
} catch (err) {
|
|
74
|
-
if (!this.recoverCorrupt) {
|
|
75
|
-
throw new DelegationStateCorruptError(
|
|
76
|
-
`FileDelegationStore: state file ${this.filePath} is corrupt (${errorMessage(err)}). Repair or archive the file, or opt into automatic recovery (recoverCorrupt / AGENT_RUNTIME_DELEGATION_STATE_RECOVER=1) to archive it and start empty.`,
|
|
77
|
-
{ cause: err }
|
|
78
|
-
);
|
|
79
|
-
}
|
|
80
|
-
const archivePath = `${this.filePath}.corrupt-${Date.now()}`;
|
|
81
|
-
await rename(this.filePath, archivePath);
|
|
82
|
-
this.loaded = true;
|
|
83
|
-
return [];
|
|
84
|
-
}
|
|
85
|
-
this.records.clear();
|
|
86
|
-
for (const record of state.records) this.records.set(record.taskId, record);
|
|
87
|
-
this.loaded = true;
|
|
88
|
-
return [...this.records.values()].map(cloneRecord);
|
|
89
|
-
}
|
|
90
|
-
async upsert(record) {
|
|
91
|
-
this.assertLoaded("upsert");
|
|
92
|
-
this.records.set(record.taskId, cloneRecord(record));
|
|
93
|
-
await this.enqueueWrite();
|
|
94
|
-
}
|
|
95
|
-
async lookupIdempotencyKey(key) {
|
|
96
|
-
this.assertLoaded("lookupIdempotencyKey");
|
|
97
|
-
for (const record of this.records.values()) {
|
|
98
|
-
if (record.idempotencyKey === key) return record.taskId;
|
|
99
|
-
}
|
|
100
|
-
return void 0;
|
|
101
|
-
}
|
|
102
|
-
async remove(taskIds) {
|
|
103
|
-
this.assertLoaded("remove");
|
|
104
|
-
let changed = false;
|
|
105
|
-
for (const taskId of taskIds) {
|
|
106
|
-
if (this.records.delete(taskId)) changed = true;
|
|
107
|
-
}
|
|
108
|
-
if (changed) await this.enqueueWrite();
|
|
109
|
-
}
|
|
110
|
-
assertLoaded(op) {
|
|
111
|
-
if (this.loaded) return;
|
|
112
|
-
throw new DelegationPersistenceError(
|
|
113
|
-
`FileDelegationStore: ${op} called before loadAll() \u2014 the on-disk state has not been read yet`
|
|
114
|
-
);
|
|
115
|
-
}
|
|
116
|
-
enqueueWrite() {
|
|
117
|
-
const write = this.writeTail.then(() => this.writeSnapshot());
|
|
118
|
-
this.writeTail = write.catch(() => {
|
|
119
|
-
});
|
|
120
|
-
return write;
|
|
121
|
-
}
|
|
122
|
-
async writeSnapshot() {
|
|
123
|
-
const state = {
|
|
124
|
-
version: STATE_FORMAT_VERSION,
|
|
125
|
-
records: [...this.records.values()]
|
|
126
|
-
};
|
|
127
|
-
const payload = `${JSON.stringify(state)}
|
|
128
|
-
`;
|
|
129
|
-
this.tmpSeq += 1;
|
|
130
|
-
const tmpPath = `${this.filePath}.tmp-${process.pid}-${this.tmpSeq}`;
|
|
131
|
-
try {
|
|
132
|
-
await mkdir(dirname(this.filePath), { recursive: true });
|
|
133
|
-
await writeFile(tmpPath, payload, "utf8");
|
|
134
|
-
await rename(tmpPath, this.filePath);
|
|
135
|
-
} catch (err) {
|
|
136
|
-
throw new DelegationPersistenceError(
|
|
137
|
-
`FileDelegationStore: failed to write ${this.filePath}: ${errorMessage(err)}`,
|
|
138
|
-
{ cause: err }
|
|
139
|
-
);
|
|
140
|
-
}
|
|
141
|
-
}
|
|
142
|
-
};
|
|
143
|
-
function parsePersistedState(raw) {
|
|
144
|
-
const parsed = JSON.parse(raw);
|
|
145
|
-
if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
146
|
-
throw new Error("top-level value is not an object");
|
|
147
|
-
}
|
|
148
|
-
const state = parsed;
|
|
149
|
-
if (state.version !== STATE_FORMAT_VERSION) {
|
|
150
|
-
throw new Error(`unsupported state version ${JSON.stringify(state.version)}`);
|
|
151
|
-
}
|
|
152
|
-
if (!Array.isArray(state.records)) {
|
|
153
|
-
throw new Error("`records` is not an array");
|
|
154
|
-
}
|
|
155
|
-
for (const record of state.records) {
|
|
156
|
-
if (record === null || typeof record !== "object") {
|
|
157
|
-
throw new Error("a record entry is not an object");
|
|
158
|
-
}
|
|
159
|
-
const candidate = record;
|
|
160
|
-
if (typeof candidate.taskId !== "string" || typeof candidate.status !== "string") {
|
|
161
|
-
throw new Error("a record entry is missing `taskId`/`status`");
|
|
162
|
-
}
|
|
163
|
-
}
|
|
164
|
-
return { version: STATE_FORMAT_VERSION, records: state.records };
|
|
165
|
-
}
|
|
166
|
-
function cloneRecord(record) {
|
|
167
|
-
return structuredClone(record);
|
|
168
|
-
}
|
|
169
|
-
function errorMessage(err) {
|
|
170
|
-
return err instanceof Error ? err.message : String(err);
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
// src/mcp/task-queue.ts
|
|
174
|
-
var DelegationTaskQueue = class _DelegationTaskQueue {
|
|
175
|
-
records = /* @__PURE__ */ new Map();
|
|
176
|
-
controllers = /* @__PURE__ */ new Map();
|
|
177
|
-
byIdempotencyKey = /* @__PURE__ */ new Map();
|
|
178
|
-
generateId;
|
|
179
|
-
now;
|
|
180
|
-
store;
|
|
181
|
-
resumeDelegate;
|
|
182
|
-
maxTerminalRecords;
|
|
183
|
-
onPersistError;
|
|
184
|
-
traceContext;
|
|
185
|
-
persistTail = Promise.resolve();
|
|
186
|
-
persistFailure;
|
|
187
|
-
constructor(options = {}) {
|
|
188
|
-
this.generateId = options.generateId ?? randomTaskId;
|
|
189
|
-
this.now = options.now ?? (() => (/* @__PURE__ */ new Date()).toISOString());
|
|
190
|
-
this.store = options.store ?? new InMemoryDelegationStore();
|
|
191
|
-
this.resumeDelegate = options.resumeDelegate;
|
|
192
|
-
if (options.maxTerminalRecords !== void 0) {
|
|
193
|
-
if (!Number.isInteger(options.maxTerminalRecords) || options.maxTerminalRecords < 1) {
|
|
194
|
-
throw new ValidationError(
|
|
195
|
-
`DelegationTaskQueue: maxTerminalRecords must be a positive integer, got ${String(options.maxTerminalRecords)}`
|
|
196
|
-
);
|
|
197
|
-
}
|
|
198
|
-
}
|
|
199
|
-
this.maxTerminalRecords = options.maxTerminalRecords ?? Number.POSITIVE_INFINITY;
|
|
200
|
-
this.traceContext = options.traceContext;
|
|
201
|
-
this.onPersistError = options.onPersistError ?? ((error) => {
|
|
202
|
-
queueMicrotask(() => {
|
|
203
|
-
throw error;
|
|
204
|
-
});
|
|
205
|
-
});
|
|
206
|
-
}
|
|
207
|
-
/**
|
|
208
|
-
* Construct a queue from previously-persisted state. Loads every record
|
|
209
|
-
* from `options.store`, rebuilds the idempotency index (so a re-submitted
|
|
210
|
-
* identical task returns the prior taskId and its terminal state), then:
|
|
211
|
-
*
|
|
212
|
-
* - terminal records stay queryable via `status()` / `history()`
|
|
213
|
-
* - in-flight records with a `detachedSessionRef` re-attach through
|
|
214
|
-
* `options.resumeDelegate` and report `running`
|
|
215
|
-
* - other in-flight records settle as failed — their driver died with
|
|
216
|
-
* the previous process and the result is unrecoverable
|
|
217
|
-
*
|
|
218
|
-
* The retention cap applies to the loaded set as well.
|
|
219
|
-
*/
|
|
220
|
-
static async restore(options = {}) {
|
|
221
|
-
const queue = new _DelegationTaskQueue(options);
|
|
222
|
-
const loaded = await queue.store.loadAll();
|
|
223
|
-
queue.rehydrate(loaded);
|
|
224
|
-
return queue;
|
|
225
|
-
}
|
|
226
|
-
/**
|
|
227
|
-
* Kick off a delegation in the background. Returns immediately. The
|
|
228
|
-
* `taskId` is queryable via `status` once this method returns. Throws
|
|
229
|
-
* the recorded `DelegationPersistenceError` once the store has failed —
|
|
230
|
-
* the queue does not accept work it cannot journal.
|
|
231
|
-
*/
|
|
232
|
-
submit(input) {
|
|
233
|
-
if (this.persistFailure) throw this.persistFailure;
|
|
234
|
-
if (input.idempotencyKey) {
|
|
235
|
-
const existing = this.byIdempotencyKey.get(input.idempotencyKey);
|
|
236
|
-
if (existing && this.records.has(existing)) {
|
|
237
|
-
return { taskId: existing, reused: true };
|
|
238
|
-
}
|
|
239
|
-
}
|
|
240
|
-
const taskId = this.generateId();
|
|
241
|
-
const controller = new AbortController();
|
|
242
|
-
const record = {
|
|
243
|
-
taskId,
|
|
244
|
-
profile: input.profile,
|
|
245
|
-
namespace: input.namespace,
|
|
246
|
-
args: input.args,
|
|
247
|
-
status: "pending",
|
|
248
|
-
startedAt: this.now(),
|
|
249
|
-
feedback: [],
|
|
250
|
-
idempotencyKey: input.idempotencyKey,
|
|
251
|
-
detachedSessionRef: input.detachedSessionRef,
|
|
252
|
-
...this.traceContext !== void 0 ? {
|
|
253
|
-
traceId: this.traceContext.traceId,
|
|
254
|
-
...this.traceContext.parentSpanId !== void 0 ? { parentSpanId: this.traceContext.parentSpanId } : {}
|
|
255
|
-
} : {}
|
|
256
|
-
};
|
|
257
|
-
this.records.set(taskId, record);
|
|
258
|
-
this.controllers.set(taskId, controller);
|
|
259
|
-
if (input.idempotencyKey) this.byIdempotencyKey.set(input.idempotencyKey, taskId);
|
|
260
|
-
this.persist(record);
|
|
261
|
-
queueMicrotask(() => {
|
|
262
|
-
this.execute(taskId, input, controller);
|
|
263
|
-
});
|
|
264
|
-
return { taskId, reused: false };
|
|
265
|
-
}
|
|
266
|
-
/**
|
|
267
|
-
* Snapshot the current state of a delegation. Returns `undefined` for
|
|
268
|
-
* unknown ids so callers can distinguish missing from terminal.
|
|
269
|
-
* `includeTrace` attaches the journaled loop-trace span tree — off by
|
|
270
|
-
* default so status polls stay light.
|
|
271
|
-
*/
|
|
272
|
-
status(taskId, opts) {
|
|
273
|
-
const record = this.records.get(taskId);
|
|
274
|
-
if (!record) return void 0;
|
|
275
|
-
return toStatusResult(record, opts);
|
|
276
|
-
}
|
|
277
|
-
/**
|
|
278
|
-
* Abort an in-flight delegation. Returns `false` if the task is unknown
|
|
279
|
-
* or already terminal. The underlying `run` function MUST honor the
|
|
280
|
-
* abort signal for the cancel to take effect; the queue marks the
|
|
281
|
-
* record `cancelled` regardless so a misbehaving runner cannot pin the
|
|
282
|
-
* UI on `running` forever.
|
|
283
|
-
*/
|
|
284
|
-
cancel(taskId) {
|
|
285
|
-
const record = this.records.get(taskId);
|
|
286
|
-
if (!record) return false;
|
|
287
|
-
if (isTerminal(record.status)) return false;
|
|
288
|
-
const controller = this.controllers.get(taskId);
|
|
289
|
-
controller?.abort();
|
|
290
|
-
record.status = "cancelled";
|
|
291
|
-
record.completedAt = this.now();
|
|
292
|
-
record.error = { message: "cancelled by caller", kind: "CancelledError" };
|
|
293
|
-
this.persist(record);
|
|
294
|
-
this.enforceRetention();
|
|
295
|
-
return true;
|
|
296
|
-
}
|
|
297
|
-
/**
|
|
298
|
-
* Append a feedback event to the matching delegation. Returns `false`
|
|
299
|
-
* when `ref` does not name a known taskId — the caller should still
|
|
300
|
-
* record the feedback through a different surface (artifact/outcome
|
|
301
|
-
* kinds are not queue-bound).
|
|
302
|
-
*/
|
|
303
|
-
attachFeedback(taskId, snapshot) {
|
|
304
|
-
const record = this.records.get(taskId);
|
|
305
|
-
if (!record) return false;
|
|
306
|
-
record.feedback.push(snapshot);
|
|
307
|
-
this.persist(record);
|
|
308
|
-
return true;
|
|
309
|
-
}
|
|
310
|
-
/**
|
|
311
|
-
* Query the recorded delegations. Returns entries newest-first (by
|
|
312
|
-
* `startedAt`), truncated to `limit`.
|
|
313
|
-
*/
|
|
314
|
-
history(args = {}) {
|
|
315
|
-
const limit = clampLimit(args.limit);
|
|
316
|
-
const since = args.since ? Date.parse(args.since) : Number.NEGATIVE_INFINITY;
|
|
317
|
-
const out = [];
|
|
318
|
-
for (const record of this.records.values()) {
|
|
319
|
-
if (args.namespace && record.namespace !== args.namespace) continue;
|
|
320
|
-
if (args.profile && record.profile !== args.profile) continue;
|
|
321
|
-
if (Number.isFinite(since) && Date.parse(record.startedAt) < since) continue;
|
|
322
|
-
out.push(toHistoryEntry(record));
|
|
323
|
-
}
|
|
324
|
-
out.sort((a, b) => b.startedAt.localeCompare(a.startedAt));
|
|
325
|
-
return out.slice(0, limit);
|
|
326
|
-
}
|
|
327
|
-
/**
|
|
328
|
-
* Await every journal write issued so far. Rejects with the recorded
|
|
329
|
-
* `DelegationPersistenceError` when any of them failed. Call before
|
|
330
|
-
* handing the store's backing file to another process.
|
|
331
|
-
*/
|
|
332
|
-
async flush() {
|
|
333
|
-
await this.persistTail;
|
|
334
|
-
if (this.persistFailure) throw this.persistFailure;
|
|
335
|
-
}
|
|
336
|
-
/** Test-only — number of in-flight (non-terminal) records. */
|
|
337
|
-
inflightCount() {
|
|
338
|
-
let n = 0;
|
|
339
|
-
for (const record of this.records.values()) {
|
|
340
|
-
if (!isTerminal(record.status)) n += 1;
|
|
341
|
-
}
|
|
342
|
-
return n;
|
|
343
|
-
}
|
|
344
|
-
async execute(taskId, input, controller) {
|
|
345
|
-
const record = this.records.get(taskId);
|
|
346
|
-
if (!record) return;
|
|
347
|
-
record.status = "running";
|
|
348
|
-
this.persist(record);
|
|
349
|
-
const traceCollector = createDelegationTraceCollector((spans) => {
|
|
350
|
-
if (isTerminal(currentStatus(record))) return;
|
|
351
|
-
this.appendTrace(record, spans);
|
|
352
|
-
this.persist(record);
|
|
353
|
-
});
|
|
354
|
-
try {
|
|
355
|
-
const output = await input.run({
|
|
356
|
-
signal: controller.signal,
|
|
357
|
-
report: (progress) => {
|
|
358
|
-
if (record.status === "running") {
|
|
359
|
-
record.progress = progress;
|
|
360
|
-
this.persist(record);
|
|
361
|
-
}
|
|
362
|
-
},
|
|
363
|
-
traceEmitter: traceCollector.emitter,
|
|
364
|
-
...record.detachedSessionRef !== void 0 ? { detachedSessionRef: record.detachedSessionRef } : {},
|
|
365
|
-
updateDetachedSessionRef: (ref) => {
|
|
366
|
-
if (typeof ref !== "string" || ref.length === 0) {
|
|
367
|
-
throw new ValidationError(
|
|
368
|
-
"DelegationTaskQueue: updateDetachedSessionRef requires a non-empty ref"
|
|
369
|
-
);
|
|
370
|
-
}
|
|
371
|
-
if (isTerminal(currentStatus(record))) return;
|
|
372
|
-
record.detachedSessionRef = ref;
|
|
373
|
-
this.persist(record);
|
|
374
|
-
}
|
|
375
|
-
});
|
|
376
|
-
traceCollector.settle();
|
|
377
|
-
if (currentStatus(record) === "cancelled") return;
|
|
378
|
-
record.status = "completed";
|
|
379
|
-
record.completedAt = this.now();
|
|
380
|
-
record.result = { profile: input.profile, output };
|
|
381
|
-
this.persist(record);
|
|
382
|
-
this.enforceRetention();
|
|
383
|
-
} catch (err) {
|
|
384
|
-
traceCollector.settle();
|
|
385
|
-
if (currentStatus(record) === "cancelled") return;
|
|
386
|
-
record.status = "failed";
|
|
387
|
-
record.completedAt = this.now();
|
|
388
|
-
record.error = errorToShape(err);
|
|
389
|
-
this.persist(record);
|
|
390
|
-
this.enforceRetention();
|
|
391
|
-
} finally {
|
|
392
|
-
this.controllers.delete(taskId);
|
|
393
|
-
}
|
|
394
|
-
}
|
|
395
|
-
appendTrace(record, spans) {
|
|
396
|
-
if (spans.length === 0) return;
|
|
397
|
-
const { trace, truncated } = capDelegationTrace([...record.trace ?? [], ...spans]);
|
|
398
|
-
record.trace = trace;
|
|
399
|
-
if (truncated) record.traceTruncated = true;
|
|
400
|
-
}
|
|
401
|
-
rehydrate(loaded) {
|
|
402
|
-
const records = [...loaded].sort((a, b) => a.startedAt.localeCompare(b.startedAt));
|
|
403
|
-
for (const record of records) {
|
|
404
|
-
this.records.set(record.taskId, record);
|
|
405
|
-
if (record.idempotencyKey) this.byIdempotencyKey.set(record.idempotencyKey, record.taskId);
|
|
406
|
-
}
|
|
407
|
-
for (const record of this.records.values()) {
|
|
408
|
-
if (isTerminal(record.status)) continue;
|
|
409
|
-
if (record.detachedSessionRef && this.resumeDelegate) {
|
|
410
|
-
record.status = "running";
|
|
411
|
-
this.persist(record);
|
|
412
|
-
this.startResume(record, record.detachedSessionRef, this.resumeDelegate);
|
|
413
|
-
continue;
|
|
414
|
-
}
|
|
415
|
-
record.status = "failed";
|
|
416
|
-
record.completedAt = this.now();
|
|
417
|
-
record.error = {
|
|
418
|
-
message: record.detachedSessionRef ? `delegation driver restarted while the task was in flight; detached session "${record.detachedSessionRef}" needs a resumeDelegate to be resumed` : "delegation driver restarted while the task was in flight; the run was not detached and cannot be resumed",
|
|
419
|
-
kind: "DriverRestartError"
|
|
420
|
-
};
|
|
421
|
-
this.persist(record);
|
|
422
|
-
}
|
|
423
|
-
this.enforceRetention();
|
|
424
|
-
}
|
|
425
|
-
startResume(record, detachedSessionRef, driver) {
|
|
426
|
-
const controller = new AbortController();
|
|
427
|
-
this.controllers.set(record.taskId, controller);
|
|
428
|
-
void this.driveResume(record, detachedSessionRef, driver, controller);
|
|
429
|
-
}
|
|
430
|
-
async driveResume(record, detachedSessionRef, driver, controller) {
|
|
431
|
-
const intervalMs = driver.intervalMs ?? 5e3;
|
|
432
|
-
const resumeStartMs = Date.parse(this.now());
|
|
433
|
-
const ctx = {
|
|
434
|
-
signal: controller.signal,
|
|
435
|
-
report: (progress) => {
|
|
436
|
-
if (currentStatus(record) !== "running") return;
|
|
437
|
-
record.progress = progress;
|
|
438
|
-
this.persist(record);
|
|
439
|
-
}
|
|
440
|
-
};
|
|
441
|
-
try {
|
|
442
|
-
while (!controller.signal.aborted && currentStatus(record) === "running") {
|
|
443
|
-
const tick = await driver.tick({ record: structuredClone(record), detachedSessionRef }, ctx);
|
|
444
|
-
if (currentStatus(record) === "cancelled") return;
|
|
445
|
-
if (tick.state === "completed") {
|
|
446
|
-
this.appendResumeSpan(record, detachedSessionRef, resumeStartMs);
|
|
447
|
-
record.status = "completed";
|
|
448
|
-
record.completedAt = this.now();
|
|
449
|
-
record.result = {
|
|
450
|
-
profile: record.profile,
|
|
451
|
-
output: tick.output
|
|
452
|
-
};
|
|
453
|
-
if (tick.costUsd !== void 0) record.costUsd = tick.costUsd;
|
|
454
|
-
this.persist(record);
|
|
455
|
-
this.enforceRetention();
|
|
456
|
-
return;
|
|
457
|
-
}
|
|
458
|
-
if (tick.state === "failed") {
|
|
459
|
-
this.appendResumeSpan(record, detachedSessionRef, resumeStartMs, tick.error.message);
|
|
460
|
-
record.status = "failed";
|
|
461
|
-
record.completedAt = this.now();
|
|
462
|
-
record.error = tick.error;
|
|
463
|
-
this.persist(record);
|
|
464
|
-
this.enforceRetention();
|
|
465
|
-
return;
|
|
466
|
-
}
|
|
467
|
-
await abortableDelay(intervalMs, controller.signal);
|
|
468
|
-
}
|
|
469
|
-
} catch (err) {
|
|
470
|
-
if (currentStatus(record) === "cancelled") return;
|
|
471
|
-
this.appendResumeSpan(record, detachedSessionRef, resumeStartMs, errorToShape(err).message);
|
|
472
|
-
record.status = "failed";
|
|
473
|
-
record.completedAt = this.now();
|
|
474
|
-
record.error = errorToShape(err);
|
|
475
|
-
this.persist(record);
|
|
476
|
-
this.enforceRetention();
|
|
477
|
-
} finally {
|
|
478
|
-
this.controllers.delete(record.taskId);
|
|
479
|
-
}
|
|
480
|
-
}
|
|
481
|
-
/**
|
|
482
|
-
* Journal the resumed segment of a detached run as one compact span. The
|
|
483
|
-
* resume driver re-attaches after a process restart, so the original
|
|
484
|
-
* process's loop events are gone — this span records the post-restart
|
|
485
|
-
* observation window (re-attach → terminal tick) under the
|
|
486
|
-
* `'detached-resume'` driver tag, keeping restored delegations observable
|
|
487
|
-
* in the journal alongside trace-carrying live runs.
|
|
488
|
-
*/
|
|
489
|
-
appendResumeSpan(record, detachedSessionRef, startMs, error) {
|
|
490
|
-
this.appendTrace(record, [
|
|
491
|
-
{
|
|
492
|
-
spanId: generateDelegationSpanId(),
|
|
493
|
-
name: "loop",
|
|
494
|
-
kind: "loop",
|
|
495
|
-
startMs,
|
|
496
|
-
endMs: Date.parse(this.now()),
|
|
497
|
-
meta: {
|
|
498
|
-
"tangle.loop.driver": "detached-resume",
|
|
499
|
-
"tangle.loop.detached_session_ref": detachedSessionRef,
|
|
500
|
-
...error !== void 0 ? { "tangle.loop.error": error } : {}
|
|
501
|
-
}
|
|
502
|
-
}
|
|
503
|
-
]);
|
|
504
|
-
}
|
|
505
|
-
persist(record) {
|
|
506
|
-
if (this.persistFailure) return;
|
|
507
|
-
const snapshot = structuredClone(record);
|
|
508
|
-
this.persistTail = this.persistTail.then(async () => {
|
|
509
|
-
if (this.persistFailure) return;
|
|
510
|
-
try {
|
|
511
|
-
await this.store.upsert(snapshot);
|
|
512
|
-
} catch (err) {
|
|
513
|
-
this.failPersistence(err);
|
|
514
|
-
}
|
|
515
|
-
});
|
|
516
|
-
}
|
|
517
|
-
persistRemoval(taskIds) {
|
|
518
|
-
if (this.persistFailure || taskIds.length === 0) return;
|
|
519
|
-
this.persistTail = this.persistTail.then(async () => {
|
|
520
|
-
if (this.persistFailure) return;
|
|
521
|
-
try {
|
|
522
|
-
await this.store.remove(taskIds);
|
|
523
|
-
} catch (err) {
|
|
524
|
-
this.failPersistence(err);
|
|
525
|
-
}
|
|
526
|
-
});
|
|
527
|
-
}
|
|
528
|
-
failPersistence(cause) {
|
|
529
|
-
if (this.persistFailure) return;
|
|
530
|
-
const error = cause instanceof DelegationPersistenceError ? cause : new DelegationPersistenceError(
|
|
531
|
-
`DelegationTaskQueue: store write failed: ${cause instanceof Error ? cause.message : String(cause)}`,
|
|
532
|
-
{ cause }
|
|
533
|
-
);
|
|
534
|
-
this.persistFailure = error;
|
|
535
|
-
this.onPersistError(error);
|
|
536
|
-
}
|
|
537
|
-
enforceRetention() {
|
|
538
|
-
if (!Number.isFinite(this.maxTerminalRecords)) return;
|
|
539
|
-
const terminal = [];
|
|
540
|
-
for (const record of this.records.values()) {
|
|
541
|
-
if (isTerminal(record.status)) terminal.push(record);
|
|
542
|
-
}
|
|
543
|
-
const excess = terminal.length - this.maxTerminalRecords;
|
|
544
|
-
if (excess <= 0) return;
|
|
545
|
-
terminal.sort(
|
|
546
|
-
(a, b) => (a.completedAt ?? a.startedAt).localeCompare(b.completedAt ?? b.startedAt)
|
|
547
|
-
);
|
|
548
|
-
const evicted = terminal.slice(0, excess);
|
|
549
|
-
for (const record of evicted) {
|
|
550
|
-
this.records.delete(record.taskId);
|
|
551
|
-
if (record.idempotencyKey && this.byIdempotencyKey.get(record.idempotencyKey) === record.taskId) {
|
|
552
|
-
this.byIdempotencyKey.delete(record.idempotencyKey);
|
|
553
|
-
}
|
|
554
|
-
}
|
|
555
|
-
this.persistRemoval(evicted.map((record) => record.taskId));
|
|
556
|
-
}
|
|
557
|
-
};
|
|
558
|
-
function isTerminal(status) {
|
|
559
|
-
return status === "completed" || status === "failed" || status === "cancelled";
|
|
560
|
-
}
|
|
561
|
-
function currentStatus(record) {
|
|
562
|
-
return record.status;
|
|
563
|
-
}
|
|
564
|
-
function clampLimit(raw) {
|
|
565
|
-
if (!Number.isFinite(raw)) return 50;
|
|
566
|
-
const n = Math.trunc(raw);
|
|
567
|
-
if (n <= 0) return 50;
|
|
568
|
-
return Math.min(n, 500);
|
|
569
|
-
}
|
|
570
|
-
function abortableDelay(ms, signal) {
|
|
571
|
-
return new Promise((resolve) => {
|
|
572
|
-
if (signal.aborted) {
|
|
573
|
-
resolve();
|
|
574
|
-
return;
|
|
575
|
-
}
|
|
576
|
-
const onAbort = () => {
|
|
577
|
-
clearTimeout(timer);
|
|
578
|
-
resolve();
|
|
579
|
-
};
|
|
580
|
-
const timer = setTimeout(() => {
|
|
581
|
-
signal.removeEventListener("abort", onAbort);
|
|
582
|
-
resolve();
|
|
583
|
-
}, ms);
|
|
584
|
-
signal.addEventListener("abort", onAbort, { once: true });
|
|
585
|
-
});
|
|
586
|
-
}
|
|
587
|
-
function toStatusResult(record, opts) {
|
|
588
|
-
const out = {
|
|
589
|
-
taskId: record.taskId,
|
|
590
|
-
profile: record.profile,
|
|
591
|
-
status: record.status,
|
|
592
|
-
startedAt: record.startedAt
|
|
593
|
-
};
|
|
594
|
-
if (record.progress) out.progress = record.progress;
|
|
595
|
-
if (record.result) out.result = record.result;
|
|
596
|
-
if (record.error) out.error = record.error;
|
|
597
|
-
if (record.costUsd !== void 0) out.costUsd = record.costUsd;
|
|
598
|
-
if (record.completedAt) out.completedAt = record.completedAt;
|
|
599
|
-
if (record.traceId !== void 0) out.traceId = record.traceId;
|
|
600
|
-
if (record.parentSpanId !== void 0) out.parentSpanId = record.parentSpanId;
|
|
601
|
-
if (opts?.includeTrace === true && record.trace && record.trace.length > 0) {
|
|
602
|
-
out.trace = record.trace.map((span) => ({ ...span }));
|
|
603
|
-
if (record.traceTruncated) out.traceTruncated = true;
|
|
604
|
-
}
|
|
605
|
-
return out;
|
|
606
|
-
}
|
|
607
|
-
function toHistoryEntry(record) {
|
|
608
|
-
const entry = {
|
|
609
|
-
taskId: record.taskId,
|
|
610
|
-
profile: record.profile,
|
|
611
|
-
args: record.args,
|
|
612
|
-
status: record.status,
|
|
613
|
-
startedAt: record.startedAt,
|
|
614
|
-
hasTrace: record.trace !== void 0 && record.trace.length > 0
|
|
615
|
-
};
|
|
616
|
-
if (record.namespace) entry.namespace = record.namespace;
|
|
617
|
-
if (record.completedAt) entry.completedAt = record.completedAt;
|
|
618
|
-
if (record.costUsd !== void 0) entry.costUsd = record.costUsd;
|
|
619
|
-
if (record.feedback.length > 0) entry.feedback = [...record.feedback];
|
|
620
|
-
if (record.traceId !== void 0) entry.traceId = record.traceId;
|
|
621
|
-
return entry;
|
|
622
|
-
}
|
|
623
|
-
function errorToShape(err) {
|
|
624
|
-
if (err instanceof Error) {
|
|
625
|
-
return { message: err.message, kind: err.name || "Error" };
|
|
626
|
-
}
|
|
627
|
-
return { message: String(err), kind: "NonError" };
|
|
628
|
-
}
|
|
629
|
-
function randomTaskId() {
|
|
630
|
-
const t = Date.now().toString(36);
|
|
631
|
-
const r = Math.random().toString(36).slice(2, 10);
|
|
632
|
-
return `dlg-${t}-${r}`;
|
|
633
|
-
}
|
|
634
|
-
function hashIdempotencyInput(value) {
|
|
635
|
-
let str;
|
|
636
|
-
try {
|
|
637
|
-
str = JSON.stringify(canonicalize(value));
|
|
638
|
-
} catch {
|
|
639
|
-
str = String(value);
|
|
640
|
-
}
|
|
641
|
-
let h = 2166136261;
|
|
642
|
-
for (let i = 0; i < str.length; i += 1) {
|
|
643
|
-
h ^= str.charCodeAt(i);
|
|
644
|
-
h = Math.imul(h, 16777619);
|
|
645
|
-
}
|
|
646
|
-
return (h >>> 0).toString(16).padStart(8, "0");
|
|
647
|
-
}
|
|
648
|
-
function canonicalize(value) {
|
|
649
|
-
if (value === null || typeof value !== "object") return value;
|
|
650
|
-
if (Array.isArray(value)) return value.map(canonicalize);
|
|
651
|
-
const entries = Object.entries(value).filter(([, v]) => v !== void 0).sort(([a], [b]) => a.localeCompare(b));
|
|
652
|
-
const out = {};
|
|
653
|
-
for (const [k, v] of entries) out[k] = canonicalize(v);
|
|
654
|
-
return out;
|
|
655
|
-
}
|
|
656
|
-
|
|
657
|
-
// src/mcp/tools/delegate-code.ts
|
|
658
|
-
var DELEGATE_CODE_TOOL_NAME = "delegate_code";
|
|
659
|
-
var DELEGATE_CODE_DESCRIPTION = [
|
|
660
|
-
"Delegate a coding task to specialist coder agents that produce a validated patch.",
|
|
661
|
-
"",
|
|
662
|
-
"Use when: you need code written, fixed, refactored, or extended to satisfy a",
|
|
663
|
-
"user goal that touches a real repository. The coder runs in an isolated",
|
|
664
|
-
"sandbox, opens a fresh branch, keeps the diff minimal, runs the supplied",
|
|
665
|
-
"test + typecheck commands, and emits a unified-diff patch.",
|
|
666
|
-
"",
|
|
667
|
-
"Returns immediately with a taskId. Poll delegation_status to retrieve the",
|
|
668
|
-
"patch + validator verdict (typically minutes-to-hours, longer for large",
|
|
669
|
-
"changes). Identical inputs return the same taskId \u2014 safe to retry.",
|
|
670
|
-
"",
|
|
671
|
-
"When variants > 1, multiple coder harnesses (claude-code, codex, opencode)",
|
|
672
|
-
"attempt the task in parallel and the highest-scoring patch wins (smallest",
|
|
673
|
-
"passing diff). Use variants for high-stakes changes; single variant for",
|
|
674
|
-
"routine ones.",
|
|
675
|
-
"",
|
|
676
|
-
"Capability scope: the coder cannot modify paths outside repoRoot and cannot",
|
|
677
|
-
"touch paths in config.forbiddenPaths. The validator hard-fails on a",
|
|
678
|
-
"forbidden-path violation, diff above config.maxDiffLines, test failure, or",
|
|
679
|
-
"typecheck failure \u2014 none of those make it past the gate."
|
|
680
|
-
].join("\n");
|
|
681
|
-
var DELEGATE_CODE_INPUT_SCHEMA = {
|
|
682
|
-
type: "object",
|
|
683
|
-
properties: {
|
|
684
|
-
goal: {
|
|
685
|
-
type: "string",
|
|
686
|
-
description: "Natural-language description of what the coder must accomplish."
|
|
687
|
-
},
|
|
688
|
-
repoRoot: {
|
|
689
|
-
type: "string",
|
|
690
|
-
description: "Absolute path inside the sandbox where the repo lives."
|
|
691
|
-
},
|
|
692
|
-
contextHint: {
|
|
693
|
-
type: "string",
|
|
694
|
-
description: "Optional free-form context the coder sees in the prompt prelude."
|
|
695
|
-
},
|
|
696
|
-
variants: {
|
|
697
|
-
type: "integer",
|
|
698
|
-
minimum: 1,
|
|
699
|
-
maximum: 8,
|
|
700
|
-
description: "Number of parallel coder harnesses. Default 1."
|
|
701
|
-
},
|
|
702
|
-
config: {
|
|
703
|
-
type: "object",
|
|
704
|
-
properties: {
|
|
705
|
-
testCmd: { type: "string" },
|
|
706
|
-
typecheckCmd: { type: "string" },
|
|
707
|
-
forbiddenPaths: { type: "array", items: { type: "string" } },
|
|
708
|
-
maxDiffLines: { type: "integer", minimum: 1 }
|
|
709
|
-
},
|
|
710
|
-
additionalProperties: false
|
|
711
|
-
},
|
|
712
|
-
namespace: {
|
|
713
|
-
type: "string",
|
|
714
|
-
description: "Multi-tenant scope (customer-id, workspace-id)."
|
|
715
|
-
}
|
|
716
|
-
},
|
|
717
|
-
required: ["goal", "repoRoot"],
|
|
718
|
-
additionalProperties: false
|
|
719
|
-
};
|
|
720
|
-
var SINGLE_VARIANT_ESTIMATE_MS = 6 * 60 * 1e3;
|
|
721
|
-
var FANOUT_PER_VARIANT_ESTIMATE_MS = 8 * 60 * 1e3;
|
|
722
|
-
function validateDelegateCodeArgs(raw) {
|
|
723
|
-
if (raw === null || typeof raw !== "object") {
|
|
724
|
-
throw new TypeError("delegate_code: arguments must be an object");
|
|
725
|
-
}
|
|
726
|
-
const value = raw;
|
|
727
|
-
const goal = value.goal;
|
|
728
|
-
if (typeof goal !== "string" || goal.trim().length === 0) {
|
|
729
|
-
throw new TypeError("delegate_code: `goal` must be a non-empty string");
|
|
730
|
-
}
|
|
731
|
-
const repoRoot = value.repoRoot;
|
|
732
|
-
if (typeof repoRoot !== "string" || repoRoot.trim().length === 0) {
|
|
733
|
-
throw new TypeError("delegate_code: `repoRoot` must be a non-empty string");
|
|
734
|
-
}
|
|
735
|
-
const args = { goal: goal.trim(), repoRoot: repoRoot.trim() };
|
|
736
|
-
if (typeof value.contextHint === "string") args.contextHint = value.contextHint;
|
|
737
|
-
if (value.variants !== void 0) {
|
|
738
|
-
const variants = Number(value.variants);
|
|
739
|
-
if (!Number.isFinite(variants) || variants < 1 || variants > 8) {
|
|
740
|
-
throw new RangeError("delegate_code: `variants` must be an integer in [1, 8]");
|
|
741
|
-
}
|
|
742
|
-
args.variants = Math.trunc(variants);
|
|
743
|
-
}
|
|
744
|
-
if (value.config !== void 0) {
|
|
745
|
-
args.config = validateConfig(value.config);
|
|
746
|
-
}
|
|
747
|
-
if (typeof value.namespace === "string") args.namespace = value.namespace;
|
|
748
|
-
return args;
|
|
749
|
-
}
|
|
750
|
-
function validateConfig(raw) {
|
|
751
|
-
if (raw === null || typeof raw !== "object") {
|
|
752
|
-
throw new TypeError("delegate_code: `config` must be an object");
|
|
753
|
-
}
|
|
754
|
-
const value = raw;
|
|
755
|
-
const out = {};
|
|
756
|
-
if (value.testCmd !== void 0) {
|
|
757
|
-
if (typeof value.testCmd !== "string") {
|
|
758
|
-
throw new TypeError("delegate_code: `config.testCmd` must be a string");
|
|
759
|
-
}
|
|
760
|
-
out.testCmd = value.testCmd;
|
|
761
|
-
}
|
|
762
|
-
if (value.typecheckCmd !== void 0) {
|
|
763
|
-
if (typeof value.typecheckCmd !== "string") {
|
|
764
|
-
throw new TypeError("delegate_code: `config.typecheckCmd` must be a string");
|
|
765
|
-
}
|
|
766
|
-
out.typecheckCmd = value.typecheckCmd;
|
|
767
|
-
}
|
|
768
|
-
if (value.forbiddenPaths !== void 0) {
|
|
769
|
-
if (!Array.isArray(value.forbiddenPaths)) {
|
|
770
|
-
throw new TypeError("delegate_code: `config.forbiddenPaths` must be a string array");
|
|
771
|
-
}
|
|
772
|
-
out.forbiddenPaths = value.forbiddenPaths.map((entry, i) => {
|
|
773
|
-
if (typeof entry !== "string") {
|
|
774
|
-
throw new TypeError(`delegate_code: forbiddenPaths[${i}] must be a string`);
|
|
775
|
-
}
|
|
776
|
-
return entry;
|
|
777
|
-
});
|
|
778
|
-
}
|
|
779
|
-
if (value.maxDiffLines !== void 0) {
|
|
780
|
-
const n = Number(value.maxDiffLines);
|
|
781
|
-
if (!Number.isFinite(n) || n < 1) {
|
|
782
|
-
throw new RangeError("delegate_code: `config.maxDiffLines` must be a positive integer");
|
|
783
|
-
}
|
|
784
|
-
out.maxDiffLines = Math.trunc(n);
|
|
785
|
-
}
|
|
786
|
-
return out;
|
|
787
|
-
}
|
|
788
|
-
function createDelegateCodeHandler(options) {
|
|
789
|
-
const estimateDurationMs = options.estimateDurationMs ?? defaultEstimate;
|
|
790
|
-
return async (raw) => {
|
|
791
|
-
const args = validateDelegateCodeArgs(raw);
|
|
792
|
-
const idempotencyKey = hashIdempotencyInput({
|
|
793
|
-
profile: "coder",
|
|
794
|
-
goal: args.goal,
|
|
795
|
-
repoRoot: args.repoRoot,
|
|
796
|
-
contextHint: args.contextHint,
|
|
797
|
-
variants: args.variants ?? 1,
|
|
798
|
-
config: args.config,
|
|
799
|
-
namespace: args.namespace
|
|
800
|
-
});
|
|
801
|
-
const detached = options.detachedDispatch === true && (args.variants ?? 1) <= 1;
|
|
802
|
-
const submitted = options.queue.submit({
|
|
803
|
-
profile: "coder",
|
|
804
|
-
args,
|
|
805
|
-
namespace: args.namespace,
|
|
806
|
-
idempotencyKey,
|
|
807
|
-
...detached ? {
|
|
808
|
-
detachedSessionRef: formatDetachedSessionRef({
|
|
809
|
-
sessionId: `dlg-turn-coder-${idempotencyKey}`
|
|
810
|
-
})
|
|
811
|
-
} : {},
|
|
812
|
-
run: async (ctx) => options.delegate(args, ctx)
|
|
813
|
-
});
|
|
814
|
-
return {
|
|
815
|
-
taskId: submitted.taskId,
|
|
816
|
-
estimatedDurationMs: estimateDurationMs(args)
|
|
817
|
-
};
|
|
818
|
-
};
|
|
819
|
-
}
|
|
820
|
-
function defaultEstimate(args) {
|
|
821
|
-
const variants = Math.max(1, args.variants ?? 1);
|
|
822
|
-
if (variants === 1) return SINGLE_VARIANT_ESTIMATE_MS;
|
|
823
|
-
return FANOUT_PER_VARIANT_ESTIMATE_MS;
|
|
824
|
-
}
|
|
825
|
-
|
|
826
|
-
// src/mcp/feedback-store.ts
|
|
827
|
-
var InMemoryFeedbackStore = class {
|
|
828
|
-
events = [];
|
|
829
|
-
async put(event) {
|
|
830
|
-
this.events.push({ ...event });
|
|
831
|
-
}
|
|
832
|
-
async list(filter = {}) {
|
|
833
|
-
let out = this.events;
|
|
834
|
-
if (filter.namespace !== void 0) {
|
|
835
|
-
out = out.filter((event) => event.namespace === filter.namespace);
|
|
836
|
-
}
|
|
837
|
-
if (filter.refersToRef !== void 0) {
|
|
838
|
-
out = out.filter((event) => event.refersTo.ref === filter.refersToRef);
|
|
839
|
-
}
|
|
840
|
-
return out.map((event) => ({ ...event }));
|
|
841
|
-
}
|
|
842
|
-
};
|
|
843
|
-
function eventToSnapshot(event) {
|
|
844
|
-
const snap = {
|
|
845
|
-
id: event.id,
|
|
846
|
-
score: event.rating.score,
|
|
847
|
-
by: event.by,
|
|
848
|
-
notes: event.rating.notes,
|
|
849
|
-
capturedAt: event.capturedAt
|
|
850
|
-
};
|
|
851
|
-
if (event.rating.label) snap.label = event.rating.label;
|
|
852
|
-
return snap;
|
|
853
|
-
}
|
|
854
|
-
|
|
855
|
-
// src/mcp/tools/delegate-feedback.ts
|
|
856
|
-
var DELEGATE_FEEDBACK_TOOL_NAME = "delegate_feedback";
|
|
857
|
-
var DELEGATE_FEEDBACK_DESCRIPTION = [
|
|
858
|
-
"Record feedback on a delegation, artifact, or outcome. Synchronous \u2014 the",
|
|
859
|
-
"event is durably stored when this call returns.",
|
|
860
|
-
"",
|
|
861
|
-
"Use when: you (the agent), the user, or a downstream judge has formed an",
|
|
862
|
-
"opinion about a piece of work and want it persisted for calibration,",
|
|
863
|
-
"pricing, or future routing. Every call is a new event \u2014 multiple ratings",
|
|
864
|
-
"on the same target are expected and never deduped.",
|
|
865
|
-
"",
|
|
866
|
-
"`refersTo.kind`:",
|
|
867
|
-
' - "delegation": ref is a taskId returned by delegate_code/delegate_research',
|
|
868
|
-
' - "artifact": ref is a URI/path/git-sha \u2014 anything you can dereference',
|
|
869
|
-
' - "outcome": ref is a free-form description of a downstream result',
|
|
870
|
-
"",
|
|
871
|
-
"`by`:",
|
|
872
|
-
' - "agent": the agent itself rated the work',
|
|
873
|
-
' - "user": the human user rated it',
|
|
874
|
-
' - "downstream-judge": an automated evaluator emitted the rating',
|
|
875
|
-
"",
|
|
876
|
-
"When ref names a known taskId, the rating is also attached to the",
|
|
877
|
-
"delegation record so delegation_history surfaces it inline."
|
|
878
|
-
].join("\n");
|
|
879
|
-
var DELEGATE_FEEDBACK_INPUT_SCHEMA = {
|
|
880
|
-
type: "object",
|
|
881
|
-
properties: {
|
|
882
|
-
refersTo: {
|
|
883
|
-
type: "object",
|
|
884
|
-
properties: {
|
|
885
|
-
kind: { type: "string", enum: ["delegation", "artifact", "outcome"] },
|
|
886
|
-
ref: { type: "string" }
|
|
887
|
-
},
|
|
888
|
-
required: ["kind", "ref"],
|
|
889
|
-
additionalProperties: false
|
|
890
|
-
},
|
|
891
|
-
rating: {
|
|
892
|
-
type: "object",
|
|
893
|
-
properties: {
|
|
894
|
-
score: { type: "number", minimum: 0, maximum: 1 },
|
|
895
|
-
label: { type: "string", enum: ["good", "bad", "neutral", "mixed"] },
|
|
896
|
-
notes: { type: "string" }
|
|
897
|
-
},
|
|
898
|
-
required: ["score", "notes"],
|
|
899
|
-
additionalProperties: false
|
|
900
|
-
},
|
|
901
|
-
by: { type: "string", enum: ["agent", "user", "downstream-judge"] },
|
|
902
|
-
capturedAt: { type: "string" },
|
|
903
|
-
namespace: { type: "string" }
|
|
904
|
-
},
|
|
905
|
-
required: ["refersTo", "rating", "by"],
|
|
906
|
-
additionalProperties: false
|
|
907
|
-
};
|
|
908
|
-
function validateDelegateFeedbackArgs(raw) {
|
|
909
|
-
if (raw === null || typeof raw !== "object") {
|
|
910
|
-
throw new TypeError("delegate_feedback: arguments must be an object");
|
|
911
|
-
}
|
|
912
|
-
const value = raw;
|
|
913
|
-
const refersTo = validateRefersTo(value.refersTo);
|
|
914
|
-
const rating = validateRating(value.rating);
|
|
915
|
-
const by = value.by;
|
|
916
|
-
if (by !== "agent" && by !== "user" && by !== "downstream-judge") {
|
|
917
|
-
throw new TypeError(
|
|
918
|
-
'delegate_feedback: `by` must be one of "agent" | "user" | "downstream-judge"'
|
|
919
|
-
);
|
|
920
|
-
}
|
|
921
|
-
const args = { refersTo, rating, by };
|
|
922
|
-
if (value.capturedAt !== void 0) {
|
|
923
|
-
if (typeof value.capturedAt !== "string" || Number.isNaN(Date.parse(value.capturedAt))) {
|
|
924
|
-
throw new TypeError("delegate_feedback: `capturedAt` must be an ISO datetime");
|
|
925
|
-
}
|
|
926
|
-
args.capturedAt = value.capturedAt;
|
|
927
|
-
}
|
|
928
|
-
if (typeof value.namespace === "string") args.namespace = value.namespace;
|
|
929
|
-
return args;
|
|
930
|
-
}
|
|
931
|
-
function validateRefersTo(raw) {
|
|
932
|
-
if (raw === null || typeof raw !== "object") {
|
|
933
|
-
throw new TypeError("delegate_feedback: `refersTo` must be an object");
|
|
934
|
-
}
|
|
935
|
-
const value = raw;
|
|
936
|
-
const kind = value.kind;
|
|
937
|
-
if (kind !== "delegation" && kind !== "artifact" && kind !== "outcome") {
|
|
938
|
-
throw new TypeError(
|
|
939
|
-
'delegate_feedback: `refersTo.kind` must be one of "delegation" | "artifact" | "outcome"'
|
|
940
|
-
);
|
|
941
|
-
}
|
|
942
|
-
const ref = value.ref;
|
|
943
|
-
if (typeof ref !== "string" || ref.trim().length === 0) {
|
|
944
|
-
throw new TypeError("delegate_feedback: `refersTo.ref` must be a non-empty string");
|
|
945
|
-
}
|
|
946
|
-
return { kind, ref: ref.trim() };
|
|
947
|
-
}
|
|
948
|
-
function validateRating(raw) {
|
|
949
|
-
if (raw === null || typeof raw !== "object") {
|
|
950
|
-
throw new TypeError("delegate_feedback: `rating` must be an object");
|
|
951
|
-
}
|
|
952
|
-
const value = raw;
|
|
953
|
-
const score = Number(value.score);
|
|
954
|
-
if (!Number.isFinite(score) || score < 0 || score > 1) {
|
|
955
|
-
throw new RangeError("delegate_feedback: `rating.score` must be a number in [0, 1]");
|
|
956
|
-
}
|
|
957
|
-
const notes = value.notes;
|
|
958
|
-
if (typeof notes !== "string") {
|
|
959
|
-
throw new TypeError("delegate_feedback: `rating.notes` must be a string");
|
|
960
|
-
}
|
|
961
|
-
const rating = { score, notes };
|
|
962
|
-
const label = value.label;
|
|
963
|
-
if (label !== void 0) {
|
|
964
|
-
if (label !== "good" && label !== "bad" && label !== "neutral" && label !== "mixed") {
|
|
965
|
-
throw new TypeError(
|
|
966
|
-
'delegate_feedback: `rating.label` must be one of "good" | "bad" | "neutral" | "mixed"'
|
|
967
|
-
);
|
|
968
|
-
}
|
|
969
|
-
rating.label = label;
|
|
970
|
-
}
|
|
971
|
-
return rating;
|
|
972
|
-
}
|
|
973
|
-
function createDelegateFeedbackHandler(options) {
|
|
974
|
-
const generateId = options.generateId ?? randomFeedbackId;
|
|
975
|
-
const now = options.now ?? (() => (/* @__PURE__ */ new Date()).toISOString());
|
|
976
|
-
return async (raw) => {
|
|
977
|
-
const args = validateDelegateFeedbackArgs(raw);
|
|
978
|
-
const id = generateId();
|
|
979
|
-
const event = {
|
|
980
|
-
id,
|
|
981
|
-
refersTo: args.refersTo,
|
|
982
|
-
rating: args.rating,
|
|
983
|
-
by: args.by,
|
|
984
|
-
capturedAt: args.capturedAt ?? now(),
|
|
985
|
-
namespace: args.namespace
|
|
986
|
-
};
|
|
987
|
-
await options.store.put(event);
|
|
988
|
-
if (args.refersTo.kind === "delegation") {
|
|
989
|
-
options.queue.attachFeedback(args.refersTo.ref, eventToSnapshot(event));
|
|
990
|
-
}
|
|
991
|
-
return { recorded: true, id };
|
|
992
|
-
};
|
|
993
|
-
}
|
|
994
|
-
function randomFeedbackId() {
|
|
995
|
-
const t = Date.now().toString(36);
|
|
996
|
-
const r = Math.random().toString(36).slice(2, 10);
|
|
997
|
-
return `fbk-${t}-${r}`;
|
|
998
|
-
}
|
|
999
|
-
|
|
1000
|
-
// src/mcp/tools/delegate-research.ts
|
|
1001
|
-
var DELEGATE_RESEARCH_TOOL_NAME = "delegate_research";
|
|
1002
|
-
var DELEGATE_RESEARCH_DESCRIPTION = [
|
|
1003
|
-
"Delegate a research question to specialist researcher agents that produce",
|
|
1004
|
-
"source-grounded, evidence-bearing knowledge items.",
|
|
1005
|
-
"",
|
|
1006
|
-
"Use when: you need to answer a factual question with external evidence \u2014",
|
|
1007
|
-
"audience research, competitive intelligence, recency-bound web searches,",
|
|
1008
|
-
"corpus / docs lookups. The researcher emits items[] with provenance, a",
|
|
1009
|
-
"citations[] index, and proposedWrites[] you decide whether to persist.",
|
|
1010
|
-
"",
|
|
1011
|
-
"Returns immediately with a taskId. Poll delegation_status to retrieve the",
|
|
1012
|
-
"items + verdict. Identical inputs return the same taskId \u2014 safe to retry.",
|
|
1013
|
-
"",
|
|
1014
|
-
"When variants > 1, multiple researcher harnesses run in parallel and the",
|
|
1015
|
-
"highest-scoring valid output wins (citation density \xD7 source diversity \xD7",
|
|
1016
|
-
"recency match \xD7 gap coverage). Use variants when answers might disagree.",
|
|
1017
|
-
"",
|
|
1018
|
-
"Multi-tenant isolation: every item carries `namespace`. The validator",
|
|
1019
|
-
"hard-fails when any item is scoped outside `namespace`. Never pass another",
|
|
1020
|
-
"tenant's namespace."
|
|
1021
|
-
].join("\n");
|
|
1022
|
-
var VALID_SOURCES = ["web", "corpus", "twitter", "github", "docs"];
|
|
1023
|
-
var DELEGATE_RESEARCH_INPUT_SCHEMA = {
|
|
1024
|
-
type: "object",
|
|
1025
|
-
properties: {
|
|
1026
|
-
question: {
|
|
1027
|
-
type: "string",
|
|
1028
|
-
description: "The research question to answer."
|
|
1029
|
-
},
|
|
1030
|
-
namespace: {
|
|
1031
|
-
type: "string",
|
|
1032
|
-
description: "Multi-tenant scope (customer-id, workspace-id). REQUIRED."
|
|
1033
|
-
},
|
|
1034
|
-
scope: { type: "string", description: 'Bound, e.g. "audience for cpg-founder ICP".' },
|
|
1035
|
-
sources: {
|
|
1036
|
-
type: "array",
|
|
1037
|
-
items: { type: "string", enum: [...VALID_SOURCES] }
|
|
1038
|
-
},
|
|
1039
|
-
variants: { type: "integer", minimum: 1, maximum: 8 },
|
|
1040
|
-
config: {
|
|
1041
|
-
type: "object",
|
|
1042
|
-
properties: {
|
|
1043
|
-
recencyWindow: {
|
|
1044
|
-
type: "object",
|
|
1045
|
-
properties: {
|
|
1046
|
-
since: { type: "string", description: "ISO datetime" },
|
|
1047
|
-
until: { type: "string", description: "ISO datetime" }
|
|
1048
|
-
},
|
|
1049
|
-
additionalProperties: false
|
|
1050
|
-
},
|
|
1051
|
-
maxItems: { type: "integer", minimum: 1 },
|
|
1052
|
-
minConfidence: { type: "number", minimum: 0, maximum: 1 }
|
|
1053
|
-
},
|
|
1054
|
-
additionalProperties: false
|
|
1055
|
-
}
|
|
1056
|
-
},
|
|
1057
|
-
required: ["question", "namespace"],
|
|
1058
|
-
additionalProperties: false
|
|
1059
|
-
};
|
|
1060
|
-
var SINGLE_VARIANT_ESTIMATE_MS2 = 4 * 60 * 1e3;
|
|
1061
|
-
var FANOUT_PER_VARIANT_ESTIMATE_MS2 = 6 * 60 * 1e3;
|
|
1062
|
-
function validateDelegateResearchArgs(raw) {
|
|
1063
|
-
if (raw === null || typeof raw !== "object") {
|
|
1064
|
-
throw new TypeError("delegate_research: arguments must be an object");
|
|
1065
|
-
}
|
|
1066
|
-
const value = raw;
|
|
1067
|
-
const question = value.question;
|
|
1068
|
-
if (typeof question !== "string" || question.trim().length === 0) {
|
|
1069
|
-
throw new TypeError("delegate_research: `question` must be a non-empty string");
|
|
1070
|
-
}
|
|
1071
|
-
const namespace = value.namespace;
|
|
1072
|
-
if (typeof namespace !== "string" || namespace.trim().length === 0) {
|
|
1073
|
-
throw new TypeError("delegate_research: `namespace` is required");
|
|
1074
|
-
}
|
|
1075
|
-
const args = { question: question.trim(), namespace: namespace.trim() };
|
|
1076
|
-
if (typeof value.scope === "string") args.scope = value.scope;
|
|
1077
|
-
if (value.sources !== void 0) {
|
|
1078
|
-
if (!Array.isArray(value.sources)) {
|
|
1079
|
-
throw new TypeError("delegate_research: `sources` must be a string array");
|
|
1080
|
-
}
|
|
1081
|
-
const sources = value.sources.map((src, i) => {
|
|
1082
|
-
if (typeof src !== "string" || !VALID_SOURCES.includes(src)) {
|
|
1083
|
-
throw new TypeError(
|
|
1084
|
-
`delegate_research: sources[${i}] must be one of ${VALID_SOURCES.join("|")}`
|
|
1085
|
-
);
|
|
1086
|
-
}
|
|
1087
|
-
return src;
|
|
1088
|
-
});
|
|
1089
|
-
args.sources = sources;
|
|
1090
|
-
}
|
|
1091
|
-
if (value.variants !== void 0) {
|
|
1092
|
-
const variants = Number(value.variants);
|
|
1093
|
-
if (!Number.isFinite(variants) || variants < 1 || variants > 8) {
|
|
1094
|
-
throw new RangeError("delegate_research: `variants` must be an integer in [1, 8]");
|
|
1095
|
-
}
|
|
1096
|
-
args.variants = Math.trunc(variants);
|
|
1097
|
-
}
|
|
1098
|
-
if (value.config !== void 0) {
|
|
1099
|
-
args.config = validateConfig2(value.config);
|
|
1100
|
-
}
|
|
1101
|
-
return args;
|
|
1102
|
-
}
|
|
1103
|
-
function validateConfig2(raw) {
|
|
1104
|
-
if (raw === null || typeof raw !== "object") {
|
|
1105
|
-
throw new TypeError("delegate_research: `config` must be an object");
|
|
1106
|
-
}
|
|
1107
|
-
const value = raw;
|
|
1108
|
-
const out = {};
|
|
1109
|
-
if (value.recencyWindow !== void 0) {
|
|
1110
|
-
if (value.recencyWindow === null || typeof value.recencyWindow !== "object") {
|
|
1111
|
-
throw new TypeError("delegate_research: `config.recencyWindow` must be an object");
|
|
1112
|
-
}
|
|
1113
|
-
const window = value.recencyWindow;
|
|
1114
|
-
const windowOut = {};
|
|
1115
|
-
if (window.since !== void 0) {
|
|
1116
|
-
if (typeof window.since !== "string" || Number.isNaN(Date.parse(window.since))) {
|
|
1117
|
-
throw new TypeError("delegate_research: `recencyWindow.since` must be an ISO datetime");
|
|
1118
|
-
}
|
|
1119
|
-
windowOut.since = window.since;
|
|
1120
|
-
}
|
|
1121
|
-
if (window.until !== void 0) {
|
|
1122
|
-
if (typeof window.until !== "string" || Number.isNaN(Date.parse(window.until))) {
|
|
1123
|
-
throw new TypeError("delegate_research: `recencyWindow.until` must be an ISO datetime");
|
|
1124
|
-
}
|
|
1125
|
-
windowOut.until = window.until;
|
|
1126
|
-
}
|
|
1127
|
-
out.recencyWindow = windowOut;
|
|
1128
|
-
}
|
|
1129
|
-
if (value.maxItems !== void 0) {
|
|
1130
|
-
const n = Number(value.maxItems);
|
|
1131
|
-
if (!Number.isFinite(n) || n < 1) {
|
|
1132
|
-
throw new RangeError("delegate_research: `config.maxItems` must be a positive integer");
|
|
1133
|
-
}
|
|
1134
|
-
out.maxItems = Math.trunc(n);
|
|
1135
|
-
}
|
|
1136
|
-
if (value.minConfidence !== void 0) {
|
|
1137
|
-
const n = Number(value.minConfidence);
|
|
1138
|
-
if (!Number.isFinite(n) || n < 0 || n > 1) {
|
|
1139
|
-
throw new RangeError("delegate_research: `config.minConfidence` must be in [0, 1]");
|
|
1140
|
-
}
|
|
1141
|
-
out.minConfidence = n;
|
|
1142
|
-
}
|
|
1143
|
-
return out;
|
|
1144
|
-
}
|
|
1145
|
-
function createDelegateResearchHandler(options) {
|
|
1146
|
-
const estimateDurationMs = options.estimateDurationMs ?? defaultEstimate2;
|
|
1147
|
-
return async (raw) => {
|
|
1148
|
-
const args = validateDelegateResearchArgs(raw);
|
|
1149
|
-
const idempotencyKey = hashIdempotencyInput({
|
|
1150
|
-
profile: "researcher",
|
|
1151
|
-
question: args.question,
|
|
1152
|
-
namespace: args.namespace,
|
|
1153
|
-
scope: args.scope,
|
|
1154
|
-
sources: args.sources,
|
|
1155
|
-
variants: args.variants ?? 1,
|
|
1156
|
-
config: args.config
|
|
1157
|
-
});
|
|
1158
|
-
const detached = options.detachedDispatch === true && (args.variants ?? 1) <= 1;
|
|
1159
|
-
const submitted = options.queue.submit({
|
|
1160
|
-
profile: "researcher",
|
|
1161
|
-
args,
|
|
1162
|
-
namespace: args.namespace,
|
|
1163
|
-
idempotencyKey,
|
|
1164
|
-
...detached ? {
|
|
1165
|
-
detachedSessionRef: formatDetachedSessionRef({
|
|
1166
|
-
sessionId: `dlg-turn-research-${idempotencyKey}`
|
|
1167
|
-
})
|
|
1168
|
-
} : {},
|
|
1169
|
-
run: async (ctx) => options.delegate(args, ctx)
|
|
1170
|
-
});
|
|
1171
|
-
return {
|
|
1172
|
-
taskId: submitted.taskId,
|
|
1173
|
-
estimatedDurationMs: estimateDurationMs(args)
|
|
1174
|
-
};
|
|
1175
|
-
};
|
|
1176
|
-
}
|
|
1177
|
-
function defaultEstimate2(args) {
|
|
1178
|
-
const variants = Math.max(1, args.variants ?? 1);
|
|
1179
|
-
if (variants === 1) return SINGLE_VARIANT_ESTIMATE_MS2;
|
|
1180
|
-
return FANOUT_PER_VARIANT_ESTIMATE_MS2;
|
|
1181
|
-
}
|
|
1182
|
-
|
|
1183
|
-
// src/mcp/tools/delegation-history.ts
|
|
1184
|
-
var DELEGATION_HISTORY_TOOL_NAME = "delegation_history";
|
|
1185
|
-
var DELEGATION_HISTORY_DESCRIPTION = [
|
|
1186
|
-
"Read past delegations newest-first. Each entry carries the original",
|
|
1187
|
-
"arguments, current status, cost, and any feedback attached via",
|
|
1188
|
-
"delegate_feedback.",
|
|
1189
|
-
"",
|
|
1190
|
-
'Use when: you want to introspect prior decisions \u2014 "have I asked this',
|
|
1191
|
-
"question before?",
|
|
1192
|
-
"did the last patch land?",
|
|
1193
|
-
"what's the historical",
|
|
1194
|
-
'success rate of coder delegations on this repo?". Feed the results back',
|
|
1195
|
-
"into your own routing and calibration.",
|
|
1196
|
-
"",
|
|
1197
|
-
"Each entry carries `hasTrace` \u2014 when true, the full loop-trace span tree",
|
|
1198
|
-
"is retrievable via delegation_status { taskId, includeTrace: true }.",
|
|
1199
|
-
"",
|
|
1200
|
-
'Filters: `namespace` (multi-tenant scope), `profile` ("coder" | "researcher"),',
|
|
1201
|
-
"`since` (ISO date \u2014 only delegations started at-or-after). `limit` defaults",
|
|
1202
|
-
"to 50, capped at 500."
|
|
1203
|
-
].join("\n");
|
|
1204
|
-
var DELEGATION_HISTORY_INPUT_SCHEMA = {
|
|
1205
|
-
type: "object",
|
|
1206
|
-
properties: {
|
|
1207
|
-
namespace: { type: "string" },
|
|
1208
|
-
profile: { type: "string", enum: ["coder", "researcher"] },
|
|
1209
|
-
since: { type: "string", description: "ISO datetime \u2014 earliest startedAt to include." },
|
|
1210
|
-
limit: { type: "integer", minimum: 1, maximum: 500 }
|
|
1211
|
-
},
|
|
1212
|
-
additionalProperties: false
|
|
1213
|
-
};
|
|
1214
|
-
function validateDelegationHistoryArgs(raw) {
|
|
1215
|
-
if (raw === void 0 || raw === null) return {};
|
|
1216
|
-
if (typeof raw !== "object") {
|
|
1217
|
-
throw new TypeError("delegation_history: arguments must be an object");
|
|
1218
|
-
}
|
|
1219
|
-
const value = raw;
|
|
1220
|
-
const out = {};
|
|
1221
|
-
if (value.namespace !== void 0) {
|
|
1222
|
-
if (typeof value.namespace !== "string") {
|
|
1223
|
-
throw new TypeError("delegation_history: `namespace` must be a string");
|
|
1224
|
-
}
|
|
1225
|
-
out.namespace = value.namespace;
|
|
1226
|
-
}
|
|
1227
|
-
if (value.profile !== void 0) {
|
|
1228
|
-
if (value.profile !== "coder" && value.profile !== "researcher") {
|
|
1229
|
-
throw new TypeError('delegation_history: `profile` must be "coder" or "researcher"');
|
|
1230
|
-
}
|
|
1231
|
-
out.profile = value.profile;
|
|
1232
|
-
}
|
|
1233
|
-
if (value.since !== void 0) {
|
|
1234
|
-
if (typeof value.since !== "string" || Number.isNaN(Date.parse(value.since))) {
|
|
1235
|
-
throw new TypeError("delegation_history: `since` must be an ISO datetime");
|
|
1236
|
-
}
|
|
1237
|
-
out.since = value.since;
|
|
1238
|
-
}
|
|
1239
|
-
if (value.limit !== void 0) {
|
|
1240
|
-
const n = Number(value.limit);
|
|
1241
|
-
if (!Number.isFinite(n) || n < 1 || n > 500) {
|
|
1242
|
-
throw new RangeError("delegation_history: `limit` must be an integer in [1, 500]");
|
|
1243
|
-
}
|
|
1244
|
-
out.limit = Math.trunc(n);
|
|
1245
|
-
}
|
|
1246
|
-
return out;
|
|
1247
|
-
}
|
|
1248
|
-
function createDelegationHistoryHandler(options) {
|
|
1249
|
-
return async (raw) => {
|
|
1250
|
-
const args = validateDelegationHistoryArgs(raw);
|
|
1251
|
-
return { delegations: options.queue.history(args) };
|
|
1252
|
-
};
|
|
1253
|
-
}
|
|
1254
|
-
|
|
1255
|
-
// src/mcp/tools/delegation-status.ts
|
|
1256
|
-
var DELEGATION_STATUS_TOOL_NAME = "delegation_status";
|
|
1257
|
-
var DELEGATION_STATUS_DESCRIPTION = [
|
|
1258
|
-
"Poll the status of an async delegation. Returns the current state",
|
|
1259
|
-
"(pending | running | completed | failed | cancelled), optional progress,",
|
|
1260
|
-
'and the final result when status === "completed".',
|
|
1261
|
-
"",
|
|
1262
|
-
"Use when: you previously called delegate_code or delegate_research and",
|
|
1263
|
-
"need to know whether the work is done. The agent's right rhythm is to",
|
|
1264
|
-
"call this every minute or two while waiting; do not busy-poll.",
|
|
1265
|
-
"",
|
|
1266
|
-
"For a completed coder task, `result.output` is a CoderOutput with branch,",
|
|
1267
|
-
"patch, test/typecheck results, and diff stats. For a completed research",
|
|
1268
|
-
"task, `result.output` is the items + citations + proposedWrites bundle.",
|
|
1269
|
-
"",
|
|
1270
|
-
"Pass includeTrace: true to also receive the journaled loop-trace span",
|
|
1271
|
-
"tree (loop \u2192 round \u2192 iteration, with placement/cost/verdict metadata).",
|
|
1272
|
-
"Default false \u2014 keep routine polls light.",
|
|
1273
|
-
"",
|
|
1274
|
-
"Throws NotFoundError when taskId is unknown \u2014 never silently returns",
|
|
1275
|
-
"`pending` for a typo."
|
|
1276
|
-
].join("\n");
|
|
1277
|
-
var DELEGATION_STATUS_INPUT_SCHEMA = {
|
|
1278
|
-
type: "object",
|
|
1279
|
-
properties: {
|
|
1280
|
-
taskId: { type: "string", description: "Returned by delegate_code / delegate_research." },
|
|
1281
|
-
includeTrace: {
|
|
1282
|
-
type: "boolean",
|
|
1283
|
-
description: "Also return the journaled loop-trace span tree for this delegation. Default false."
|
|
1284
|
-
}
|
|
1285
|
-
},
|
|
1286
|
-
required: ["taskId"],
|
|
1287
|
-
additionalProperties: false
|
|
1288
|
-
};
|
|
1289
|
-
function validateDelegationStatusArgs(raw) {
|
|
1290
|
-
if (raw === null || typeof raw !== "object") {
|
|
1291
|
-
throw new TypeError("delegation_status: arguments must be an object");
|
|
1292
|
-
}
|
|
1293
|
-
const value = raw;
|
|
1294
|
-
const taskId = value.taskId;
|
|
1295
|
-
if (typeof taskId !== "string" || taskId.trim().length === 0) {
|
|
1296
|
-
throw new TypeError("delegation_status: `taskId` must be a non-empty string");
|
|
1297
|
-
}
|
|
1298
|
-
const out = { taskId: taskId.trim() };
|
|
1299
|
-
if (value.includeTrace !== void 0) {
|
|
1300
|
-
if (typeof value.includeTrace !== "boolean") {
|
|
1301
|
-
throw new TypeError("delegation_status: `includeTrace` must be a boolean");
|
|
1302
|
-
}
|
|
1303
|
-
out.includeTrace = value.includeTrace;
|
|
1304
|
-
}
|
|
1305
|
-
return out;
|
|
1306
|
-
}
|
|
1307
|
-
function createDelegationStatusHandler(options) {
|
|
1308
|
-
return async (raw) => {
|
|
1309
|
-
const args = validateDelegationStatusArgs(raw);
|
|
1310
|
-
const status = options.queue.status(
|
|
1311
|
-
args.taskId,
|
|
1312
|
-
args.includeTrace !== void 0 ? { includeTrace: args.includeTrace } : void 0
|
|
1313
|
-
);
|
|
1314
|
-
if (!status) {
|
|
1315
|
-
throw new NotFoundError(`delegation_status: unknown taskId "${args.taskId}"`);
|
|
1316
|
-
}
|
|
1317
|
-
return status;
|
|
1318
|
-
};
|
|
1319
|
-
}
|
|
1320
|
-
|
|
1321
|
-
export {
|
|
1322
|
-
DelegationStateCorruptError,
|
|
1323
|
-
DelegationPersistenceError,
|
|
1324
|
-
InMemoryDelegationStore,
|
|
1325
|
-
FileDelegationStore,
|
|
1326
|
-
DelegationTaskQueue,
|
|
1327
|
-
hashIdempotencyInput,
|
|
1328
|
-
DELEGATE_CODE_TOOL_NAME,
|
|
1329
|
-
DELEGATE_CODE_DESCRIPTION,
|
|
1330
|
-
DELEGATE_CODE_INPUT_SCHEMA,
|
|
1331
|
-
validateDelegateCodeArgs,
|
|
1332
|
-
createDelegateCodeHandler,
|
|
1333
|
-
InMemoryFeedbackStore,
|
|
1334
|
-
eventToSnapshot,
|
|
1335
|
-
DELEGATE_FEEDBACK_TOOL_NAME,
|
|
1336
|
-
DELEGATE_FEEDBACK_DESCRIPTION,
|
|
1337
|
-
DELEGATE_FEEDBACK_INPUT_SCHEMA,
|
|
1338
|
-
validateDelegateFeedbackArgs,
|
|
1339
|
-
createDelegateFeedbackHandler,
|
|
1340
|
-
DELEGATE_RESEARCH_TOOL_NAME,
|
|
1341
|
-
DELEGATE_RESEARCH_DESCRIPTION,
|
|
1342
|
-
DELEGATE_RESEARCH_INPUT_SCHEMA,
|
|
1343
|
-
validateDelegateResearchArgs,
|
|
1344
|
-
createDelegateResearchHandler,
|
|
1345
|
-
DELEGATION_HISTORY_TOOL_NAME,
|
|
1346
|
-
DELEGATION_HISTORY_DESCRIPTION,
|
|
1347
|
-
DELEGATION_HISTORY_INPUT_SCHEMA,
|
|
1348
|
-
validateDelegationHistoryArgs,
|
|
1349
|
-
createDelegationHistoryHandler,
|
|
1350
|
-
DELEGATION_STATUS_TOOL_NAME,
|
|
1351
|
-
DELEGATION_STATUS_DESCRIPTION,
|
|
1352
|
-
DELEGATION_STATUS_INPUT_SCHEMA,
|
|
1353
|
-
validateDelegationStatusArgs,
|
|
1354
|
-
createDelegationStatusHandler
|
|
1355
|
-
};
|
|
1356
|
-
//# sourceMappingURL=chunk-BERLUBAP.js.map
|