@tangle-network/agent-runtime 0.47.0 → 0.49.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 +79 -15
- package/dist/agent.js +1 -1
- package/dist/chunk-GHX7XOJ2.js +433 -0
- package/dist/chunk-GHX7XOJ2.js.map +1 -0
- package/dist/{chunk-T4OQQEE3.js → chunk-IQS4HI3F.js} +14 -5
- package/dist/chunk-IQS4HI3F.js.map +1 -0
- package/dist/{chunk-72JQCHOZ.js → chunk-PXUTIMGJ.js} +2318 -237
- package/dist/chunk-PXUTIMGJ.js.map +1 -0
- package/dist/{chunk-MGFEUYOH.js → chunk-U2VEWKKK.js} +3 -3
- package/dist/{chunk-JNPK46YH.js → chunk-VIEDXELL.js} +408 -6
- package/dist/chunk-VIEDXELL.js.map +1 -0
- package/dist/{chunk-VR4JIC5H.js → chunk-XTEZ3YJ4.js} +2 -2
- package/dist/index.d.ts +29 -4
- package/dist/index.js +109 -21
- package/dist/index.js.map +1 -1
- package/dist/kb-gate-CsXpNRk7.d.ts +1145 -0
- package/dist/{loop-runner-bin-DEm4roYF.d.ts → loop-runner-bin-Cgn0A-NW.d.ts} +1 -1
- package/dist/loop-runner-bin.d.ts +2 -2
- package/dist/loop-runner-bin.js +3 -3
- package/dist/loops.d.ts +3 -3
- package/dist/loops.js +57 -1
- package/dist/mcp/bin.js +187 -24
- package/dist/mcp/bin.js.map +1 -1
- package/dist/mcp/index.d.ts +28 -125
- package/dist/mcp/index.js +28 -6
- package/dist/mcp/index.js.map +1 -1
- package/dist/platform.js +2 -2
- package/dist/platform.js.map +1 -1
- package/dist/runtime.d.ts +1100 -62
- package/dist/runtime.js +57 -1
- package/dist/{types-Cbx3dNK5.d.ts → types-BpDfCPUp.d.ts} +1 -1
- package/dist/workflow.js +1 -1
- package/package.json +7 -6
- package/dist/chunk-5YDS7BLC.js +0 -218
- package/dist/chunk-5YDS7BLC.js.map +0 -1
- package/dist/chunk-72JQCHOZ.js.map +0 -1
- package/dist/chunk-JNPK46YH.js.map +0 -1
- package/dist/chunk-T4OQQEE3.js.map +0 -1
- package/dist/kb-gate-51BlLlVM.d.ts +0 -529
- /package/dist/{chunk-MGFEUYOH.js.map → chunk-U2VEWKKK.js.map} +0 -0
- /package/dist/{chunk-VR4JIC5H.js.map → chunk-XTEZ3YJ4.js.map} +0 -0
|
@@ -3,14 +3,14 @@ import {
|
|
|
3
3
|
} from "./chunk-FNMGYYSS.js";
|
|
4
4
|
import {
|
|
5
5
|
createDefaultCoderDelegate
|
|
6
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-GHX7XOJ2.js";
|
|
7
7
|
import {
|
|
8
8
|
runAnalystLoop
|
|
9
9
|
} from "./chunk-HNUXAZIJ.js";
|
|
10
10
|
import {
|
|
11
11
|
createDriver,
|
|
12
12
|
runLoop
|
|
13
|
-
} from "./chunk-
|
|
13
|
+
} from "./chunk-PXUTIMGJ.js";
|
|
14
14
|
import {
|
|
15
15
|
ConfigError
|
|
16
16
|
} from "./chunk-GSUO5QS6.js";
|
|
@@ -200,4 +200,4 @@ export {
|
|
|
200
200
|
runLoopRunnerCli,
|
|
201
201
|
parseLoopRunnerArgv
|
|
202
202
|
};
|
|
203
|
-
//# sourceMappingURL=chunk-
|
|
203
|
+
//# sourceMappingURL=chunk-U2VEWKKK.js.map
|
|
@@ -1,23 +1,231 @@
|
|
|
1
1
|
import {
|
|
2
|
-
|
|
2
|
+
formatDetachedSessionRef
|
|
3
|
+
} from "./chunk-GHX7XOJ2.js";
|
|
4
|
+
import {
|
|
5
|
+
AgentEvalError,
|
|
6
|
+
NotFoundError,
|
|
7
|
+
ValidationError
|
|
3
8
|
} from "./chunk-GSUO5QS6.js";
|
|
4
9
|
|
|
10
|
+
// src/mcp/delegation-store.ts
|
|
11
|
+
import { mkdir, readFile, rename, writeFile } from "fs/promises";
|
|
12
|
+
import { dirname } from "path";
|
|
13
|
+
var DelegationStateCorruptError = class extends AgentEvalError {
|
|
14
|
+
constructor(message, options) {
|
|
15
|
+
super("validation", message, options);
|
|
16
|
+
}
|
|
17
|
+
};
|
|
18
|
+
var DelegationPersistenceError = class extends AgentEvalError {
|
|
19
|
+
constructor(message, options) {
|
|
20
|
+
super("config", message, options);
|
|
21
|
+
}
|
|
22
|
+
};
|
|
23
|
+
var InMemoryDelegationStore = class {
|
|
24
|
+
records = /* @__PURE__ */ new Map();
|
|
25
|
+
async loadAll() {
|
|
26
|
+
return [...this.records.values()].map(cloneRecord);
|
|
27
|
+
}
|
|
28
|
+
async upsert(record) {
|
|
29
|
+
this.records.set(record.taskId, cloneRecord(record));
|
|
30
|
+
}
|
|
31
|
+
async lookupIdempotencyKey(key) {
|
|
32
|
+
for (const record of this.records.values()) {
|
|
33
|
+
if (record.idempotencyKey === key) return record.taskId;
|
|
34
|
+
}
|
|
35
|
+
return void 0;
|
|
36
|
+
}
|
|
37
|
+
async remove(taskIds) {
|
|
38
|
+
for (const taskId of taskIds) this.records.delete(taskId);
|
|
39
|
+
}
|
|
40
|
+
};
|
|
41
|
+
var STATE_FORMAT_VERSION = 1;
|
|
42
|
+
var FileDelegationStore = class {
|
|
43
|
+
filePath;
|
|
44
|
+
recoverCorrupt;
|
|
45
|
+
records = /* @__PURE__ */ new Map();
|
|
46
|
+
loaded = false;
|
|
47
|
+
writeTail = Promise.resolve();
|
|
48
|
+
tmpSeq = 0;
|
|
49
|
+
constructor(options) {
|
|
50
|
+
this.filePath = options.filePath;
|
|
51
|
+
this.recoverCorrupt = options.recoverCorrupt ?? false;
|
|
52
|
+
}
|
|
53
|
+
async loadAll() {
|
|
54
|
+
let raw;
|
|
55
|
+
try {
|
|
56
|
+
raw = await readFile(this.filePath, "utf8");
|
|
57
|
+
} catch (err) {
|
|
58
|
+
if (err.code === "ENOENT") {
|
|
59
|
+
this.loaded = true;
|
|
60
|
+
return [];
|
|
61
|
+
}
|
|
62
|
+
throw new DelegationPersistenceError(
|
|
63
|
+
`FileDelegationStore: failed to read ${this.filePath}: ${errorMessage(err)}`,
|
|
64
|
+
{ cause: err }
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
let state;
|
|
68
|
+
try {
|
|
69
|
+
state = parsePersistedState(raw);
|
|
70
|
+
} catch (err) {
|
|
71
|
+
if (!this.recoverCorrupt) {
|
|
72
|
+
throw new DelegationStateCorruptError(
|
|
73
|
+
`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.`,
|
|
74
|
+
{ cause: err }
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
const archivePath = `${this.filePath}.corrupt-${Date.now()}`;
|
|
78
|
+
await rename(this.filePath, archivePath);
|
|
79
|
+
this.loaded = true;
|
|
80
|
+
return [];
|
|
81
|
+
}
|
|
82
|
+
this.records.clear();
|
|
83
|
+
for (const record of state.records) this.records.set(record.taskId, record);
|
|
84
|
+
this.loaded = true;
|
|
85
|
+
return [...this.records.values()].map(cloneRecord);
|
|
86
|
+
}
|
|
87
|
+
async upsert(record) {
|
|
88
|
+
this.assertLoaded("upsert");
|
|
89
|
+
this.records.set(record.taskId, cloneRecord(record));
|
|
90
|
+
await this.enqueueWrite();
|
|
91
|
+
}
|
|
92
|
+
async lookupIdempotencyKey(key) {
|
|
93
|
+
this.assertLoaded("lookupIdempotencyKey");
|
|
94
|
+
for (const record of this.records.values()) {
|
|
95
|
+
if (record.idempotencyKey === key) return record.taskId;
|
|
96
|
+
}
|
|
97
|
+
return void 0;
|
|
98
|
+
}
|
|
99
|
+
async remove(taskIds) {
|
|
100
|
+
this.assertLoaded("remove");
|
|
101
|
+
let changed = false;
|
|
102
|
+
for (const taskId of taskIds) {
|
|
103
|
+
if (this.records.delete(taskId)) changed = true;
|
|
104
|
+
}
|
|
105
|
+
if (changed) await this.enqueueWrite();
|
|
106
|
+
}
|
|
107
|
+
assertLoaded(op) {
|
|
108
|
+
if (this.loaded) return;
|
|
109
|
+
throw new DelegationPersistenceError(
|
|
110
|
+
`FileDelegationStore: ${op} called before loadAll() \u2014 the on-disk state has not been read yet`
|
|
111
|
+
);
|
|
112
|
+
}
|
|
113
|
+
enqueueWrite() {
|
|
114
|
+
const write = this.writeTail.then(() => this.writeSnapshot());
|
|
115
|
+
this.writeTail = write.catch(() => {
|
|
116
|
+
});
|
|
117
|
+
return write;
|
|
118
|
+
}
|
|
119
|
+
async writeSnapshot() {
|
|
120
|
+
const state = {
|
|
121
|
+
version: STATE_FORMAT_VERSION,
|
|
122
|
+
records: [...this.records.values()]
|
|
123
|
+
};
|
|
124
|
+
const payload = `${JSON.stringify(state)}
|
|
125
|
+
`;
|
|
126
|
+
this.tmpSeq += 1;
|
|
127
|
+
const tmpPath = `${this.filePath}.tmp-${process.pid}-${this.tmpSeq}`;
|
|
128
|
+
try {
|
|
129
|
+
await mkdir(dirname(this.filePath), { recursive: true });
|
|
130
|
+
await writeFile(tmpPath, payload, "utf8");
|
|
131
|
+
await rename(tmpPath, this.filePath);
|
|
132
|
+
} catch (err) {
|
|
133
|
+
throw new DelegationPersistenceError(
|
|
134
|
+
`FileDelegationStore: failed to write ${this.filePath}: ${errorMessage(err)}`,
|
|
135
|
+
{ cause: err }
|
|
136
|
+
);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
};
|
|
140
|
+
function parsePersistedState(raw) {
|
|
141
|
+
const parsed = JSON.parse(raw);
|
|
142
|
+
if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
143
|
+
throw new Error("top-level value is not an object");
|
|
144
|
+
}
|
|
145
|
+
const state = parsed;
|
|
146
|
+
if (state.version !== STATE_FORMAT_VERSION) {
|
|
147
|
+
throw new Error(`unsupported state version ${JSON.stringify(state.version)}`);
|
|
148
|
+
}
|
|
149
|
+
if (!Array.isArray(state.records)) {
|
|
150
|
+
throw new Error("`records` is not an array");
|
|
151
|
+
}
|
|
152
|
+
for (const record of state.records) {
|
|
153
|
+
if (record === null || typeof record !== "object") {
|
|
154
|
+
throw new Error("a record entry is not an object");
|
|
155
|
+
}
|
|
156
|
+
const candidate = record;
|
|
157
|
+
if (typeof candidate.taskId !== "string" || typeof candidate.status !== "string") {
|
|
158
|
+
throw new Error("a record entry is missing `taskId`/`status`");
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
return { version: STATE_FORMAT_VERSION, records: state.records };
|
|
162
|
+
}
|
|
163
|
+
function cloneRecord(record) {
|
|
164
|
+
return structuredClone(record);
|
|
165
|
+
}
|
|
166
|
+
function errorMessage(err) {
|
|
167
|
+
return err instanceof Error ? err.message : String(err);
|
|
168
|
+
}
|
|
169
|
+
|
|
5
170
|
// src/mcp/task-queue.ts
|
|
6
|
-
var DelegationTaskQueue = class {
|
|
171
|
+
var DelegationTaskQueue = class _DelegationTaskQueue {
|
|
7
172
|
records = /* @__PURE__ */ new Map();
|
|
8
173
|
controllers = /* @__PURE__ */ new Map();
|
|
9
174
|
byIdempotencyKey = /* @__PURE__ */ new Map();
|
|
10
175
|
generateId;
|
|
11
176
|
now;
|
|
177
|
+
store;
|
|
178
|
+
resumeDelegate;
|
|
179
|
+
maxTerminalRecords;
|
|
180
|
+
onPersistError;
|
|
181
|
+
persistTail = Promise.resolve();
|
|
182
|
+
persistFailure;
|
|
12
183
|
constructor(options = {}) {
|
|
13
184
|
this.generateId = options.generateId ?? randomTaskId;
|
|
14
185
|
this.now = options.now ?? (() => (/* @__PURE__ */ new Date()).toISOString());
|
|
186
|
+
this.store = options.store ?? new InMemoryDelegationStore();
|
|
187
|
+
this.resumeDelegate = options.resumeDelegate;
|
|
188
|
+
if (options.maxTerminalRecords !== void 0) {
|
|
189
|
+
if (!Number.isInteger(options.maxTerminalRecords) || options.maxTerminalRecords < 1) {
|
|
190
|
+
throw new ValidationError(
|
|
191
|
+
`DelegationTaskQueue: maxTerminalRecords must be a positive integer, got ${String(options.maxTerminalRecords)}`
|
|
192
|
+
);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
this.maxTerminalRecords = options.maxTerminalRecords ?? Number.POSITIVE_INFINITY;
|
|
196
|
+
this.onPersistError = options.onPersistError ?? ((error) => {
|
|
197
|
+
queueMicrotask(() => {
|
|
198
|
+
throw error;
|
|
199
|
+
});
|
|
200
|
+
});
|
|
201
|
+
}
|
|
202
|
+
/**
|
|
203
|
+
* Construct a queue from previously-persisted state. Loads every record
|
|
204
|
+
* from `options.store`, rebuilds the idempotency index (so a re-submitted
|
|
205
|
+
* identical task returns the prior taskId and its terminal state), then:
|
|
206
|
+
*
|
|
207
|
+
* - terminal records stay queryable via `status()` / `history()`
|
|
208
|
+
* - in-flight records with a `detachedSessionRef` re-attach through
|
|
209
|
+
* `options.resumeDelegate` and report `running`
|
|
210
|
+
* - other in-flight records settle as failed — their driver died with
|
|
211
|
+
* the previous process and the result is unrecoverable
|
|
212
|
+
*
|
|
213
|
+
* The retention cap applies to the loaded set as well.
|
|
214
|
+
*/
|
|
215
|
+
static async restore(options = {}) {
|
|
216
|
+
const queue = new _DelegationTaskQueue(options);
|
|
217
|
+
const loaded = await queue.store.loadAll();
|
|
218
|
+
queue.rehydrate(loaded);
|
|
219
|
+
return queue;
|
|
15
220
|
}
|
|
16
221
|
/**
|
|
17
222
|
* Kick off a delegation in the background. Returns immediately. The
|
|
18
|
-
* `taskId` is queryable via `status` once this method returns.
|
|
223
|
+
* `taskId` is queryable via `status` once this method returns. Throws
|
|
224
|
+
* the recorded `DelegationPersistenceError` once the store has failed —
|
|
225
|
+
* the queue does not accept work it cannot journal.
|
|
19
226
|
*/
|
|
20
227
|
submit(input) {
|
|
228
|
+
if (this.persistFailure) throw this.persistFailure;
|
|
21
229
|
if (input.idempotencyKey) {
|
|
22
230
|
const existing = this.byIdempotencyKey.get(input.idempotencyKey);
|
|
23
231
|
if (existing && this.records.has(existing)) {
|
|
@@ -34,11 +242,13 @@ var DelegationTaskQueue = class {
|
|
|
34
242
|
status: "pending",
|
|
35
243
|
startedAt: this.now(),
|
|
36
244
|
feedback: [],
|
|
37
|
-
idempotencyKey: input.idempotencyKey
|
|
245
|
+
idempotencyKey: input.idempotencyKey,
|
|
246
|
+
detachedSessionRef: input.detachedSessionRef
|
|
38
247
|
};
|
|
39
248
|
this.records.set(taskId, record);
|
|
40
249
|
this.controllers.set(taskId, controller);
|
|
41
250
|
if (input.idempotencyKey) this.byIdempotencyKey.set(input.idempotencyKey, taskId);
|
|
251
|
+
this.persist(record);
|
|
42
252
|
queueMicrotask(() => {
|
|
43
253
|
this.execute(taskId, input, controller);
|
|
44
254
|
});
|
|
@@ -69,6 +279,8 @@ var DelegationTaskQueue = class {
|
|
|
69
279
|
record.status = "cancelled";
|
|
70
280
|
record.completedAt = this.now();
|
|
71
281
|
record.error = { message: "cancelled by caller", kind: "CancelledError" };
|
|
282
|
+
this.persist(record);
|
|
283
|
+
this.enforceRetention();
|
|
72
284
|
return true;
|
|
73
285
|
}
|
|
74
286
|
/**
|
|
@@ -81,6 +293,7 @@ var DelegationTaskQueue = class {
|
|
|
81
293
|
const record = this.records.get(taskId);
|
|
82
294
|
if (!record) return false;
|
|
83
295
|
record.feedback.push(snapshot);
|
|
296
|
+
this.persist(record);
|
|
84
297
|
return true;
|
|
85
298
|
}
|
|
86
299
|
/**
|
|
@@ -100,6 +313,15 @@ var DelegationTaskQueue = class {
|
|
|
100
313
|
out.sort((a, b) => b.startedAt.localeCompare(a.startedAt));
|
|
101
314
|
return out.slice(0, limit);
|
|
102
315
|
}
|
|
316
|
+
/**
|
|
317
|
+
* Await every journal write issued so far. Rejects with the recorded
|
|
318
|
+
* `DelegationPersistenceError` when any of them failed. Call before
|
|
319
|
+
* handing the store's backing file to another process.
|
|
320
|
+
*/
|
|
321
|
+
async flush() {
|
|
322
|
+
await this.persistTail;
|
|
323
|
+
if (this.persistFailure) throw this.persistFailure;
|
|
324
|
+
}
|
|
103
325
|
/** Test-only — number of in-flight (non-terminal) records. */
|
|
104
326
|
inflightCount() {
|
|
105
327
|
let n = 0;
|
|
@@ -112,26 +334,173 @@ var DelegationTaskQueue = class {
|
|
|
112
334
|
const record = this.records.get(taskId);
|
|
113
335
|
if (!record) return;
|
|
114
336
|
record.status = "running";
|
|
337
|
+
this.persist(record);
|
|
115
338
|
try {
|
|
116
339
|
const output = await input.run({
|
|
117
340
|
signal: controller.signal,
|
|
118
341
|
report: (progress) => {
|
|
119
|
-
if (record.status === "running")
|
|
342
|
+
if (record.status === "running") {
|
|
343
|
+
record.progress = progress;
|
|
344
|
+
this.persist(record);
|
|
345
|
+
}
|
|
346
|
+
},
|
|
347
|
+
...record.detachedSessionRef !== void 0 ? { detachedSessionRef: record.detachedSessionRef } : {},
|
|
348
|
+
updateDetachedSessionRef: (ref) => {
|
|
349
|
+
if (typeof ref !== "string" || ref.length === 0) {
|
|
350
|
+
throw new ValidationError(
|
|
351
|
+
"DelegationTaskQueue: updateDetachedSessionRef requires a non-empty ref"
|
|
352
|
+
);
|
|
353
|
+
}
|
|
354
|
+
if (isTerminal(currentStatus(record))) return;
|
|
355
|
+
record.detachedSessionRef = ref;
|
|
356
|
+
this.persist(record);
|
|
120
357
|
}
|
|
121
358
|
});
|
|
122
359
|
if (currentStatus(record) === "cancelled") return;
|
|
123
360
|
record.status = "completed";
|
|
124
361
|
record.completedAt = this.now();
|
|
125
362
|
record.result = { profile: input.profile, output };
|
|
363
|
+
this.persist(record);
|
|
364
|
+
this.enforceRetention();
|
|
126
365
|
} catch (err) {
|
|
127
366
|
if (currentStatus(record) === "cancelled") return;
|
|
128
367
|
record.status = "failed";
|
|
129
368
|
record.completedAt = this.now();
|
|
130
369
|
record.error = errorToShape(err);
|
|
370
|
+
this.persist(record);
|
|
371
|
+
this.enforceRetention();
|
|
131
372
|
} finally {
|
|
132
373
|
this.controllers.delete(taskId);
|
|
133
374
|
}
|
|
134
375
|
}
|
|
376
|
+
rehydrate(loaded) {
|
|
377
|
+
const records = [...loaded].sort((a, b) => a.startedAt.localeCompare(b.startedAt));
|
|
378
|
+
for (const record of records) {
|
|
379
|
+
this.records.set(record.taskId, record);
|
|
380
|
+
if (record.idempotencyKey) this.byIdempotencyKey.set(record.idempotencyKey, record.taskId);
|
|
381
|
+
}
|
|
382
|
+
for (const record of this.records.values()) {
|
|
383
|
+
if (isTerminal(record.status)) continue;
|
|
384
|
+
if (record.detachedSessionRef && this.resumeDelegate) {
|
|
385
|
+
record.status = "running";
|
|
386
|
+
this.persist(record);
|
|
387
|
+
this.startResume(record, record.detachedSessionRef, this.resumeDelegate);
|
|
388
|
+
continue;
|
|
389
|
+
}
|
|
390
|
+
record.status = "failed";
|
|
391
|
+
record.completedAt = this.now();
|
|
392
|
+
record.error = {
|
|
393
|
+
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",
|
|
394
|
+
kind: "DriverRestartError"
|
|
395
|
+
};
|
|
396
|
+
this.persist(record);
|
|
397
|
+
}
|
|
398
|
+
this.enforceRetention();
|
|
399
|
+
}
|
|
400
|
+
startResume(record, detachedSessionRef, driver) {
|
|
401
|
+
const controller = new AbortController();
|
|
402
|
+
this.controllers.set(record.taskId, controller);
|
|
403
|
+
void this.driveResume(record, detachedSessionRef, driver, controller);
|
|
404
|
+
}
|
|
405
|
+
async driveResume(record, detachedSessionRef, driver, controller) {
|
|
406
|
+
const intervalMs = driver.intervalMs ?? 5e3;
|
|
407
|
+
const ctx = {
|
|
408
|
+
signal: controller.signal,
|
|
409
|
+
report: (progress) => {
|
|
410
|
+
if (currentStatus(record) !== "running") return;
|
|
411
|
+
record.progress = progress;
|
|
412
|
+
this.persist(record);
|
|
413
|
+
}
|
|
414
|
+
};
|
|
415
|
+
try {
|
|
416
|
+
while (!controller.signal.aborted && currentStatus(record) === "running") {
|
|
417
|
+
const tick = await driver.tick({ record: structuredClone(record), detachedSessionRef }, ctx);
|
|
418
|
+
if (currentStatus(record) === "cancelled") return;
|
|
419
|
+
if (tick.state === "completed") {
|
|
420
|
+
record.status = "completed";
|
|
421
|
+
record.completedAt = this.now();
|
|
422
|
+
record.result = {
|
|
423
|
+
profile: record.profile,
|
|
424
|
+
output: tick.output
|
|
425
|
+
};
|
|
426
|
+
if (tick.costUsd !== void 0) record.costUsd = tick.costUsd;
|
|
427
|
+
this.persist(record);
|
|
428
|
+
this.enforceRetention();
|
|
429
|
+
return;
|
|
430
|
+
}
|
|
431
|
+
if (tick.state === "failed") {
|
|
432
|
+
record.status = "failed";
|
|
433
|
+
record.completedAt = this.now();
|
|
434
|
+
record.error = tick.error;
|
|
435
|
+
this.persist(record);
|
|
436
|
+
this.enforceRetention();
|
|
437
|
+
return;
|
|
438
|
+
}
|
|
439
|
+
await abortableDelay(intervalMs, controller.signal);
|
|
440
|
+
}
|
|
441
|
+
} catch (err) {
|
|
442
|
+
if (currentStatus(record) === "cancelled") return;
|
|
443
|
+
record.status = "failed";
|
|
444
|
+
record.completedAt = this.now();
|
|
445
|
+
record.error = errorToShape(err);
|
|
446
|
+
this.persist(record);
|
|
447
|
+
this.enforceRetention();
|
|
448
|
+
} finally {
|
|
449
|
+
this.controllers.delete(record.taskId);
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
persist(record) {
|
|
453
|
+
if (this.persistFailure) return;
|
|
454
|
+
const snapshot = structuredClone(record);
|
|
455
|
+
this.persistTail = this.persistTail.then(async () => {
|
|
456
|
+
if (this.persistFailure) return;
|
|
457
|
+
try {
|
|
458
|
+
await this.store.upsert(snapshot);
|
|
459
|
+
} catch (err) {
|
|
460
|
+
this.failPersistence(err);
|
|
461
|
+
}
|
|
462
|
+
});
|
|
463
|
+
}
|
|
464
|
+
persistRemoval(taskIds) {
|
|
465
|
+
if (this.persistFailure || taskIds.length === 0) return;
|
|
466
|
+
this.persistTail = this.persistTail.then(async () => {
|
|
467
|
+
if (this.persistFailure) return;
|
|
468
|
+
try {
|
|
469
|
+
await this.store.remove(taskIds);
|
|
470
|
+
} catch (err) {
|
|
471
|
+
this.failPersistence(err);
|
|
472
|
+
}
|
|
473
|
+
});
|
|
474
|
+
}
|
|
475
|
+
failPersistence(cause) {
|
|
476
|
+
if (this.persistFailure) return;
|
|
477
|
+
const error = cause instanceof DelegationPersistenceError ? cause : new DelegationPersistenceError(
|
|
478
|
+
`DelegationTaskQueue: store write failed: ${cause instanceof Error ? cause.message : String(cause)}`,
|
|
479
|
+
{ cause }
|
|
480
|
+
);
|
|
481
|
+
this.persistFailure = error;
|
|
482
|
+
this.onPersistError(error);
|
|
483
|
+
}
|
|
484
|
+
enforceRetention() {
|
|
485
|
+
if (!Number.isFinite(this.maxTerminalRecords)) return;
|
|
486
|
+
const terminal = [];
|
|
487
|
+
for (const record of this.records.values()) {
|
|
488
|
+
if (isTerminal(record.status)) terminal.push(record);
|
|
489
|
+
}
|
|
490
|
+
const excess = terminal.length - this.maxTerminalRecords;
|
|
491
|
+
if (excess <= 0) return;
|
|
492
|
+
terminal.sort(
|
|
493
|
+
(a, b) => (a.completedAt ?? a.startedAt).localeCompare(b.completedAt ?? b.startedAt)
|
|
494
|
+
);
|
|
495
|
+
const evicted = terminal.slice(0, excess);
|
|
496
|
+
for (const record of evicted) {
|
|
497
|
+
this.records.delete(record.taskId);
|
|
498
|
+
if (record.idempotencyKey && this.byIdempotencyKey.get(record.idempotencyKey) === record.taskId) {
|
|
499
|
+
this.byIdempotencyKey.delete(record.idempotencyKey);
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
this.persistRemoval(evicted.map((record) => record.taskId));
|
|
503
|
+
}
|
|
135
504
|
};
|
|
136
505
|
function isTerminal(status) {
|
|
137
506
|
return status === "completed" || status === "failed" || status === "cancelled";
|
|
@@ -145,6 +514,23 @@ function clampLimit(raw) {
|
|
|
145
514
|
if (n <= 0) return 50;
|
|
146
515
|
return Math.min(n, 500);
|
|
147
516
|
}
|
|
517
|
+
function abortableDelay(ms, signal) {
|
|
518
|
+
return new Promise((resolve) => {
|
|
519
|
+
if (signal.aborted) {
|
|
520
|
+
resolve();
|
|
521
|
+
return;
|
|
522
|
+
}
|
|
523
|
+
const onAbort = () => {
|
|
524
|
+
clearTimeout(timer);
|
|
525
|
+
resolve();
|
|
526
|
+
};
|
|
527
|
+
const timer = setTimeout(() => {
|
|
528
|
+
signal.removeEventListener("abort", onAbort);
|
|
529
|
+
resolve();
|
|
530
|
+
}, ms);
|
|
531
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
532
|
+
});
|
|
533
|
+
}
|
|
148
534
|
function toStatusResult(record) {
|
|
149
535
|
const out = {
|
|
150
536
|
taskId: record.taskId,
|
|
@@ -351,11 +737,17 @@ function createDelegateCodeHandler(options) {
|
|
|
351
737
|
config: args.config,
|
|
352
738
|
namespace: args.namespace
|
|
353
739
|
});
|
|
740
|
+
const detached = options.detachedDispatch === true && (args.variants ?? 1) <= 1;
|
|
354
741
|
const submitted = options.queue.submit({
|
|
355
742
|
profile: "coder",
|
|
356
743
|
args,
|
|
357
744
|
namespace: args.namespace,
|
|
358
745
|
idempotencyKey,
|
|
746
|
+
...detached ? {
|
|
747
|
+
detachedSessionRef: formatDetachedSessionRef({
|
|
748
|
+
sessionId: `dlg-turn-coder-${idempotencyKey}`
|
|
749
|
+
})
|
|
750
|
+
} : {},
|
|
359
751
|
run: async (ctx) => options.delegate(args, ctx)
|
|
360
752
|
});
|
|
361
753
|
return {
|
|
@@ -702,11 +1094,17 @@ function createDelegateResearchHandler(options) {
|
|
|
702
1094
|
variants: args.variants ?? 1,
|
|
703
1095
|
config: args.config
|
|
704
1096
|
});
|
|
1097
|
+
const detached = options.detachedDispatch === true && (args.variants ?? 1) <= 1;
|
|
705
1098
|
const submitted = options.queue.submit({
|
|
706
1099
|
profile: "researcher",
|
|
707
1100
|
args,
|
|
708
1101
|
namespace: args.namespace,
|
|
709
1102
|
idempotencyKey,
|
|
1103
|
+
...detached ? {
|
|
1104
|
+
detachedSessionRef: formatDetachedSessionRef({
|
|
1105
|
+
sessionId: `dlg-turn-research-${idempotencyKey}`
|
|
1106
|
+
})
|
|
1107
|
+
} : {},
|
|
710
1108
|
run: async (ctx) => options.delegate(args, ctx)
|
|
711
1109
|
});
|
|
712
1110
|
return {
|
|
@@ -1170,6 +1568,10 @@ async function exportEvalRuns(events, config) {
|
|
|
1170
1568
|
}
|
|
1171
1569
|
|
|
1172
1570
|
export {
|
|
1571
|
+
DelegationStateCorruptError,
|
|
1572
|
+
DelegationPersistenceError,
|
|
1573
|
+
InMemoryDelegationStore,
|
|
1574
|
+
FileDelegationStore,
|
|
1173
1575
|
DelegationTaskQueue,
|
|
1174
1576
|
hashIdempotencyInput,
|
|
1175
1577
|
DELEGATE_CODE_TOOL_NAME,
|
|
@@ -1205,4 +1607,4 @@ export {
|
|
|
1205
1607
|
INTELLIGENCE_WIRE_VERSION,
|
|
1206
1608
|
exportEvalRuns
|
|
1207
1609
|
};
|
|
1208
|
-
//# sourceMappingURL=chunk-
|
|
1610
|
+
//# sourceMappingURL=chunk-VIEDXELL.js.map
|