@tangle-network/agent-runtime 0.48.0 → 0.50.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.d.ts +1 -1
- package/dist/agent.js +1 -1
- package/dist/analyst-loop.d.ts +1 -1
- package/dist/{chunk-656G2XCL.js → chunk-BKAIVNFA.js} +3 -3
- package/dist/{chunk-IW2LMLK6.js → chunk-CM2IK7VS.js} +913 -152
- package/dist/chunk-CM2IK7VS.js.map +1 -0
- package/dist/{chunk-VR4JIC5H.js → chunk-ML4IXGTV.js} +2 -2
- package/dist/{chunk-TJS7S3HJ.js → chunk-NDM5VXZW.js} +19 -8
- package/dist/chunk-NDM5VXZW.js.map +1 -0
- package/dist/chunk-OM3YNZIW.js +978 -0
- package/dist/chunk-OM3YNZIW.js.map +1 -0
- package/dist/{chunk-JNPK46YH.js → chunk-RHW75JW5.js} +498 -350
- package/dist/chunk-RHW75JW5.js.map +1 -0
- package/dist/{coder-CVZNGbyg.d.ts → coder-_YCf3BAK.d.ts} +2 -2
- package/dist/{driver-DYU2sgHr.d.ts → driver-DLI1io57.d.ts} +1 -1
- package/dist/index.d.ts +34 -9
- package/dist/index.js +117 -27
- package/dist/index.js.map +1 -1
- package/dist/kb-gate-CHAyt4aI.d.ts +1571 -0
- package/dist/{loop-runner-bin-DEm4roYF.d.ts → loop-runner-bin-DFUNgpeK.d.ts} +4 -4
- package/dist/loop-runner-bin.d.ts +5 -5
- package/dist/loop-runner-bin.js +3 -3
- package/dist/loops.d.ts +6 -6
- package/dist/loops.js +17 -1
- package/dist/mcp/bin.js +206 -29
- package/dist/mcp/bin.js.map +1 -1
- package/dist/mcp/index.d.ts +41 -177
- package/dist/mcp/index.js +40 -6
- package/dist/mcp/index.js.map +1 -1
- package/dist/openai-tools-D4HLDWgw.d.ts +45 -0
- package/dist/platform.js +2 -2
- package/dist/platform.js.map +1 -1
- package/dist/profiles.d.ts +2 -2
- package/dist/{run-loop-DvD4aGiE.d.ts → run-loop-BIineL1T.d.ts} +1 -1
- package/dist/runtime.d.ts +403 -24
- package/dist/runtime.js +17 -1
- package/dist/{types-BpDfCPUp.d.ts → types-5MGt5KTY.d.ts} +1 -1
- package/dist/{types-nBMuollC.d.ts → types-BEQsBhOE.d.ts} +1 -1
- package/dist/workflow.d.ts +2 -2
- package/dist/workflow.js +1 -1
- package/package.json +6 -5
- package/dist/chunk-IW2LMLK6.js.map +0 -1
- package/dist/chunk-JNPK46YH.js.map +0 -1
- package/dist/chunk-LX66I3SC.js +0 -218
- package/dist/chunk-LX66I3SC.js.map +0 -1
- package/dist/chunk-TJS7S3HJ.js.map +0 -1
- package/dist/kb-gate-51BlLlVM.d.ts +0 -529
- package/dist/otel-export-EzfsVUhh.d.ts +0 -191
- /package/dist/{chunk-656G2XCL.js.map → chunk-BKAIVNFA.js.map} +0 -0
- /package/dist/{chunk-VR4JIC5H.js.map → chunk-ML4IXGTV.js.map} +0 -0
|
@@ -1,23 +1,236 @@
|
|
|
1
1
|
import {
|
|
2
|
-
|
|
2
|
+
capDelegationTrace,
|
|
3
|
+
createDelegationTraceCollector,
|
|
4
|
+
formatDetachedSessionRef,
|
|
5
|
+
generateDelegationSpanId
|
|
6
|
+
} from "./chunk-OM3YNZIW.js";
|
|
7
|
+
import {
|
|
8
|
+
AgentEvalError,
|
|
9
|
+
NotFoundError,
|
|
10
|
+
ValidationError
|
|
3
11
|
} from "./chunk-GSUO5QS6.js";
|
|
4
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
|
+
|
|
5
173
|
// src/mcp/task-queue.ts
|
|
6
|
-
var DelegationTaskQueue = class {
|
|
174
|
+
var DelegationTaskQueue = class _DelegationTaskQueue {
|
|
7
175
|
records = /* @__PURE__ */ new Map();
|
|
8
176
|
controllers = /* @__PURE__ */ new Map();
|
|
9
177
|
byIdempotencyKey = /* @__PURE__ */ new Map();
|
|
10
178
|
generateId;
|
|
11
179
|
now;
|
|
180
|
+
store;
|
|
181
|
+
resumeDelegate;
|
|
182
|
+
maxTerminalRecords;
|
|
183
|
+
onPersistError;
|
|
184
|
+
traceContext;
|
|
185
|
+
persistTail = Promise.resolve();
|
|
186
|
+
persistFailure;
|
|
12
187
|
constructor(options = {}) {
|
|
13
188
|
this.generateId = options.generateId ?? randomTaskId;
|
|
14
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;
|
|
15
225
|
}
|
|
16
226
|
/**
|
|
17
227
|
* Kick off a delegation in the background. Returns immediately. The
|
|
18
|
-
* `taskId` is queryable via `status` once this method returns.
|
|
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.
|
|
19
231
|
*/
|
|
20
232
|
submit(input) {
|
|
233
|
+
if (this.persistFailure) throw this.persistFailure;
|
|
21
234
|
if (input.idempotencyKey) {
|
|
22
235
|
const existing = this.byIdempotencyKey.get(input.idempotencyKey);
|
|
23
236
|
if (existing && this.records.has(existing)) {
|
|
@@ -34,11 +247,17 @@ var DelegationTaskQueue = class {
|
|
|
34
247
|
status: "pending",
|
|
35
248
|
startedAt: this.now(),
|
|
36
249
|
feedback: [],
|
|
37
|
-
idempotencyKey: input.idempotencyKey
|
|
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
|
+
} : {}
|
|
38
256
|
};
|
|
39
257
|
this.records.set(taskId, record);
|
|
40
258
|
this.controllers.set(taskId, controller);
|
|
41
259
|
if (input.idempotencyKey) this.byIdempotencyKey.set(input.idempotencyKey, taskId);
|
|
260
|
+
this.persist(record);
|
|
42
261
|
queueMicrotask(() => {
|
|
43
262
|
this.execute(taskId, input, controller);
|
|
44
263
|
});
|
|
@@ -47,11 +266,13 @@ var DelegationTaskQueue = class {
|
|
|
47
266
|
/**
|
|
48
267
|
* Snapshot the current state of a delegation. Returns `undefined` for
|
|
49
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.
|
|
50
271
|
*/
|
|
51
|
-
status(taskId) {
|
|
272
|
+
status(taskId, opts) {
|
|
52
273
|
const record = this.records.get(taskId);
|
|
53
274
|
if (!record) return void 0;
|
|
54
|
-
return toStatusResult(record);
|
|
275
|
+
return toStatusResult(record, opts);
|
|
55
276
|
}
|
|
56
277
|
/**
|
|
57
278
|
* Abort an in-flight delegation. Returns `false` if the task is unknown
|
|
@@ -69,6 +290,8 @@ var DelegationTaskQueue = class {
|
|
|
69
290
|
record.status = "cancelled";
|
|
70
291
|
record.completedAt = this.now();
|
|
71
292
|
record.error = { message: "cancelled by caller", kind: "CancelledError" };
|
|
293
|
+
this.persist(record);
|
|
294
|
+
this.enforceRetention();
|
|
72
295
|
return true;
|
|
73
296
|
}
|
|
74
297
|
/**
|
|
@@ -81,6 +304,7 @@ var DelegationTaskQueue = class {
|
|
|
81
304
|
const record = this.records.get(taskId);
|
|
82
305
|
if (!record) return false;
|
|
83
306
|
record.feedback.push(snapshot);
|
|
307
|
+
this.persist(record);
|
|
84
308
|
return true;
|
|
85
309
|
}
|
|
86
310
|
/**
|
|
@@ -100,6 +324,15 @@ var DelegationTaskQueue = class {
|
|
|
100
324
|
out.sort((a, b) => b.startedAt.localeCompare(a.startedAt));
|
|
101
325
|
return out.slice(0, limit);
|
|
102
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
|
+
}
|
|
103
336
|
/** Test-only — number of in-flight (non-terminal) records. */
|
|
104
337
|
inflightCount() {
|
|
105
338
|
let n = 0;
|
|
@@ -112,26 +345,215 @@ var DelegationTaskQueue = class {
|
|
|
112
345
|
const record = this.records.get(taskId);
|
|
113
346
|
if (!record) return;
|
|
114
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
|
+
});
|
|
115
354
|
try {
|
|
116
355
|
const output = await input.run({
|
|
117
356
|
signal: controller.signal,
|
|
118
357
|
report: (progress) => {
|
|
119
|
-
if (record.status === "running")
|
|
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);
|
|
120
374
|
}
|
|
121
375
|
});
|
|
376
|
+
traceCollector.settle();
|
|
122
377
|
if (currentStatus(record) === "cancelled") return;
|
|
123
378
|
record.status = "completed";
|
|
124
379
|
record.completedAt = this.now();
|
|
125
380
|
record.result = { profile: input.profile, output };
|
|
381
|
+
this.persist(record);
|
|
382
|
+
this.enforceRetention();
|
|
126
383
|
} catch (err) {
|
|
384
|
+
traceCollector.settle();
|
|
127
385
|
if (currentStatus(record) === "cancelled") return;
|
|
128
386
|
record.status = "failed";
|
|
129
387
|
record.completedAt = this.now();
|
|
130
388
|
record.error = errorToShape(err);
|
|
389
|
+
this.persist(record);
|
|
390
|
+
this.enforceRetention();
|
|
131
391
|
} finally {
|
|
132
392
|
this.controllers.delete(taskId);
|
|
133
393
|
}
|
|
134
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
|
+
}
|
|
135
557
|
};
|
|
136
558
|
function isTerminal(status) {
|
|
137
559
|
return status === "completed" || status === "failed" || status === "cancelled";
|
|
@@ -145,7 +567,24 @@ function clampLimit(raw) {
|
|
|
145
567
|
if (n <= 0) return 50;
|
|
146
568
|
return Math.min(n, 500);
|
|
147
569
|
}
|
|
148
|
-
function
|
|
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) {
|
|
149
588
|
const out = {
|
|
150
589
|
taskId: record.taskId,
|
|
151
590
|
profile: record.profile,
|
|
@@ -157,6 +596,12 @@ function toStatusResult(record) {
|
|
|
157
596
|
if (record.error) out.error = record.error;
|
|
158
597
|
if (record.costUsd !== void 0) out.costUsd = record.costUsd;
|
|
159
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
|
+
}
|
|
160
605
|
return out;
|
|
161
606
|
}
|
|
162
607
|
function toHistoryEntry(record) {
|
|
@@ -165,12 +610,14 @@ function toHistoryEntry(record) {
|
|
|
165
610
|
profile: record.profile,
|
|
166
611
|
args: record.args,
|
|
167
612
|
status: record.status,
|
|
168
|
-
startedAt: record.startedAt
|
|
613
|
+
startedAt: record.startedAt,
|
|
614
|
+
hasTrace: record.trace !== void 0 && record.trace.length > 0
|
|
169
615
|
};
|
|
170
616
|
if (record.namespace) entry.namespace = record.namespace;
|
|
171
617
|
if (record.completedAt) entry.completedAt = record.completedAt;
|
|
172
618
|
if (record.costUsd !== void 0) entry.costUsd = record.costUsd;
|
|
173
619
|
if (record.feedback.length > 0) entry.feedback = [...record.feedback];
|
|
620
|
+
if (record.traceId !== void 0) entry.traceId = record.traceId;
|
|
174
621
|
return entry;
|
|
175
622
|
}
|
|
176
623
|
function errorToShape(err) {
|
|
@@ -351,11 +798,17 @@ function createDelegateCodeHandler(options) {
|
|
|
351
798
|
config: args.config,
|
|
352
799
|
namespace: args.namespace
|
|
353
800
|
});
|
|
801
|
+
const detached = options.detachedDispatch === true && (args.variants ?? 1) <= 1;
|
|
354
802
|
const submitted = options.queue.submit({
|
|
355
803
|
profile: "coder",
|
|
356
804
|
args,
|
|
357
805
|
namespace: args.namespace,
|
|
358
806
|
idempotencyKey,
|
|
807
|
+
...detached ? {
|
|
808
|
+
detachedSessionRef: formatDetachedSessionRef({
|
|
809
|
+
sessionId: `dlg-turn-coder-${idempotencyKey}`
|
|
810
|
+
})
|
|
811
|
+
} : {},
|
|
359
812
|
run: async (ctx) => options.delegate(args, ctx)
|
|
360
813
|
});
|
|
361
814
|
return {
|
|
@@ -702,11 +1155,17 @@ function createDelegateResearchHandler(options) {
|
|
|
702
1155
|
variants: args.variants ?? 1,
|
|
703
1156
|
config: args.config
|
|
704
1157
|
});
|
|
1158
|
+
const detached = options.detachedDispatch === true && (args.variants ?? 1) <= 1;
|
|
705
1159
|
const submitted = options.queue.submit({
|
|
706
1160
|
profile: "researcher",
|
|
707
1161
|
args,
|
|
708
1162
|
namespace: args.namespace,
|
|
709
1163
|
idempotencyKey,
|
|
1164
|
+
...detached ? {
|
|
1165
|
+
detachedSessionRef: formatDetachedSessionRef({
|
|
1166
|
+
sessionId: `dlg-turn-research-${idempotencyKey}`
|
|
1167
|
+
})
|
|
1168
|
+
} : {},
|
|
710
1169
|
run: async (ctx) => options.delegate(args, ctx)
|
|
711
1170
|
});
|
|
712
1171
|
return {
|
|
@@ -735,6 +1194,9 @@ var DELEGATION_HISTORY_DESCRIPTION = [
|
|
|
735
1194
|
'success rate of coder delegations on this repo?". Feed the results back',
|
|
736
1195
|
"into your own routing and calibration.",
|
|
737
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
|
+
"",
|
|
738
1200
|
'Filters: `namespace` (multi-tenant scope), `profile` ("coder" | "researcher"),',
|
|
739
1201
|
"`since` (ISO date \u2014 only delegations started at-or-after). `limit` defaults",
|
|
740
1202
|
"to 50, capped at 500."
|
|
@@ -805,13 +1267,21 @@ var DELEGATION_STATUS_DESCRIPTION = [
|
|
|
805
1267
|
"patch, test/typecheck results, and diff stats. For a completed research",
|
|
806
1268
|
"task, `result.output` is the items + citations + proposedWrites bundle.",
|
|
807
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
|
+
"",
|
|
808
1274
|
"Throws NotFoundError when taskId is unknown \u2014 never silently returns",
|
|
809
1275
|
"`pending` for a typo."
|
|
810
1276
|
].join("\n");
|
|
811
1277
|
var DELEGATION_STATUS_INPUT_SCHEMA = {
|
|
812
1278
|
type: "object",
|
|
813
1279
|
properties: {
|
|
814
|
-
taskId: { type: "string", description: "Returned by delegate_code / delegate_research." }
|
|
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
|
+
}
|
|
815
1285
|
},
|
|
816
1286
|
required: ["taskId"],
|
|
817
1287
|
additionalProperties: false
|
|
@@ -825,12 +1295,22 @@ function validateDelegationStatusArgs(raw) {
|
|
|
825
1295
|
if (typeof taskId !== "string" || taskId.trim().length === 0) {
|
|
826
1296
|
throw new TypeError("delegation_status: `taskId` must be a non-empty string");
|
|
827
1297
|
}
|
|
828
|
-
|
|
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;
|
|
829
1306
|
}
|
|
830
1307
|
function createDelegationStatusHandler(options) {
|
|
831
1308
|
return async (raw) => {
|
|
832
1309
|
const args = validateDelegationStatusArgs(raw);
|
|
833
|
-
const status = options.queue.status(
|
|
1310
|
+
const status = options.queue.status(
|
|
1311
|
+
args.taskId,
|
|
1312
|
+
args.includeTrace !== void 0 ? { includeTrace: args.includeTrace } : void 0
|
|
1313
|
+
);
|
|
834
1314
|
if (!status) {
|
|
835
1315
|
throw new NotFoundError(`delegation_status: unknown taskId "${args.taskId}"`);
|
|
836
1316
|
}
|
|
@@ -838,338 +1318,11 @@ function createDelegationStatusHandler(options) {
|
|
|
838
1318
|
};
|
|
839
1319
|
}
|
|
840
1320
|
|
|
841
|
-
// src/otel-export.ts
|
|
842
|
-
var SCOPE = { name: "@tangle-network/agent-runtime", version: "0.33.0" };
|
|
843
|
-
var GEN_AI = {
|
|
844
|
-
operation: "gen_ai.operation.name",
|
|
845
|
-
agentName: "gen_ai.agent.name",
|
|
846
|
-
conversationId: "gen_ai.conversation.id",
|
|
847
|
-
inputTokens: "gen_ai.usage.input_tokens",
|
|
848
|
-
outputTokens: "gen_ai.usage.output_tokens"
|
|
849
|
-
};
|
|
850
|
-
function createOtelExporter(config) {
|
|
851
|
-
const resolvedEndpoint = config?.endpoint ?? (typeof process !== "undefined" ? process.env.OTEL_EXPORTER_OTLP_ENDPOINT : void 0);
|
|
852
|
-
if (!resolvedEndpoint) return void 0;
|
|
853
|
-
const endpoint = resolvedEndpoint;
|
|
854
|
-
const headers = config?.headers ?? parseHeadersFromEnv();
|
|
855
|
-
const batchSize = config?.batchSize ?? 64;
|
|
856
|
-
const flushIntervalMs = config?.flushIntervalMs ?? 5e3;
|
|
857
|
-
const serviceName = config?.serviceName ?? "agent-runtime";
|
|
858
|
-
const resourceAttrs = config?.resourceAttributes ?? {};
|
|
859
|
-
const pending = [];
|
|
860
|
-
let timer;
|
|
861
|
-
let stopped = false;
|
|
862
|
-
const exporter = {
|
|
863
|
-
exportSpan(span) {
|
|
864
|
-
if (stopped) return;
|
|
865
|
-
pending.push(span);
|
|
866
|
-
if (pending.length >= batchSize) {
|
|
867
|
-
void doFlush();
|
|
868
|
-
}
|
|
869
|
-
},
|
|
870
|
-
async flush() {
|
|
871
|
-
await doFlush();
|
|
872
|
-
},
|
|
873
|
-
async shutdown() {
|
|
874
|
-
stopped = true;
|
|
875
|
-
if (timer !== void 0) {
|
|
876
|
-
clearInterval(timer);
|
|
877
|
-
timer = void 0;
|
|
878
|
-
}
|
|
879
|
-
await doFlush();
|
|
880
|
-
}
|
|
881
|
-
};
|
|
882
|
-
timer = setInterval(() => {
|
|
883
|
-
if (pending.length > 0) void doFlush();
|
|
884
|
-
}, flushIntervalMs);
|
|
885
|
-
if (typeof timer === "object" && "unref" in timer) {
|
|
886
|
-
;
|
|
887
|
-
timer.unref();
|
|
888
|
-
}
|
|
889
|
-
async function doFlush() {
|
|
890
|
-
if (pending.length === 0) return;
|
|
891
|
-
const batch = pending.splice(0);
|
|
892
|
-
const body = {
|
|
893
|
-
resourceSpans: [
|
|
894
|
-
{
|
|
895
|
-
resource: {
|
|
896
|
-
attributes: toAttributes({
|
|
897
|
-
"service.name": serviceName,
|
|
898
|
-
...resourceAttrs
|
|
899
|
-
})
|
|
900
|
-
},
|
|
901
|
-
scopeSpans: [{ scope: SCOPE, spans: batch }]
|
|
902
|
-
}
|
|
903
|
-
]
|
|
904
|
-
};
|
|
905
|
-
const url = `${endpoint.replace(/\/+$/, "")}/v1/traces`;
|
|
906
|
-
try {
|
|
907
|
-
await fetch(url, {
|
|
908
|
-
method: "POST",
|
|
909
|
-
headers: { "content-type": "application/json", ...headers },
|
|
910
|
-
body: JSON.stringify(body)
|
|
911
|
-
});
|
|
912
|
-
} catch {
|
|
913
|
-
}
|
|
914
|
-
}
|
|
915
|
-
return exporter;
|
|
916
|
-
}
|
|
917
|
-
function loopEventToOtelSpan(event, traceId, parentSpanId) {
|
|
918
|
-
const spanId = generateSpanId();
|
|
919
|
-
const attrs = {
|
|
920
|
-
"loop.event_kind": event.kind,
|
|
921
|
-
"loop.run_id": event.runId
|
|
922
|
-
};
|
|
923
|
-
for (const [k, v] of Object.entries(event.payload)) {
|
|
924
|
-
if (typeof v === "string" || typeof v === "number" || typeof v === "boolean") {
|
|
925
|
-
attrs[`loop.${k}`] = v;
|
|
926
|
-
}
|
|
927
|
-
}
|
|
928
|
-
const ts = msToNs(event.timestamp);
|
|
929
|
-
return {
|
|
930
|
-
traceId: padTraceId(traceId),
|
|
931
|
-
spanId,
|
|
932
|
-
parentSpanId: parentSpanId ? padSpanId(parentSpanId) : void 0,
|
|
933
|
-
name: event.kind,
|
|
934
|
-
kind: 1,
|
|
935
|
-
startTimeUnixNano: ts,
|
|
936
|
-
endTimeUnixNano: ts,
|
|
937
|
-
attributes: toAttributes(attrs),
|
|
938
|
-
status: { code: 1 }
|
|
939
|
-
};
|
|
940
|
-
}
|
|
941
|
-
function buildLoopOtelSpans(events, traceId, rootParentSpanId) {
|
|
942
|
-
if (events.length === 0) return [];
|
|
943
|
-
const tid = padTraceId(traceId);
|
|
944
|
-
const out = [];
|
|
945
|
-
const num = (v) => typeof v === "number" && Number.isFinite(v) ? v : void 0;
|
|
946
|
-
const str = (v) => typeof v === "string" && v.length > 0 ? v : void 0;
|
|
947
|
-
const rec = (v) => v && typeof v === "object" ? v : {};
|
|
948
|
-
const started = events.find((e) => e.kind === "loop.started");
|
|
949
|
-
const ended = events.find((e) => e.kind === "loop.ended");
|
|
950
|
-
const runId = events[0]?.runId ?? "";
|
|
951
|
-
const rootStart = started?.timestamp ?? events[0].timestamp;
|
|
952
|
-
const rootEnd = ended?.timestamp ?? events[events.length - 1].timestamp;
|
|
953
|
-
const rootId = generateSpanId();
|
|
954
|
-
const make = (spanId, parentSpanId, name, startMs, endMs, attrs, statusCode = 1) => ({
|
|
955
|
-
traceId: tid,
|
|
956
|
-
spanId,
|
|
957
|
-
parentSpanId: parentSpanId ? padSpanId(parentSpanId) : void 0,
|
|
958
|
-
name,
|
|
959
|
-
kind: 1,
|
|
960
|
-
startTimeUnixNano: msToNs(startMs),
|
|
961
|
-
endTimeUnixNano: msToNs(endMs),
|
|
962
|
-
attributes: toAttributes(attrs),
|
|
963
|
-
status: { code: statusCode }
|
|
964
|
-
});
|
|
965
|
-
const sp = rec(started?.payload);
|
|
966
|
-
const rootAttrs = {
|
|
967
|
-
[GEN_AI.operation]: "invoke_workflow",
|
|
968
|
-
[GEN_AI.conversationId]: runId,
|
|
969
|
-
"tangle.loop.driver": str(sp.driver) ?? "driver"
|
|
970
|
-
};
|
|
971
|
-
if (Array.isArray(sp.agentRunNames) && sp.agentRunNames.length > 0) {
|
|
972
|
-
rootAttrs["tangle.loop.agents"] = sp.agentRunNames.map(String).join(",");
|
|
973
|
-
}
|
|
974
|
-
if (ended) {
|
|
975
|
-
const ep = rec(ended.payload);
|
|
976
|
-
const win = num(ep.winnerIterationIndex);
|
|
977
|
-
if (win !== void 0) rootAttrs["tangle.loop.winner.iteration_index"] = win;
|
|
978
|
-
const cost = num(ep.totalCostUsd);
|
|
979
|
-
if (cost !== void 0) rootAttrs["tangle.cost.usd"] = cost;
|
|
980
|
-
const dur = num(ep.durationMs);
|
|
981
|
-
if (dur !== void 0) rootAttrs["tangle.loop.duration_ms"] = dur;
|
|
982
|
-
const iters = num(ep.iterations);
|
|
983
|
-
if (iters !== void 0) rootAttrs["tangle.loop.iterations"] = iters;
|
|
984
|
-
}
|
|
985
|
-
out.push(make(rootId, rootParentSpanId, "loop", rootStart, rootEnd, rootAttrs));
|
|
986
|
-
const iterStartTs = /* @__PURE__ */ new Map();
|
|
987
|
-
const placementByIdx = /* @__PURE__ */ new Map();
|
|
988
|
-
let currentRoundId;
|
|
989
|
-
let pendingRound;
|
|
990
|
-
const flushRound = (endMs) => {
|
|
991
|
-
if (!pendingRound) return;
|
|
992
|
-
out.push(
|
|
993
|
-
make(pendingRound.id, rootId, "loop.round", pendingRound.start, endMs, pendingRound.attrs)
|
|
994
|
-
);
|
|
995
|
-
pendingRound = void 0;
|
|
996
|
-
};
|
|
997
|
-
for (const e of events) {
|
|
998
|
-
const p = rec(e.payload);
|
|
999
|
-
switch (e.kind) {
|
|
1000
|
-
case "loop.plan": {
|
|
1001
|
-
flushRound(e.timestamp);
|
|
1002
|
-
const id = generateSpanId();
|
|
1003
|
-
const roundIdx = num(p.roundIndex) ?? 0;
|
|
1004
|
-
const attrs = {
|
|
1005
|
-
[GEN_AI.operation]: "invoke_workflow",
|
|
1006
|
-
"tangle.loop.round.index": roundIdx,
|
|
1007
|
-
"tangle.loop.move.kind": str(p.moveKind) ?? "unknown",
|
|
1008
|
-
"tangle.loop.move.round": roundIdx,
|
|
1009
|
-
"tangle.loop.move.width": num(p.plannedCount) ?? 0
|
|
1010
|
-
};
|
|
1011
|
-
const r = str(p.rationale);
|
|
1012
|
-
if (r) attrs["tangle.loop.move.rationale"] = r;
|
|
1013
|
-
const parent = num(p.parentIndex);
|
|
1014
|
-
if (parent !== void 0) attrs["tangle.loop.move.parent_index"] = parent;
|
|
1015
|
-
if (Array.isArray(p.childIndices) && p.childIndices.length > 0) {
|
|
1016
|
-
attrs["tangle.loop.move.child_indices"] = p.childIndices.map(String).join(",");
|
|
1017
|
-
}
|
|
1018
|
-
pendingRound = { id, start: e.timestamp, attrs };
|
|
1019
|
-
currentRoundId = id;
|
|
1020
|
-
break;
|
|
1021
|
-
}
|
|
1022
|
-
case "loop.iteration.started": {
|
|
1023
|
-
const idx = num(p.iterationIndex);
|
|
1024
|
-
if (idx !== void 0) iterStartTs.set(idx, e.timestamp);
|
|
1025
|
-
break;
|
|
1026
|
-
}
|
|
1027
|
-
case "loop.iteration.dispatch": {
|
|
1028
|
-
const idx = num(p.iterationIndex);
|
|
1029
|
-
if (idx === void 0) break;
|
|
1030
|
-
const place = {};
|
|
1031
|
-
const kind = str(p.placement);
|
|
1032
|
-
if (kind) place["tangle.loop.placement.kind"] = kind;
|
|
1033
|
-
const sid = str(p.sandboxId);
|
|
1034
|
-
if (sid) place["tangle.sandbox.id"] = sid;
|
|
1035
|
-
const fid = str(p.fleetId);
|
|
1036
|
-
if (fid) place["tangle.fleet.id"] = fid;
|
|
1037
|
-
const mid = str(p.machineId);
|
|
1038
|
-
if (mid) place["tangle.machine.id"] = mid;
|
|
1039
|
-
placementByIdx.set(idx, place);
|
|
1040
|
-
break;
|
|
1041
|
-
}
|
|
1042
|
-
case "loop.iteration.ended": {
|
|
1043
|
-
const idx = num(p.iterationIndex) ?? 0;
|
|
1044
|
-
const start = iterStartTs.get(idx) ?? e.timestamp;
|
|
1045
|
-
const err = str(p.error);
|
|
1046
|
-
const attrs = {
|
|
1047
|
-
[GEN_AI.operation]: "invoke_agent",
|
|
1048
|
-
"tangle.loop.iteration.index": idx
|
|
1049
|
-
};
|
|
1050
|
-
const agent = str(p.agentRunName);
|
|
1051
|
-
if (agent) attrs[GEN_AI.agentName] = agent;
|
|
1052
|
-
const tu = rec(p.tokenUsage);
|
|
1053
|
-
const inTok = num(tu.input);
|
|
1054
|
-
if (inTok !== void 0) attrs[GEN_AI.inputTokens] = inTok;
|
|
1055
|
-
const outTok = num(tu.output);
|
|
1056
|
-
if (outTok !== void 0) attrs[GEN_AI.outputTokens] = outTok;
|
|
1057
|
-
const cost = num(p.costUsd);
|
|
1058
|
-
if (cost !== void 0) attrs["tangle.cost.usd"] = cost;
|
|
1059
|
-
const verdict = rec(p.verdict);
|
|
1060
|
-
if (typeof verdict.valid === "boolean") attrs["tangle.loop.verdict.valid"] = verdict.valid;
|
|
1061
|
-
const score = num(verdict.score);
|
|
1062
|
-
if (score !== void 0) attrs["tangle.loop.verdict.score"] = score;
|
|
1063
|
-
if (err) attrs["tangle.loop.error"] = err;
|
|
1064
|
-
const gid = num(p.groupId);
|
|
1065
|
-
if (gid !== void 0) attrs["tangle.loop.iteration.group_id"] = gid;
|
|
1066
|
-
const par = num(p.parentIndex);
|
|
1067
|
-
if (par !== void 0) attrs["tangle.loop.iteration.parent_index"] = par;
|
|
1068
|
-
const dur = num(p.durationMs);
|
|
1069
|
-
if (dur !== void 0) attrs["tangle.loop.iteration.duration_ms"] = dur;
|
|
1070
|
-
const preview = str(p.outputPreview);
|
|
1071
|
-
if (preview) attrs["tangle.loop.iteration.output_preview"] = preview;
|
|
1072
|
-
Object.assign(attrs, placementByIdx.get(idx) ?? {});
|
|
1073
|
-
out.push(
|
|
1074
|
-
make(
|
|
1075
|
-
generateSpanId(),
|
|
1076
|
-
currentRoundId ?? rootId,
|
|
1077
|
-
"loop.iteration",
|
|
1078
|
-
start,
|
|
1079
|
-
e.timestamp,
|
|
1080
|
-
attrs,
|
|
1081
|
-
err ? 2 : 1
|
|
1082
|
-
)
|
|
1083
|
-
);
|
|
1084
|
-
break;
|
|
1085
|
-
}
|
|
1086
|
-
case "loop.decision": {
|
|
1087
|
-
if (pendingRound) {
|
|
1088
|
-
const dec = str(p.decision);
|
|
1089
|
-
if (dec) pendingRound.attrs["tangle.loop.decision"] = dec;
|
|
1090
|
-
flushRound(e.timestamp);
|
|
1091
|
-
}
|
|
1092
|
-
currentRoundId = void 0;
|
|
1093
|
-
break;
|
|
1094
|
-
}
|
|
1095
|
-
}
|
|
1096
|
-
}
|
|
1097
|
-
flushRound(rootEnd);
|
|
1098
|
-
return out;
|
|
1099
|
-
}
|
|
1100
|
-
function parseHeadersFromEnv() {
|
|
1101
|
-
if (typeof process === "undefined") return {};
|
|
1102
|
-
const raw = process.env.OTEL_EXPORTER_OTLP_HEADERS;
|
|
1103
|
-
if (!raw) return {};
|
|
1104
|
-
const out = {};
|
|
1105
|
-
for (const pair of raw.split(",")) {
|
|
1106
|
-
const eq = pair.indexOf("=");
|
|
1107
|
-
if (eq < 0) continue;
|
|
1108
|
-
const key = pair.slice(0, eq).trim();
|
|
1109
|
-
const value = pair.slice(eq + 1).trim();
|
|
1110
|
-
if (key) out[key] = value;
|
|
1111
|
-
}
|
|
1112
|
-
return out;
|
|
1113
|
-
}
|
|
1114
|
-
function toAttributes(record) {
|
|
1115
|
-
return Object.entries(record).map(([key, value]) => ({
|
|
1116
|
-
key,
|
|
1117
|
-
value: typeof value === "number" ? Number.isInteger(value) ? { intValue: value.toString() } : { doubleValue: value } : typeof value === "boolean" ? { boolValue: value } : { stringValue: value }
|
|
1118
|
-
}));
|
|
1119
|
-
}
|
|
1120
|
-
function msToNs(ms) {
|
|
1121
|
-
return (BigInt(Math.floor(ms)) * 1000000n).toString();
|
|
1122
|
-
}
|
|
1123
|
-
function padSpanId(id) {
|
|
1124
|
-
const cleaned = id.replace(/-/g, "");
|
|
1125
|
-
return cleaned.slice(0, 16).padEnd(16, "0");
|
|
1126
|
-
}
|
|
1127
|
-
function padTraceId(id) {
|
|
1128
|
-
const cleaned = id.replace(/-/g, "");
|
|
1129
|
-
return cleaned.slice(0, 32).padEnd(32, "0");
|
|
1130
|
-
}
|
|
1131
|
-
function generateSpanId() {
|
|
1132
|
-
const bytes = new Uint8Array(8);
|
|
1133
|
-
if (typeof globalThis.crypto?.getRandomValues === "function") {
|
|
1134
|
-
globalThis.crypto.getRandomValues(bytes);
|
|
1135
|
-
} else {
|
|
1136
|
-
for (let i = 0; i < 8; i++) bytes[i] = Math.floor(Math.random() * 256);
|
|
1137
|
-
}
|
|
1138
|
-
return Array.from(bytes).map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
1139
|
-
}
|
|
1140
|
-
var INTELLIGENCE_WIRE_VERSION = "2026-05-26.v1";
|
|
1141
|
-
var DEFAULT_INTELLIGENCE_BASE = "https://intelligence.tangle.tools";
|
|
1142
|
-
async function exportEvalRuns(events, config) {
|
|
1143
|
-
if (events.length === 0) return { ok: true, status: 0, accepted: 0, rejected: [] };
|
|
1144
|
-
const apiKey = config?.apiKey ?? (typeof process !== "undefined" ? process.env.TANGLE_API_KEY : void 0);
|
|
1145
|
-
if (!apiKey)
|
|
1146
|
-
throw new Error("exportEvalRuns: apiKey required (pass config.apiKey or set TANGLE_API_KEY)");
|
|
1147
|
-
const base = config?.base ?? (typeof process !== "undefined" ? process.env.INTELLIGENCE_BASE : void 0) ?? DEFAULT_INTELLIGENCE_BASE;
|
|
1148
|
-
const url = `${base.replace(/\/+$/, "")}/v1/ingest/eval-runs`;
|
|
1149
|
-
const res = await fetch(url, {
|
|
1150
|
-
method: "POST",
|
|
1151
|
-
headers: {
|
|
1152
|
-
"content-type": "application/json",
|
|
1153
|
-
authorization: `Bearer ${apiKey}`,
|
|
1154
|
-
"X-Tangle-Wire-Version": INTELLIGENCE_WIRE_VERSION,
|
|
1155
|
-
...config?.idempotencyKey ? { "Idempotency-Key": config.idempotencyKey } : {}
|
|
1156
|
-
},
|
|
1157
|
-
body: JSON.stringify({ wireVersion: INTELLIGENCE_WIRE_VERSION, events })
|
|
1158
|
-
});
|
|
1159
|
-
let parsed = {};
|
|
1160
|
-
try {
|
|
1161
|
-
parsed = await res.json();
|
|
1162
|
-
} catch {
|
|
1163
|
-
}
|
|
1164
|
-
return {
|
|
1165
|
-
ok: res.ok,
|
|
1166
|
-
status: res.status,
|
|
1167
|
-
accepted: parsed.accepted ?? (res.ok ? events.length : 0),
|
|
1168
|
-
rejected: parsed.rejected ?? []
|
|
1169
|
-
};
|
|
1170
|
-
}
|
|
1171
|
-
|
|
1172
1321
|
export {
|
|
1322
|
+
DelegationStateCorruptError,
|
|
1323
|
+
DelegationPersistenceError,
|
|
1324
|
+
InMemoryDelegationStore,
|
|
1325
|
+
FileDelegationStore,
|
|
1173
1326
|
DelegationTaskQueue,
|
|
1174
1327
|
hashIdempotencyInput,
|
|
1175
1328
|
DELEGATE_CODE_TOOL_NAME,
|
|
@@ -1198,11 +1351,6 @@ export {
|
|
|
1198
1351
|
DELEGATION_STATUS_DESCRIPTION,
|
|
1199
1352
|
DELEGATION_STATUS_INPUT_SCHEMA,
|
|
1200
1353
|
validateDelegationStatusArgs,
|
|
1201
|
-
createDelegationStatusHandler
|
|
1202
|
-
createOtelExporter,
|
|
1203
|
-
loopEventToOtelSpan,
|
|
1204
|
-
buildLoopOtelSpans,
|
|
1205
|
-
INTELLIGENCE_WIRE_VERSION,
|
|
1206
|
-
exportEvalRuns
|
|
1354
|
+
createDelegationStatusHandler
|
|
1207
1355
|
};
|
|
1208
|
-
//# sourceMappingURL=chunk-
|
|
1356
|
+
//# sourceMappingURL=chunk-RHW75JW5.js.map
|