@anvia/durable 0.0.0-bootstrap.0 → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js ADDED
@@ -0,0 +1,1930 @@
1
+ import {
2
+ parseTaskSubmission
3
+ } from "./chunk-ETGANBD4.js";
4
+ import {
5
+ errorMessage,
6
+ graphListSchema,
7
+ json,
8
+ listOptionsSchema,
9
+ modelRetrySchema,
10
+ nonblank,
11
+ parseDurableGraphSubmission,
12
+ sameJson,
13
+ taskListSchema,
14
+ taskWaitSchema
15
+ } from "./chunk-KIMHK6CE.js";
16
+
17
+ // src/errors.ts
18
+ var DurableRecoveryError = class extends Error {
19
+ constructor(message, operationId) {
20
+ super(message);
21
+ this.operationId = operationId;
22
+ this.name = "DurableRecoveryError";
23
+ }
24
+ operationId;
25
+ };
26
+ var DurableRunError = class extends Error {
27
+ constructor(runId, status, message) {
28
+ super(message);
29
+ this.runId = runId;
30
+ this.status = status;
31
+ this.name = "DurableRunError";
32
+ }
33
+ runId;
34
+ status;
35
+ };
36
+ var DurableNotFoundError = class extends Error {
37
+ };
38
+ var DurableConflictError = class extends Error {
39
+ };
40
+ var DurableModelError = class extends Error {
41
+ constructor(operationId, attempts, cause) {
42
+ super(cause instanceof Error ? cause.message : String(cause), { cause });
43
+ this.operationId = operationId;
44
+ this.attempts = attempts;
45
+ }
46
+ operationId;
47
+ attempts;
48
+ };
49
+ var DurableStorageError = class extends Error {
50
+ };
51
+ var DurableLimitError = class extends Error {
52
+ };
53
+
54
+ // src/limits.ts
55
+ function durableLimits(input = {}) {
56
+ const limits = {
57
+ maxPendingRuns: 1e4,
58
+ maxPendingTasks: 1e4,
59
+ maxPayloadBytes: 1048576,
60
+ maxOperations: 1e4,
61
+ ...input
62
+ };
63
+ for (const [name, value] of Object.entries(limits))
64
+ if (!Number.isSafeInteger(value) || value < 1)
65
+ throw new TypeError(`Invalid durable limit: ${name}`);
66
+ return limits;
67
+ }
68
+ function limitedTransaction(tx, limits) {
69
+ const payload = (value) => {
70
+ if (value !== void 0 && new TextEncoder().encode(JSON.stringify(value)).byteLength > limits.maxPayloadBytes)
71
+ throw new DurableLimitError("Durable payload byte limit exceeded.");
72
+ };
73
+ const terminal2 = (status) => ["completed", "failed", "cancelled"].includes(status);
74
+ return {
75
+ ...tx,
76
+ putRun(run) {
77
+ const previous = tx.getRun(run.id);
78
+ if (!terminal2(run.status) && (previous === void 0 || terminal2(previous.status)) && tx.pendingCounts().runs >= limits.maxPendingRuns)
79
+ throw new DurableLimitError("Pending durable run limit reached.");
80
+ if (previous === void 0) payload(run.prompt);
81
+ for (const key of ["input", "history", "responses", "outcome"])
82
+ if (JSON.stringify(previous?.[key]) !== JSON.stringify(run[key])) payload(run[key]);
83
+ tx.putRun(run);
84
+ },
85
+ putTask(task) {
86
+ const previous = tx.getTask(task.id);
87
+ if (!terminal2(task.status) && (previous === void 0 || terminal2(previous.status)) && tx.pendingCounts().tasks >= limits.maxPendingTasks)
88
+ throw new DurableLimitError("Pending durable task limit reached.");
89
+ for (const key of ["input", "checkpoint", "signals"])
90
+ if (JSON.stringify(previous?.[key]) !== JSON.stringify(task[key])) payload(task[key]);
91
+ if (task.agentRunId === void 0 && task.outcome?.status === "completed" && JSON.stringify(previous?.outcome) !== JSON.stringify(task.outcome))
92
+ payload(task.outcome.output);
93
+ tx.putTask(task);
94
+ },
95
+ putOperation(id, operation) {
96
+ const previous = tx.getOperation(id, operation.key);
97
+ if (previous === void 0 && tx.operationCount(id) >= limits.maxOperations)
98
+ throw new DurableLimitError("Durable operation count limit reached.");
99
+ if (JSON.stringify(previous?.input) !== JSON.stringify(operation.input))
100
+ payload(operation.input);
101
+ if (JSON.stringify(previous?.result) !== JSON.stringify(operation.result))
102
+ payload(operation.result);
103
+ tx.putOperation(id, operation);
104
+ }
105
+ };
106
+ }
107
+
108
+ // src/storage-guard.ts
109
+ function guardStore(store, onFailure, limits) {
110
+ let failure;
111
+ const fail = (cause) => {
112
+ if (failure === void 0) {
113
+ failure = new DurableStorageError("Durable storage failed; close and reopen the runtime.", {
114
+ cause
115
+ });
116
+ onFailure(failure);
117
+ }
118
+ return failure;
119
+ };
120
+ const check = () => {
121
+ if (failure !== void 0) throw failure;
122
+ };
123
+ const transaction = (callback) => {
124
+ check();
125
+ let callbackFailed = false;
126
+ let callbackError;
127
+ try {
128
+ return store.transaction((tx) => {
129
+ const guarded = new Proxy(tx, {
130
+ get(target, key) {
131
+ const method = Reflect.get(target, key);
132
+ if (typeof method !== "function") return method;
133
+ return (...args) => {
134
+ check();
135
+ try {
136
+ return Reflect.apply(method, target, args);
137
+ } catch (error) {
138
+ throw fail(error);
139
+ }
140
+ };
141
+ }
142
+ });
143
+ try {
144
+ const result = callback(limitedTransaction(guarded, limits));
145
+ check();
146
+ return result;
147
+ } catch (error) {
148
+ callbackFailed = true;
149
+ callbackError = error;
150
+ throw error;
151
+ }
152
+ });
153
+ } catch (error) {
154
+ if (!callbackFailed || error !== callbackError) throw fail(error);
155
+ throw failure ?? error;
156
+ }
157
+ };
158
+ return new Proxy(store, {
159
+ get(target, key) {
160
+ if (key === "transaction") return transaction;
161
+ const method = Reflect.get(target, key);
162
+ if (typeof method !== "function") return method;
163
+ if (key === "close") return method.bind(target);
164
+ return (...args) => {
165
+ check();
166
+ try {
167
+ return Reflect.apply(method, target, args);
168
+ } catch (error) {
169
+ throw fail(error);
170
+ }
171
+ };
172
+ }
173
+ });
174
+ }
175
+
176
+ // src/wait.ts
177
+ function delay(signal) {
178
+ return new Promise((resolve, reject) => {
179
+ signal?.throwIfAborted();
180
+ const done = () => {
181
+ signal?.removeEventListener("abort", abort);
182
+ resolve();
183
+ };
184
+ const timer = setTimeout(done, 25);
185
+ const abort = () => {
186
+ clearTimeout(timer);
187
+ signal?.removeEventListener("abort", abort);
188
+ reject(signal?.reason);
189
+ };
190
+ signal?.addEventListener("abort", abort, { once: true });
191
+ });
192
+ }
193
+
194
+ // src/task-graph.ts
195
+ var DurableTaskGraph = class {
196
+ constructor(id, runtime) {
197
+ this.id = id;
198
+ this.runtime = runtime;
199
+ }
200
+ id;
201
+ runtime;
202
+ async snapshot() {
203
+ return this.runtime.graphSnapshot(this.id);
204
+ }
205
+ async cancel() {
206
+ this.runtime.cancelGraph(this.id);
207
+ }
208
+ async *stream(options = {}) {
209
+ let cursor = options.after ?? 0;
210
+ const initial = await this.snapshot();
211
+ if (!Number.isSafeInteger(cursor) || cursor < 0 || cursor > initial.cursor)
212
+ throw new TypeError("Invalid graph event cursor.");
213
+ const taskIds = new Map(initial.nodes.map((node) => [node.runId, node.id]));
214
+ while (true) {
215
+ options.abortSignal?.throwIfAborted();
216
+ const events = this.runtime.graphEvents(this.id, cursor);
217
+ for (const event of events) {
218
+ options.abortSignal?.throwIfAborted();
219
+ const taskId = taskIds.get(event.runId);
220
+ if (taskId === void 0) throw new Error("Graph event belongs to an unknown task.");
221
+ cursor = event.sequence;
222
+ yield { ...event, graphId: this.id, taskId };
223
+ }
224
+ if (events.length > 0) continue;
225
+ const snapshot = await this.snapshot();
226
+ if (snapshot.status === "completed" || snapshot.status === "cancelled") return;
227
+ await delay(options.abortSignal);
228
+ }
229
+ }
230
+ };
231
+
232
+ // src/tasks/state.ts
233
+ function taskKey(value) {
234
+ nonblank(value, "Task key");
235
+ if (value.length > 256 || value !== value.trim())
236
+ throw new TypeError("Task keys must be trimmed and at most 256 characters.");
237
+ if (value === "__proto__") throw new TypeError("Reserved task key.");
238
+ }
239
+ function requireTask(tx, id) {
240
+ const task = tx.getTask(id);
241
+ if (task === void 0) throw new DurableNotFoundError(`Unknown durable task: ${id}`);
242
+ return task;
243
+ }
244
+ function terminal(task) {
245
+ return ["completed", "failed", "cancelled"].includes(task.status);
246
+ }
247
+ function saveTask(tx, task) {
248
+ task.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
249
+ tx.putTask(task);
250
+ tx.appendEvent(
251
+ task.id,
252
+ "status",
253
+ json({
254
+ status: task.status,
255
+ wait: task.wait,
256
+ outcome: task.outcome,
257
+ error: task.error,
258
+ blockedOperation: task.blockedOperation
259
+ })
260
+ );
261
+ }
262
+ function createTask(tx, definition, input, sessionId, key, parentId) {
263
+ taskKey(key);
264
+ taskKey(sessionId);
265
+ let parsed;
266
+ try {
267
+ parsed = definition.parseInput(input);
268
+ } catch (cause) {
269
+ if (cause instanceof TypeError) throw cause;
270
+ throw new TypeError("Invalid task input.", { cause });
271
+ }
272
+ const parent = parentId === void 0 ? void 0 : requireTask(tx, parentId);
273
+ if (parent !== void 0 && parent.status !== "running")
274
+ throw new DurableConflictError("Parent task is no longer running.");
275
+ const existing = tx.findTask(sessionId, parentId, key);
276
+ if (existing !== void 0) {
277
+ if (existing.name !== definition.name || existing.submissionVersion !== definition.version || !sameJson(existing.submissionInput, parsed))
278
+ throw new DurableConflictError("Task key already belongs to a different submission.");
279
+ return existing;
280
+ }
281
+ if (parent !== void 0 && (parent.depth >= 32 || tx.taskTree(parent.rootId).length >= 1e3))
282
+ throw new DurableConflictError("Task trees support at most 1000 nodes and depth 32.");
283
+ const id = globalThis.crypto.randomUUID();
284
+ const now = (/* @__PURE__ */ new Date()).toISOString();
285
+ const task = {
286
+ id,
287
+ rootId: parent?.rootId ?? id,
288
+ ...parentId === void 0 ? {} : { parentId },
289
+ depth: parent === void 0 ? 0 : parent.depth + 1,
290
+ sessionId,
291
+ key,
292
+ name: definition.name,
293
+ version: definition.version,
294
+ submissionVersion: definition.version,
295
+ submissionInput: parsed,
296
+ input: parsed,
297
+ checkpoint: definition.initial(parsed),
298
+ status: "pending",
299
+ signals: {},
300
+ createdAt: now,
301
+ updatedAt: now
302
+ };
303
+ tx.putTask(task);
304
+ tx.appendEvent(id, "submitted", json({ name: task.name, parentId, rootId: task.rootId }));
305
+ return task;
306
+ }
307
+ function cancelTree(tx, id, error) {
308
+ const ids = [];
309
+ const pending = [requireTask(tx, id)];
310
+ for (const task of pending) {
311
+ if (terminal(task)) continue;
312
+ ids.push(task.id);
313
+ pending.push(...tx.taskChildren(task.id));
314
+ task.status = "cancelling";
315
+ task.outcome = { status: "cancelled", error };
316
+ if (task.agentRunId !== void 0) {
317
+ const run = tx.getRun(task.agentRunId);
318
+ if (!["completed", "failed", "cancelled"].includes(run.status)) {
319
+ run.status = "cancelled";
320
+ run.error = error;
321
+ run.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
322
+ delete run.nextAttemptAt;
323
+ tx.putRun(run);
324
+ tx.appendEvent(run.id, "status", { status: "cancelled", error });
325
+ }
326
+ }
327
+ delete task.wait;
328
+ delete task.error;
329
+ delete task.blockedOperation;
330
+ saveTask(tx, task);
331
+ }
332
+ return ids;
333
+ }
334
+ function reconcileTree(tx, rootId, active, agentActive) {
335
+ const abort = [];
336
+ let deadline;
337
+ const nodes = tx.taskTree(rootId).reverse();
338
+ for (const node of nodes) {
339
+ const task = requireTask(tx, node.id);
340
+ if (terminal(task)) continue;
341
+ if (task.status === "waiting") {
342
+ const wait = task.wait;
343
+ let ready = false;
344
+ if (wait.type === "children") {
345
+ const children = wait.ids.map((id) => requireTask(tx, id));
346
+ if (wait.policy === "failFast" && children.some((child) => terminal(child) && child.status !== "completed")) {
347
+ for (const child of children)
348
+ if (!terminal(child) && child.status !== "cancelling")
349
+ abort.push(...cancelTree(tx, child.id, "Sibling task failed."));
350
+ }
351
+ ready = children.every(terminal);
352
+ } else if (wait.type === "agent") {
353
+ const run = tx.getRun(wait.runId);
354
+ if (run === void 0) throw new Error("Owned agent run is missing.");
355
+ if (wait.status !== run.status) {
356
+ wait.status = run.status;
357
+ saveTask(tx, task);
358
+ }
359
+ if (["completed", "failed", "cancelled"].includes(run.status)) {
360
+ task.outcome = run.status === "completed" && run.outcome?.type === "response" ? { status: "completed", output: json(run.outcome.output) } : {
361
+ status: run.status === "cancelled" ? "cancelled" : "failed",
362
+ error: run.error ?? "Agent did not return a successful response."
363
+ };
364
+ task.status = "completing";
365
+ delete task.wait;
366
+ saveTask(tx, task);
367
+ }
368
+ } else if (wait.type === "signal") ready = Object.hasOwn(task.signals, wait.name);
369
+ else {
370
+ const until = Date.parse(wait.until);
371
+ ready = until <= Date.now();
372
+ if (!ready) deadline = Math.min(deadline ?? Infinity, until);
373
+ }
374
+ if (ready) {
375
+ task.status = "pending";
376
+ delete task.wait;
377
+ saveTask(tx, task);
378
+ }
379
+ }
380
+ if (["completing", "cancelling"].includes(task.status) && !active.has(task.id) && (task.agentRunId === void 0 || !agentActive(task.agentRunId)) && tx.taskChildren(task.id).every(terminal)) {
381
+ task.status = task.outcome.status;
382
+ saveTask(tx, task);
383
+ }
384
+ }
385
+ return { abort, ...deadline === void 0 ? {} : { deadline } };
386
+ }
387
+
388
+ // src/tasks/invocation.ts
389
+ function taskInvocation(store, task, definitions, controller, changed, spawnAgent2) {
390
+ let open = true;
391
+ let fatal;
392
+ const transaction = (callback) => {
393
+ try {
394
+ return store.transaction(callback);
395
+ } catch (error) {
396
+ if (error instanceof DurableStorageError) fatal = error;
397
+ throw error;
398
+ }
399
+ };
400
+ const effects = /* @__PURE__ */ new Set();
401
+ const keys = /* @__PURE__ */ new Set();
402
+ const assertActive = () => {
403
+ if (fatal !== void 0) throw fatal;
404
+ controller.signal.throwIfAborted();
405
+ if (!open) throw new DurableConflictError("Task invocation has finished.");
406
+ transaction((tx) => {
407
+ if (requireTask(tx, task.id).status !== "running")
408
+ throw new DurableConflictError("Task is no longer running.");
409
+ });
410
+ };
411
+ const effect = async (key, input, execute, recovery = "manual") => {
412
+ assertActive();
413
+ taskKey(key);
414
+ if (!["manual", "safe", "idempotent"].includes(recovery))
415
+ throw new TypeError("Invalid effect recovery policy.");
416
+ if (keys.has(key))
417
+ throw new DurableConflictError("Concurrent use of one effect key is not allowed.");
418
+ keys.add(key);
419
+ try {
420
+ const saved = transaction((tx) => {
421
+ const existing = tx.getOperation(task.id, key);
422
+ if (existing !== void 0) {
423
+ if (!sameJson(existing.input, input) || existing.recovery !== recovery)
424
+ throw new DurableRecoveryError("Effect input or recovery policy changed.", key);
425
+ return existing;
426
+ }
427
+ const operation = {
428
+ key,
429
+ kind: "effect",
430
+ input: json(input),
431
+ status: "started",
432
+ recovery
433
+ };
434
+ tx.putOperation(task.id, operation);
435
+ return void 0;
436
+ });
437
+ if (saved?.status === "completed") return json(saved.result);
438
+ if (saved?.recovery === "manual")
439
+ throw new DurableRecoveryError("Effect requires external reconciliation.", key);
440
+ const output = await execute(`${task.id}/${key}`, controller.signal);
441
+ let result;
442
+ try {
443
+ result = json(output);
444
+ } catch {
445
+ throw new DurableRecoveryError(
446
+ "Effect returned non-JSON output; reconcile its external result.",
447
+ key
448
+ );
449
+ }
450
+ assertActive();
451
+ try {
452
+ transaction((tx) => {
453
+ const operation = tx.getOperation(task.id, key);
454
+ tx.putOperation(task.id, { ...operation, status: "completed", result });
455
+ });
456
+ } catch (error) {
457
+ if (error instanceof DurableLimitError)
458
+ throw new DurableRecoveryError(
459
+ "Effect result exceeds the payload limit; reconcile its external result.",
460
+ key
461
+ );
462
+ throw error;
463
+ }
464
+ return result;
465
+ } catch (error) {
466
+ if (error instanceof DurableRecoveryError) fatal ??= error;
467
+ throw error;
468
+ } finally {
469
+ keys.delete(key);
470
+ }
471
+ };
472
+ const context = {
473
+ id: task.id,
474
+ input: json(task.input),
475
+ checkpoint: json(task.checkpoint),
476
+ signal: controller.signal,
477
+ spawn: (key, definition, input) => {
478
+ assertActive();
479
+ const registered = definitions.get(definition.name);
480
+ if (registered === void 0 || registered.version !== definition.version)
481
+ throw new DurableRecoveryError(`Task definition is not registered: ${definition.name}`);
482
+ const child = transaction(
483
+ (tx) => createTask(tx, registered, input, task.sessionId, key, task.id)
484
+ );
485
+ changed();
486
+ return child.id;
487
+ },
488
+ spawnAgent: (key, input) => {
489
+ assertActive();
490
+ const id = transaction((tx) => spawnAgent2(tx, task, key, input));
491
+ changed();
492
+ return id;
493
+ },
494
+ children: () => {
495
+ assertActive();
496
+ return transaction((tx) => tx.taskChildren(task.id));
497
+ },
498
+ signalValue: (name) => {
499
+ assertActive();
500
+ const signals = transaction((tx) => requireTask(tx, task.id).signals);
501
+ return Object.hasOwn(signals, name) ? json(signals[name].value) : void 0;
502
+ },
503
+ effect: (key, input, execute, recovery) => {
504
+ const pending = effect(key, input, execute, recovery);
505
+ effects.add(pending);
506
+ void pending.then(
507
+ () => effects.delete(pending),
508
+ () => effects.delete(pending)
509
+ );
510
+ return pending;
511
+ }
512
+ };
513
+ return {
514
+ context,
515
+ async finish() {
516
+ open = false;
517
+ if (effects.size > 0) {
518
+ controller.abort(new Error("Task returned before its effects settled."));
519
+ await Promise.allSettled(effects);
520
+ if (fatal !== void 0) throw fatal;
521
+ throw new Error("Task returned before its effects settled.");
522
+ }
523
+ if (fatal !== void 0) throw fatal;
524
+ }
525
+ };
526
+ }
527
+
528
+ // src/tasks/agent.ts
529
+ import { z } from "zod";
530
+ import { isJsonValue } from "@anvia/core/completion";
531
+
532
+ // src/run-record.ts
533
+ import { Usage } from "@anvia/core/completion";
534
+ var GRAPH_SESSION_PREFIX = "__anvia_graph__:";
535
+ function createRunRecord(submission, registration) {
536
+ const now = (/* @__PURE__ */ new Date()).toISOString();
537
+ return {
538
+ ...submission,
539
+ id: globalThis.crypto.randomUUID(),
540
+ version: registration.version,
541
+ status: "queued",
542
+ createdAt: now,
543
+ updatedAt: now,
544
+ epoch: 0,
545
+ modelTurns: 0,
546
+ maxModelTurns: (registration.agent.defaultMaxTurns ?? 0) + 1,
547
+ usage: Usage.empty(),
548
+ history: [],
549
+ responses: {},
550
+ ...registration.modelRetry === void 0 ? {} : { modelRetry: { ...registration.modelRetry } },
551
+ input: { messages: [{ role: "user", content: submission.prompt }] }
552
+ };
553
+ }
554
+
555
+ // src/tasks/definition.ts
556
+ function defineTask(definition) {
557
+ return { ...definition, registration: registerTask(definition) };
558
+ }
559
+ function registerTask(task) {
560
+ nonblank(task.name, "Task name");
561
+ taskKey(task.name);
562
+ if (!Number.isSafeInteger(task.version) || task.version < 1)
563
+ throw new TypeError("Task version must be a positive integer.");
564
+ const parse = (schema, value) => {
565
+ const result = json(schema.parse(json(value)));
566
+ if (!sameJson(result, schema.parse(json(result))))
567
+ throw new TypeError("Task schemas must round-trip persisted JSON without changing it.");
568
+ return result;
569
+ };
570
+ const input = (value) => parse(task.input, value);
571
+ const checkpoint = (value) => parse(task.checkpoint, value);
572
+ return {
573
+ name: task.name,
574
+ version: task.version,
575
+ parseInput: input,
576
+ parseCheckpoint: checkpoint,
577
+ parseOutput: (value) => parse(task.output, value),
578
+ initial: (value) => checkpoint(task.initial(value)),
579
+ // Both values came through the registered schemas before this invocation.
580
+ run: async (context) => await task.run(context),
581
+ ...task.migrate === void 0 ? {} : {
582
+ migrate: (value, state, version) => {
583
+ const result = task.migrate(value, state, version);
584
+ return { input: input(result.input), checkpoint: checkpoint(result.checkpoint) };
585
+ }
586
+ }
587
+ };
588
+ }
589
+
590
+ // src/tasks/agent.ts
591
+ var TASK_SESSION_PREFIX = "__anvia_task__:";
592
+ var agentTask = defineTask({
593
+ name: "anvia.agent",
594
+ version: 1,
595
+ input: z.object({ agentId: z.string().min(1), prompt: z.string().min(1) }).strict(),
596
+ checkpoint: z.null(),
597
+ output: z.custom(isJsonValue),
598
+ initial: () => null,
599
+ run: async () => {
600
+ throw new Error("Agent task is advanced by its owned run.");
601
+ }
602
+ });
603
+ function spawnAgent(tx, agents, parent, key, input) {
604
+ const registration = agents.get(input.agentId);
605
+ if (registration === void 0)
606
+ throw new DurableNotFoundError(`Unknown durable agent: ${input.agentId}`);
607
+ const child = createTask(tx, agentTask.registration, input, parent.sessionId, key, parent.id);
608
+ if (child.agentRunId !== void 0) return child.id;
609
+ const run = createRunRecord(
610
+ {
611
+ agentId: input.agentId,
612
+ prompt: input.prompt,
613
+ sessionId: `${TASK_SESSION_PREFIX}${child.id}`,
614
+ requestId: child.id
615
+ },
616
+ registration
617
+ );
618
+ tx.putRun(run);
619
+ tx.appendEvent(run.id, "submitted", json({ taskId: child.id, prompt: input.prompt }));
620
+ child.agentRunId = run.id;
621
+ child.status = "waiting";
622
+ child.wait = { type: "agent", runId: run.id, status: run.status };
623
+ saveTask(tx, child);
624
+ return child.id;
625
+ }
626
+
627
+ // src/tasks/scheduler.ts
628
+ var TaskScheduler = class {
629
+ constructor(store, definitions, capacity, fatal, agents, agentActive, syncAgents) {
630
+ this.store = store;
631
+ this.definitions = definitions;
632
+ this.capacity = capacity;
633
+ this.fatal = fatal;
634
+ this.agents = agents;
635
+ this.agentActive = agentActive;
636
+ this.syncAgents = syncAgents;
637
+ }
638
+ store;
639
+ definitions;
640
+ capacity;
641
+ fatal;
642
+ agents;
643
+ agentActive;
644
+ syncAgents;
645
+ active = /* @__PURE__ */ new Map();
646
+ deadlines = /* @__PURE__ */ new Map();
647
+ timer;
648
+ closing = false;
649
+ get activeCount() {
650
+ return this.active.size;
651
+ }
652
+ agentChanged(id) {
653
+ if (this.closing) return;
654
+ this.reconcile(this.snapshot(id).task.rootId);
655
+ this.pump();
656
+ }
657
+ submit(name, version, input, sessionId, key) {
658
+ const definition = this.definitions.get(name);
659
+ if (definition === void 0 || definition.version !== version)
660
+ throw new DurableConflictError(`Task definition/version is not registered: ${name}`);
661
+ const task = this.store.transaction((tx) => createTask(tx, definition, input, sessionId, key));
662
+ this.pump();
663
+ return task.id;
664
+ }
665
+ resume() {
666
+ let cursor = 0;
667
+ while (cursor !== void 0) {
668
+ const page = this.store.unsettledTaskRoots(cursor);
669
+ for (const root of page.ids) this.reconcile(root);
670
+ cursor = page.cursor;
671
+ }
672
+ this.pump();
673
+ }
674
+ stop(reason) {
675
+ this.closing = true;
676
+ clearTimeout(this.timer);
677
+ for (const invocation of this.active.values()) invocation.controller.abort(reason);
678
+ }
679
+ async close() {
680
+ this.stop(new Error("Durable runtime closed."));
681
+ await Promise.all([...this.active.values()].map((value) => value.promise));
682
+ }
683
+ snapshot(id) {
684
+ return this.store.transaction((tx) => ({
685
+ task: requireTask(tx, id),
686
+ operations: tx.operations(id),
687
+ cursor: tx.cursor()
688
+ }));
689
+ }
690
+ graph(id) {
691
+ return this.store.transaction((tx) => {
692
+ const rootId = requireTask(tx, id).rootId;
693
+ const nodes = tx.taskTree(rootId);
694
+ const edges = [];
695
+ for (const task of nodes) {
696
+ if (task.parentId !== void 0)
697
+ edges.push({ source: task.parentId, target: task.id, type: "owns" });
698
+ if (task.wait?.type === "children")
699
+ for (const child of task.wait.ids)
700
+ edges.push({ source: task.id, target: child, type: "waits" });
701
+ }
702
+ return { rootId, nodes, edges, cursor: tx.cursor() };
703
+ });
704
+ }
705
+ cancel(id, expectedTreeIds) {
706
+ const root = this.snapshot(id).task.rootId;
707
+ const ids = this.store.transaction((tx) => {
708
+ if (expectedTreeIds !== void 0) {
709
+ const current = tx.taskTree(root);
710
+ const expected = new Set(expectedTreeIds);
711
+ if (current.length !== expected.size || current.some((task) => !expected.has(task.id)))
712
+ throw new DurableConflictError(
713
+ "Task tree changed during authorization; retry cancellation."
714
+ );
715
+ }
716
+ return cancelTree(tx, id, "Durable task cancelled.");
717
+ });
718
+ for (const task of ids)
719
+ this.active.get(task)?.controller.abort(new Error("Durable task cancelled."));
720
+ this.reconcile(root);
721
+ this.pump();
722
+ }
723
+ signal(id, name, requestId, value) {
724
+ taskKey(name);
725
+ taskKey(requestId);
726
+ const root = this.store.transaction((tx) => {
727
+ const task = requireTask(tx, id);
728
+ if (Object.hasOwn(task.signals, name)) {
729
+ if (!sameJson(task.signals[name], { requestId, value: json(value) }))
730
+ throw new DurableConflictError("Signal already has a different delivery.");
731
+ return task.rootId;
732
+ }
733
+ if (terminal(task) || ["completing", "cancelling"].includes(task.status))
734
+ throw new DurableConflictError("Task cannot receive a signal.");
735
+ if (Object.keys(task.signals).length >= 1e3)
736
+ throw new DurableConflictError("Task signal limit reached.");
737
+ Object.defineProperty(task.signals, name, {
738
+ value: { requestId, value: json(value) },
739
+ enumerable: true
740
+ });
741
+ saveTask(tx, task);
742
+ return task.rootId;
743
+ });
744
+ this.reconcile(root);
745
+ this.pump();
746
+ }
747
+ retry(id) {
748
+ if (this.active.has(id)) throw new DurableConflictError("Task invocation is still settling.");
749
+ this.store.transaction((tx) => {
750
+ const task = requireTask(tx, id);
751
+ if (task.status !== "needs_attention")
752
+ throw new DurableConflictError(
753
+ "Only tasks needing attention can resume; terminal outcomes are immutable."
754
+ );
755
+ delete task.error;
756
+ delete task.blockedOperation;
757
+ task.status = "pending";
758
+ saveTask(tx, task);
759
+ });
760
+ this.pump();
761
+ }
762
+ resolveEffect(id, key, value) {
763
+ if (this.active.has(id)) throw new DurableConflictError("Task invocation is still settling.");
764
+ this.store.transaction((tx) => {
765
+ const task = requireTask(tx, id);
766
+ const operation = tx.getOperation(id, key);
767
+ if (task.status !== "needs_attention" || task.blockedOperation !== key || operation?.status !== "started" || operation.kind !== "effect")
768
+ throw new DurableConflictError("No matching effect awaiting reconciliation.");
769
+ tx.putOperation(id, { ...operation, status: "completed", result: json(value) });
770
+ task.status = "pending";
771
+ delete task.blockedOperation;
772
+ delete task.error;
773
+ saveTask(tx, task);
774
+ });
775
+ this.pump();
776
+ }
777
+ reconcile(root) {
778
+ for (let pass = 0; pass < 2; pass++) {
779
+ const result = this.store.transaction(
780
+ (tx) => reconcileTree(tx, root, new Set(this.active.keys()), this.agentActive)
781
+ );
782
+ for (const id of result.abort)
783
+ this.active.get(id)?.controller.abort(new Error("Sibling task failed."));
784
+ if (result.deadline === void 0) this.deadlines.delete(root);
785
+ else this.deadlines.set(root, result.deadline);
786
+ }
787
+ this.syncAgents(root);
788
+ }
789
+ pump() {
790
+ if (this.closing) return;
791
+ clearTimeout(this.timer);
792
+ for (const [root, deadline] of this.deadlines) if (deadline <= Date.now()) this.reconcile(root);
793
+ const capacity = this.capacity - this.active.size;
794
+ if (capacity > 0)
795
+ for (const id of this.store.scheduleTasks([...this.active.keys()], capacity)) this.launch(id);
796
+ if (this.deadlines.size > 0) {
797
+ const deadline = Math.min(...this.deadlines.values());
798
+ this.timer = setTimeout(
799
+ () => {
800
+ try {
801
+ this.pump();
802
+ } catch (error) {
803
+ this.stop(error);
804
+ this.fatal(error);
805
+ }
806
+ },
807
+ Math.max(1, Math.min(2147483647, deadline - Date.now()))
808
+ );
809
+ this.timer.unref();
810
+ }
811
+ }
812
+ launch(id) {
813
+ const controller = new AbortController();
814
+ const promise = new Promise((resolve) => setTimeout(resolve, 0)).then(() => this.execute(id, controller)).catch((error) => {
815
+ if (!(error instanceof DurableLimitError)) throw error;
816
+ this.store.transaction((tx) => {
817
+ const task = requireTask(tx, id);
818
+ delete task.wait;
819
+ delete task.outcome;
820
+ task.status = "needs_attention";
821
+ task.error = error.message;
822
+ saveTask(tx, task);
823
+ });
824
+ }).finally(() => {
825
+ this.active.delete(id);
826
+ if (!this.closing) {
827
+ this.reconcile(this.snapshot(id).task.rootId);
828
+ this.pump();
829
+ }
830
+ }).catch((error) => {
831
+ this.stop(error);
832
+ this.fatal(error);
833
+ });
834
+ this.active.set(id, { controller, promise });
835
+ }
836
+ async execute(id, controller) {
837
+ if (controller.signal.aborted) return;
838
+ const task = this.store.transaction((tx) => {
839
+ const saved = requireTask(tx, id);
840
+ if (!["pending", "running"].includes(saved.status)) return void 0;
841
+ const definition2 = this.definitions.get(saved.name);
842
+ try {
843
+ if (definition2 === void 0 || definition2.version < saved.version)
844
+ throw new Error("Task definition/version is unavailable.");
845
+ if (definition2.version !== saved.version) {
846
+ if (definition2.migrate === void 0)
847
+ throw new Error("Task checkpoint requires a migration.");
848
+ const migrated = definition2.migrate(saved.input, saved.checkpoint, saved.version);
849
+ saved.input = migrated.input;
850
+ saved.checkpoint = migrated.checkpoint;
851
+ saved.version = definition2.version;
852
+ }
853
+ saved.input = definition2.parseInput(saved.input);
854
+ saved.checkpoint = definition2.parseCheckpoint(saved.checkpoint);
855
+ saved.status = "running";
856
+ delete saved.error;
857
+ } catch (error) {
858
+ saved.status = "needs_attention";
859
+ saved.error = errorMessage(error);
860
+ }
861
+ saveTask(tx, saved);
862
+ return saved;
863
+ });
864
+ if (task?.status !== "running") return;
865
+ const definition = this.definitions.get(task.name);
866
+ const invocation = taskInvocation(
867
+ this.store,
868
+ task,
869
+ this.definitions,
870
+ controller,
871
+ () => {
872
+ this.syncAgents(task.rootId);
873
+ this.pump();
874
+ },
875
+ (tx, parent, key, input) => spawnAgent(tx, this.agents, parent, key, input)
876
+ );
877
+ let transition;
878
+ let failure;
879
+ try {
880
+ transition = await definition.run(invocation.context);
881
+ } catch (error) {
882
+ failure = error;
883
+ }
884
+ try {
885
+ await invocation.finish();
886
+ } catch (error) {
887
+ failure = error;
888
+ }
889
+ if (failure instanceof DurableStorageError) throw failure;
890
+ if (this.closing) return;
891
+ const abort = this.store.transaction((tx) => {
892
+ const current = requireTask(tx, id);
893
+ if (current.status !== "running") return [];
894
+ let cancelChildren = false;
895
+ try {
896
+ if (failure !== void 0) throw failure;
897
+ if (transition === void 0) throw new TypeError("Task must return a transition.");
898
+ switch (transition.status) {
899
+ case "pending":
900
+ current.checkpoint = definition.parseCheckpoint(transition.checkpoint);
901
+ current.status = "pending";
902
+ break;
903
+ case "waiting": {
904
+ const wait = taskWaitSchema.parse(transition.wait);
905
+ if (wait.type === "agent")
906
+ throw new TypeError("Agent waits are managed by spawnAgent().");
907
+ if (wait.type === "children" && (new Set(wait.ids).size !== wait.ids.length || wait.ids.some((child) => requireTask(tx, child).parentId !== id)))
908
+ throw new TypeError("Tasks may wait only on distinct direct children.");
909
+ current.checkpoint = definition.parseCheckpoint(transition.checkpoint);
910
+ current.wait = wait;
911
+ current.status = "waiting";
912
+ break;
913
+ }
914
+ case "completed":
915
+ current.outcome = {
916
+ status: "completed",
917
+ output: definition.parseOutput(transition.output)
918
+ };
919
+ current.status = "completing";
920
+ break;
921
+ case "failed":
922
+ case "cancelled":
923
+ current.outcome = { status: transition.status, error: errorMessage(transition.error) };
924
+ current.status = "cancelling";
925
+ cancelChildren = true;
926
+ break;
927
+ default:
928
+ throw new TypeError("Invalid task transition.");
929
+ }
930
+ } catch (error) {
931
+ delete current.wait;
932
+ delete current.outcome;
933
+ current.error = errorMessage(error);
934
+ if (error instanceof DurableRecoveryError || error instanceof DurableLimitError) {
935
+ current.status = "needs_attention";
936
+ if (error instanceof DurableRecoveryError && error.operationId !== void 0)
937
+ current.blockedOperation = error.operationId;
938
+ } else {
939
+ current.status = "cancelling";
940
+ current.outcome = { status: "failed", error: current.error };
941
+ cancelChildren = true;
942
+ }
943
+ }
944
+ const ids = cancelChildren ? tx.taskChildren(id).flatMap((child) => cancelTree(tx, child.id, "Owner task failed or was cancelled.")) : [];
945
+ saveTask(tx, current);
946
+ return ids;
947
+ });
948
+ for (const child of abort)
949
+ this.active.get(child)?.controller.abort(new Error("Owner task ended."));
950
+ }
951
+ };
952
+
953
+ // src/tasks/handle.ts
954
+ var DurableTaskHandle = class {
955
+ constructor(id, scheduler, store, assertOpen) {
956
+ this.id = id;
957
+ this.scheduler = scheduler;
958
+ this.store = store;
959
+ this.assertOpen = assertOpen;
960
+ }
961
+ id;
962
+ scheduler;
963
+ store;
964
+ assertOpen;
965
+ async snapshot() {
966
+ this.assertOpen();
967
+ return this.scheduler.snapshot(this.id);
968
+ }
969
+ async graph() {
970
+ this.assertOpen();
971
+ return this.scheduler.graph(this.id);
972
+ }
973
+ /** Optional identity fence for callers authorizing a captured ownership tree. */
974
+ async cancel(options = {}) {
975
+ this.assertOpen();
976
+ this.scheduler.cancel(this.id, options.expectedTreeIds);
977
+ }
978
+ async retry() {
979
+ this.assertOpen();
980
+ this.scheduler.retry(this.id);
981
+ }
982
+ async resolveEffect(key, value) {
983
+ this.assertOpen();
984
+ this.scheduler.resolveEffect(this.id, key, value);
985
+ }
986
+ async signal(name, requestId, value) {
987
+ this.assertOpen();
988
+ this.scheduler.signal(this.id, name, requestId, value);
989
+ }
990
+ async result(options = {}) {
991
+ while (true) {
992
+ options.abortSignal?.throwIfAborted();
993
+ const { task } = await this.snapshot();
994
+ if (terminal(task)) {
995
+ const outcome = task.outcome;
996
+ if (outcome.status === "completed") return outcome.output;
997
+ throw new DurableRunError(task.id, outcome.status, outcome.error);
998
+ }
999
+ await delay(options.abortSignal);
1000
+ }
1001
+ }
1002
+ /** Streams the entire owned tree, including children created after attachment. */
1003
+ async *stream(options = {}) {
1004
+ let cursor = options.after ?? 0;
1005
+ const initial = await this.snapshot();
1006
+ if (!Number.isSafeInteger(cursor) || cursor < 0 || cursor > initial.cursor)
1007
+ throw new TypeError("Invalid task event cursor.");
1008
+ while (true) {
1009
+ this.assertOpen();
1010
+ options.abortSignal?.throwIfAborted();
1011
+ const events = this.store.taskEvents(initial.task.rootId, cursor, 100);
1012
+ for (const event of events) {
1013
+ options.abortSignal?.throwIfAborted();
1014
+ if (event.type !== "submitted" && event.type !== "status")
1015
+ throw new Error("Invalid task event type.");
1016
+ cursor = event.sequence;
1017
+ yield {
1018
+ rootId: initial.task.rootId,
1019
+ sequence: event.sequence,
1020
+ taskId: event.runId,
1021
+ createdAt: event.createdAt,
1022
+ type: event.type,
1023
+ data: event.data
1024
+ };
1025
+ }
1026
+ if (events.length > 0) continue;
1027
+ if (terminal(this.scheduler.snapshot(initial.task.rootId).task)) return;
1028
+ await delay(options.abortSignal);
1029
+ }
1030
+ }
1031
+ };
1032
+
1033
+ // src/graph-state.ts
1034
+ function createGraph(tx, submission, agents) {
1035
+ const existing = tx.findGraphRequest(submission.sessionId, submission.requestId);
1036
+ if (existing !== void 0) {
1037
+ if (!sameJson(existing.submission, submission))
1038
+ throw new DurableConflictError("Graph requestId already belongs to a different submission.");
1039
+ return existing.id;
1040
+ }
1041
+ const id = globalThis.crypto.randomUUID();
1042
+ const runs = submission.tasks.map((task) => {
1043
+ const registration = agents.get(task.agentId);
1044
+ if (registration === void 0)
1045
+ throw new DurableNotFoundError(`Unknown durable agent: ${task.agentId}`);
1046
+ return {
1047
+ task,
1048
+ run: createRunRecord(
1049
+ {
1050
+ agentId: task.agentId,
1051
+ sessionId: `${GRAPH_SESSION_PREFIX}${id}:${task.id}`,
1052
+ requestId: task.id,
1053
+ prompt: task.prompt
1054
+ },
1055
+ registration
1056
+ )
1057
+ };
1058
+ });
1059
+ const runIds = Object.fromEntries(runs.map(({ task, run }) => [task.id, run.id]));
1060
+ const graph = {
1061
+ id,
1062
+ submission,
1063
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
1064
+ cancelled: false,
1065
+ runIds
1066
+ };
1067
+ tx.putGraph(graph);
1068
+ for (const { task, run } of runs) {
1069
+ run.graphId = id;
1070
+ run.taskId = task.id;
1071
+ run.dependencies = (task.dependsOn ?? []).map((dependency) => runIds[dependency]);
1072
+ tx.putRun(run);
1073
+ tx.appendEvent(
1074
+ run.id,
1075
+ "submitted",
1076
+ json({ graphId: id, taskId: task.id, prompt: task.prompt })
1077
+ );
1078
+ }
1079
+ return id;
1080
+ }
1081
+ function requireGraph(tx, id) {
1082
+ const graph = tx.getGraph(id);
1083
+ if (graph === void 0) throw new DurableNotFoundError(`Unknown durable graph: ${id}`);
1084
+ return graph;
1085
+ }
1086
+ function graphSnapshot(tx, id) {
1087
+ const graph = requireGraph(tx, id);
1088
+ const runs = new Map(
1089
+ graph.submission.tasks.map((task) => [task.id, tx.getRun(graph.runIds[task.id])])
1090
+ );
1091
+ const nodes = graph.submission.tasks.map((task) => {
1092
+ const run = runs.get(task.id);
1093
+ if (run === void 0 || run.graphId !== id || run.taskId !== task.id)
1094
+ throw new Error("Graph task/run mapping changed.");
1095
+ let wait;
1096
+ if (run.status === "queued") {
1097
+ const incomplete = (task.dependsOn ?? []).filter(
1098
+ (dependency) => !successful(runs.get(dependency))
1099
+ );
1100
+ const failed = incomplete.filter((dependency) => dependencyFailed(runs.get(dependency)));
1101
+ wait = failed.length > 0 ? { type: "dependency_failed", taskIds: failed } : incomplete.length > 0 ? { type: "dependencies", taskIds: incomplete } : { type: "capacity" };
1102
+ } else if (run.status === "retry_wait") wait = { type: "retry", until: run.nextAttemptAt };
1103
+ else if (run.status === "waiting" && run.outcome?.type === "interaction")
1104
+ wait = { type: "interaction", interactionId: run.outcome.interaction.id };
1105
+ else if (run.status === "needs_attention")
1106
+ wait = {
1107
+ type: "recovery",
1108
+ ...run.blockedOperation === void 0 ? {} : { operationId: run.blockedOperation }
1109
+ };
1110
+ return {
1111
+ id: task.id,
1112
+ agentId: task.agentId,
1113
+ runId: run.id,
1114
+ status: run.status,
1115
+ ...wait === void 0 ? {} : { wait },
1116
+ ...run.error === void 0 ? {} : { error: run.error },
1117
+ ...run.outcome?.type === "response" ? { output: json(run.outcome.output) } : {},
1118
+ ...run.outcome?.type === "interaction" ? { interaction: run.outcome.interaction } : {}
1119
+ };
1120
+ });
1121
+ const all = [...runs.values()];
1122
+ const status = graph.cancelled ? "cancelled" : all.every(successful) ? "completed" : nodes.some(
1123
+ (node) => node.status === "running" || node.status === "pending" || node.wait?.type === "capacity"
1124
+ ) ? "running" : all.some((run) => dependencyFailed(run) || run.status === "needs_attention") ? "blocked" : "waiting";
1125
+ return {
1126
+ id,
1127
+ sessionId: graph.submission.sessionId,
1128
+ requestId: graph.submission.requestId,
1129
+ createdAt: graph.createdAt,
1130
+ status,
1131
+ nodes,
1132
+ edges: graph.submission.tasks.flatMap(
1133
+ (task) => (task.dependsOn ?? []).map((source) => ({ source, target: task.id }))
1134
+ ),
1135
+ cursor: tx.cursor()
1136
+ };
1137
+ }
1138
+ function successful(run) {
1139
+ return run.status === "completed" && run.outcome?.type === "response";
1140
+ }
1141
+ function dependencyFailed(run) {
1142
+ return run.status === "failed" || run.status === "cancelled" || run.status === "completed" && !successful(run);
1143
+ }
1144
+ function graphInput(tx, run) {
1145
+ const values = {};
1146
+ for (const dependency of run.dependencies ?? []) {
1147
+ const prerequisite = tx.getRun(dependency);
1148
+ if (prerequisite === void 0 || prerequisite.graphId !== run.graphId || !successful(prerequisite) || prerequisite.outcome?.type !== "response")
1149
+ throw new DurableConflictError("Task dependencies are not successful.");
1150
+ values[prerequisite.taskId] = json(prerequisite.outcome.output);
1151
+ }
1152
+ return Object.keys(values).length === 0 ? run.prompt : `${run.prompt}
1153
+
1154
+ Task dependency results (JSON):
1155
+ ${JSON.stringify(values)}`;
1156
+ }
1157
+
1158
+ // src/runtime.ts
1159
+ import {
1160
+ assertAgentInteractionResponse,
1161
+ parseAgentInteractionResponse
1162
+ } from "@anvia/core/agent/interactions";
1163
+ import { parseMessage } from "@anvia/core/completion";
1164
+ import { withInternalAgentRunOptions } from "@anvia/core/internal/agent";
1165
+
1166
+ // src/execution.ts
1167
+ import { Usage as Usage2 } from "@anvia/core/completion";
1168
+ function createExecution(store, run, registration, signal) {
1169
+ const prefix = `${run.epoch}:`;
1170
+ let modelPhase = 0;
1171
+ const check = () => signal.throwIfAborted();
1172
+ function start(key, kind, input, recovery) {
1173
+ check();
1174
+ return store.transaction((tx) => {
1175
+ const previous = tx.getOperation(run.id, key);
1176
+ if (previous !== void 0) {
1177
+ if (!sameJson(previous.input, input) || previous.kind !== kind) {
1178
+ throw new DurableRecoveryError(
1179
+ "Execution changed at a saved checkpoint; restore the registered agent version.",
1180
+ key
1181
+ );
1182
+ }
1183
+ if (previous.status === "started" && previous.recovery === "manual") {
1184
+ throw new DurableRecoveryError(
1185
+ "An interrupted tool has an uncertain outcome and requires reconciliation.",
1186
+ key
1187
+ );
1188
+ }
1189
+ return previous;
1190
+ }
1191
+ const current = tx.getRun(run.id);
1192
+ if (kind === "model") {
1193
+ if (current.modelTurns >= current.maxModelTurns) {
1194
+ throw new Error("Durable run exhausted its model-turn budget.");
1195
+ }
1196
+ current.modelTurns += 1;
1197
+ tx.putRun(current);
1198
+ }
1199
+ const operation = { key, kind, input, recovery, status: "started" };
1200
+ tx.putOperation(run.id, operation);
1201
+ tx.appendEvent(run.id, kind === "model" ? "model_started" : "tool_started", {
1202
+ operationId: key,
1203
+ input
1204
+ });
1205
+ return operation;
1206
+ });
1207
+ }
1208
+ function complete(operation, result, usage) {
1209
+ check();
1210
+ store.transaction((tx) => {
1211
+ const current = tx.getRun(run.id);
1212
+ if (current.status !== "running") throw new Error("Durable run is no longer running.");
1213
+ tx.putOperation(run.id, { ...operation, status: "completed", result });
1214
+ if (usage !== void 0) {
1215
+ current.usage = Usage2.add(current.usage, usage);
1216
+ tx.putRun(current);
1217
+ }
1218
+ tx.appendEvent(run.id, operation.kind === "model" ? "model_completed" : "tool_completed", {
1219
+ operationId: operation.key,
1220
+ result
1221
+ });
1222
+ });
1223
+ }
1224
+ return {
1225
+ async completion(turn, request, execute) {
1226
+ modelPhase = turn;
1227
+ const operation = start(
1228
+ `${prefix}model:${turn}`,
1229
+ "model",
1230
+ json({
1231
+ provider: registration.agent.model.provider,
1232
+ modelId: registration.agent.model.modelId,
1233
+ request
1234
+ }),
1235
+ "safe"
1236
+ );
1237
+ if (operation.status === "completed")
1238
+ return operation.result;
1239
+ if (run.modelRetry !== void 0 && (operation.attempts ?? 0) >= run.modelRetry.maxAttempts) {
1240
+ throw new DurableModelError(
1241
+ operation.key,
1242
+ operation.attempts,
1243
+ new Error("Durable model attempt budget exhausted; retry explicitly to reset it.")
1244
+ );
1245
+ }
1246
+ const attempts = (operation.attempts ?? 0) + 1;
1247
+ operation.attempts = attempts;
1248
+ store.transaction((tx) => tx.putOperation(run.id, operation));
1249
+ let response;
1250
+ try {
1251
+ response = await execute();
1252
+ } catch (error) {
1253
+ check();
1254
+ throw new DurableModelError(operation.key, attempts, error);
1255
+ }
1256
+ const { rawResponse: _raw, ...normalized } = response;
1257
+ const saved = json({ ...normalized, rawResponse: null });
1258
+ complete(operation, saved, response.usage);
1259
+ return saved;
1260
+ },
1261
+ async tool(call, execute) {
1262
+ const key = `${prefix}tool:${modelPhase === 0 ? "resume" : modelPhase}:${call.toolCall.toolCallId}`;
1263
+ const operation = start(
1264
+ key,
1265
+ "tool",
1266
+ json(call),
1267
+ registration.toolRecovery?.[call.toolCall.toolName] ?? "manual"
1268
+ );
1269
+ if (operation.status === "completed") return restoreToolResult(operation.result);
1270
+ const result = await execute({ operationId: `${run.id}/${key}` });
1271
+ check();
1272
+ try {
1273
+ const saved = json(
1274
+ result.failed ? { output: result.output, failed: true, error: errorMessage(result.error) } : result
1275
+ );
1276
+ complete(operation, saved);
1277
+ return restoreToolResult(saved);
1278
+ } catch (error) {
1279
+ if (error instanceof DurableStorageError) throw error;
1280
+ throw new DurableRecoveryError(
1281
+ `Could not record the tool result: ${errorMessage(error)}`,
1282
+ key
1283
+ );
1284
+ }
1285
+ }
1286
+ };
1287
+ }
1288
+ function restoreToolResult(value) {
1289
+ const result = value;
1290
+ return result.failed ? { ...result, error: new Error(String(result.error)) } : result;
1291
+ }
1292
+
1293
+ // src/registration.ts
1294
+ import { AGENT_RUN_EXECUTION_VERSION, getResolvedAgentOptions } from "@anvia/core/internal/agent";
1295
+ function registrations(values) {
1296
+ if (AGENT_RUN_EXECUTION_VERSION !== 1) throw new Error("Unsupported core execution protocol.");
1297
+ const result = /* @__PURE__ */ new Map();
1298
+ for (const registration of values) {
1299
+ nonblank(registration.version, "Agent version");
1300
+ const { agent } = registration;
1301
+ const options = getResolvedAgentOptions(agent);
1302
+ if (result.has(agent.id)) throw new TypeError(`Duplicate durable agent: ${agent.id}`);
1303
+ if (agent.memory !== void 0 || agent.lifecycle !== void 0 || agent.middlewares.length > 0 || agent.guardrails.length > 0 || agent.context.length > 0 || agent.mcpServers.length > 0 || (options.providerTools?.length ?? 0) > 0 || (options.toolIndexes?.length ?? 0) > 0) {
1304
+ throw new TypeError(
1305
+ "Durable agents currently support static local tools and instructions; memory, lifecycle, middleware, guardrails, context, MCP, provider tools, and tool indexes require additional checkpoint support."
1306
+ );
1307
+ }
1308
+ for (const [name, policy] of Object.entries(registration.toolRecovery ?? {})) {
1309
+ if (agent.getTool(name) === void 0) throw new TypeError(`Unknown recovery tool: ${name}`);
1310
+ if (policy !== "safe" && policy !== "idempotent" && policy !== "manual")
1311
+ throw new TypeError(`Invalid recovery policy for ${name}`);
1312
+ }
1313
+ result.set(agent.id, {
1314
+ agent,
1315
+ version: registration.version,
1316
+ toolRecovery: { ...registration.toolRecovery },
1317
+ ...registration.modelRetry === void 0 ? {} : { modelRetry: modelRetrySchema.parse(registration.modelRetry) }
1318
+ });
1319
+ }
1320
+ return result;
1321
+ }
1322
+
1323
+ // src/run.ts
1324
+ var DurableRun = class {
1325
+ constructor(id, runtime) {
1326
+ this.id = id;
1327
+ this.runtime = runtime;
1328
+ }
1329
+ id;
1330
+ runtime;
1331
+ async snapshot() {
1332
+ return this.runtime.snapshot(this.id);
1333
+ }
1334
+ async *stream(options = {}) {
1335
+ let cursor = options.after ?? 0;
1336
+ if (!Number.isSafeInteger(cursor) || cursor < 0) throw new TypeError("Invalid event cursor.");
1337
+ if (cursor > (await this.snapshot()).cursor)
1338
+ throw new TypeError("Event cursor is ahead of the store.");
1339
+ while (true) {
1340
+ options.abortSignal?.throwIfAborted();
1341
+ const events = this.runtime.events(this.id, cursor);
1342
+ for (const event of events) {
1343
+ options.abortSignal?.throwIfAborted();
1344
+ cursor = event.sequence;
1345
+ yield event;
1346
+ }
1347
+ if (events.length > 0) continue;
1348
+ const { run } = await this.snapshot();
1349
+ if (run.status === "completed" || run.status === "failed" || run.status === "cancelled")
1350
+ return;
1351
+ await delay(options.abortSignal);
1352
+ }
1353
+ }
1354
+ async result(options = {}) {
1355
+ while (true) {
1356
+ options.abortSignal?.throwIfAborted();
1357
+ const { run } = await this.snapshot();
1358
+ if (run.status === "failed" || run.status === "cancelled") {
1359
+ throw new DurableRunError(run.id, run.status, run.error ?? run.status);
1360
+ }
1361
+ if (run.status === "completed") {
1362
+ if (run.outcome === void 0) throw new Error("Completed durable run has no outcome.");
1363
+ return run.outcome;
1364
+ }
1365
+ await delay(options.abortSignal);
1366
+ }
1367
+ }
1368
+ async respond(interactionId, response) {
1369
+ this.runtime.respond(this.id, interactionId, response);
1370
+ }
1371
+ async resolveTool(operationId, output) {
1372
+ this.runtime.resolveTool(this.id, operationId, output);
1373
+ }
1374
+ async retry() {
1375
+ this.runtime.retry(this.id);
1376
+ }
1377
+ async cancel() {
1378
+ this.runtime.cancel(this.id);
1379
+ }
1380
+ };
1381
+
1382
+ // src/runtime.ts
1383
+ var DurableRuntime = class _DurableRuntime {
1384
+ constructor(store, agents, maxConcurrentRuns, definitions, maxConcurrentTasks, limits, onFatalError) {
1385
+ this.maxConcurrentRuns = maxConcurrentRuns;
1386
+ this.onFatalError = onFatalError;
1387
+ this.agents = agents;
1388
+ this.store = guardStore(store, (error) => this.fail(error), limits);
1389
+ this.tasks = new TaskScheduler(
1390
+ this.store,
1391
+ definitions,
1392
+ maxConcurrentTasks,
1393
+ (error) => this.fail(error),
1394
+ agents,
1395
+ (id) => this.active.has(id),
1396
+ (root) => {
1397
+ const tasks = this.store.transaction((tx) => tx.taskTree(root));
1398
+ for (const task of tasks)
1399
+ if (task.agentRunId !== void 0 && task.status === "cancelling")
1400
+ this.active.get(task.agentRunId)?.controller.abort(new Error("Owner task cancelled."));
1401
+ this.pump();
1402
+ }
1403
+ );
1404
+ }
1405
+ maxConcurrentRuns;
1406
+ onFatalError;
1407
+ agents;
1408
+ active = /* @__PURE__ */ new Map();
1409
+ closing = false;
1410
+ closed = false;
1411
+ store;
1412
+ closePromise;
1413
+ failure;
1414
+ wakeTimer;
1415
+ tasks;
1416
+ static async open(options) {
1417
+ const maxConcurrentRuns = options.maxConcurrentRuns ?? 4;
1418
+ if (!Number.isSafeInteger(maxConcurrentRuns) || maxConcurrentRuns < 1 || maxConcurrentRuns > 1e3)
1419
+ throw new TypeError("maxConcurrentRuns must be an integer between 1 and 1000.");
1420
+ const maxConcurrentTasks = options.maxConcurrentTasks ?? 4;
1421
+ if (!Number.isSafeInteger(maxConcurrentTasks) || maxConcurrentTasks < 1 || maxConcurrentTasks > 1e3)
1422
+ throw new TypeError("maxConcurrentTasks must be an integer between 1 and 1000.");
1423
+ const definitions = /* @__PURE__ */ new Map();
1424
+ for (const { registration } of options.tasks ?? []) {
1425
+ if (registration.name === agentTask.name)
1426
+ throw new TypeError("Reserved task name: anvia.agent");
1427
+ if (definitions.has(registration.name))
1428
+ throw new TypeError(`Duplicate task definition: ${registration.name}`);
1429
+ definitions.set(registration.name, registration);
1430
+ }
1431
+ const agents = registrations(options.agents ?? []);
1432
+ const limits = durableLimits(options.limits);
1433
+ options.store.acquire();
1434
+ return new _DurableRuntime(
1435
+ options.store,
1436
+ agents,
1437
+ maxConcurrentRuns,
1438
+ definitions,
1439
+ maxConcurrentTasks,
1440
+ limits,
1441
+ options.onFatalError
1442
+ );
1443
+ }
1444
+ async submitTask(definition, input) {
1445
+ this.assertOpen();
1446
+ const id = this.tasks.submit(
1447
+ definition.name,
1448
+ definition.version,
1449
+ input.input,
1450
+ input.sessionId,
1451
+ input.requestId
1452
+ );
1453
+ return new DurableTaskHandle(id, this.tasks, this.store, () => this.assertOpen());
1454
+ }
1455
+ async submitRegisteredTask(input) {
1456
+ this.assertOpen();
1457
+ const submission = parseTaskSubmission(input);
1458
+ const id = this.tasks.submit(
1459
+ submission.name,
1460
+ submission.version,
1461
+ submission.input,
1462
+ submission.sessionId,
1463
+ submission.requestId
1464
+ );
1465
+ return new DurableTaskHandle(id, this.tasks, this.store, () => this.assertOpen());
1466
+ }
1467
+ /** Owning scope for authorization without loading operation journals or graph results. */
1468
+ runScope(runId) {
1469
+ this.assertOpen();
1470
+ return this.store.transaction((tx) => {
1471
+ const run = requireRun(tx, runId);
1472
+ const base = { runId, agentId: run.agentId, sessionId: run.sessionId };
1473
+ if (run.graphId !== void 0)
1474
+ return {
1475
+ ...base,
1476
+ sessionId: requireGraph(tx, run.graphId).submission.sessionId,
1477
+ graphId: run.graphId,
1478
+ taskId: run.taskId
1479
+ };
1480
+ if (!run.sessionId.startsWith(TASK_SESSION_PREFIX)) return base;
1481
+ const task = tx.getTask(run.sessionId.slice(TASK_SESSION_PREFIX.length));
1482
+ if (task === void 0 || task.agentRunId !== runId)
1483
+ throw new DurableNotFoundError("Owned task is missing.");
1484
+ return {
1485
+ ...base,
1486
+ sessionId: task.sessionId,
1487
+ taskId: task.id,
1488
+ rootTaskId: task.rootId,
1489
+ taskName: task.name
1490
+ };
1491
+ });
1492
+ }
1493
+ async getTask(id) {
1494
+ this.assertOpen();
1495
+ this.tasks.snapshot(id);
1496
+ return new DurableTaskHandle(id, this.tasks, this.store, () => this.assertOpen());
1497
+ }
1498
+ async listTasks(options = {}) {
1499
+ this.assertOpen();
1500
+ return this.store.listTasks(taskListSchema.parse(options));
1501
+ }
1502
+ async taskGraph(id) {
1503
+ this.assertOpen();
1504
+ return this.tasks.graph(id);
1505
+ }
1506
+ async submit(input, options = {}) {
1507
+ this.assertOpen();
1508
+ if (options.enqueue !== void 0 && typeof options.enqueue !== "boolean")
1509
+ throw new TypeError("enqueue must be a boolean.");
1510
+ for (const key of ["agentId", "sessionId", "requestId", "prompt"])
1511
+ nonblank(input[key], key);
1512
+ if (input.sessionId.startsWith(GRAPH_SESSION_PREFIX) || input.sessionId.startsWith(TASK_SESSION_PREFIX))
1513
+ throw new TypeError("Reserved durable session ID.");
1514
+ const submission = {
1515
+ agentId: input.agentId,
1516
+ sessionId: input.sessionId,
1517
+ requestId: input.requestId,
1518
+ prompt: input.prompt
1519
+ };
1520
+ const registration = this.agents.get(input.agentId);
1521
+ if (registration === void 0)
1522
+ throw new DurableNotFoundError(`Unknown durable agent: ${input.agentId}`);
1523
+ const id = this.store.transaction((tx) => {
1524
+ const existing = tx.findRequest(input.sessionId, input.requestId);
1525
+ if (existing !== void 0) {
1526
+ if (existing.agentId !== input.agentId || existing.prompt !== input.prompt) {
1527
+ throw new DurableConflictError("requestId already belongs to a different submission.");
1528
+ }
1529
+ return existing.id;
1530
+ }
1531
+ if (!options.enqueue && tx.activeRun(input.sessionId) !== void 0)
1532
+ throw new DurableConflictError("Session already has unfinished work.");
1533
+ if (!options.enqueue && [...this.active.values()].some((active) => active.sessionId === input.sessionId)) {
1534
+ throw new DurableConflictError("Previous session execution is still settling.");
1535
+ }
1536
+ const run = createRunRecord(submission, registration);
1537
+ tx.putRun(run);
1538
+ tx.appendEvent(run.id, "submitted", json(submission));
1539
+ return run.id;
1540
+ });
1541
+ this.pump();
1542
+ return new DurableRun(id, this);
1543
+ }
1544
+ async submitGraph(input) {
1545
+ this.assertOpen();
1546
+ const submission = parseDurableGraphSubmission(input);
1547
+ const id = this.store.transaction((tx) => createGraph(tx, submission, this.agents));
1548
+ this.pump();
1549
+ return new DurableTaskGraph(id, this);
1550
+ }
1551
+ async getGraph(id) {
1552
+ this.graphSnapshot(id);
1553
+ return new DurableTaskGraph(id, this);
1554
+ }
1555
+ async listGraphs(options = {}) {
1556
+ this.assertOpen();
1557
+ return this.store.listGraphs(graphListSchema.parse(options));
1558
+ }
1559
+ graphSnapshot(id) {
1560
+ this.assertOpen();
1561
+ return this.store.transaction((tx) => graphSnapshot(tx, id));
1562
+ }
1563
+ graphEvents(id, after) {
1564
+ this.assertOpen();
1565
+ if (!Number.isSafeInteger(after) || after < 0) throw new TypeError("Invalid event cursor.");
1566
+ return this.store.graphEvents(id, after, 100);
1567
+ }
1568
+ cancelGraph(id) {
1569
+ this.assertOpen();
1570
+ const ids = this.store.transaction((tx) => {
1571
+ const graph = requireGraph(tx, id);
1572
+ if (graph.cancelled || graphSnapshot(tx, id).status === "completed") return [];
1573
+ graph.cancelled = true;
1574
+ tx.putGraph(graph);
1575
+ const runIds = Object.values(graph.runIds);
1576
+ for (const runId of runIds) {
1577
+ const run = requireRun(tx, runId);
1578
+ if (["completed", "failed", "cancelled"].includes(run.status)) continue;
1579
+ delete run.nextAttemptAt;
1580
+ run.error = "Durable graph cancelled.";
1581
+ setStatus(tx, run, "cancelled");
1582
+ }
1583
+ tx.appendEvent(runIds[0], "status", { graphCancelled: true });
1584
+ return runIds;
1585
+ });
1586
+ for (const runId of ids)
1587
+ this.active.get(runId)?.controller.abort(new Error("Durable graph cancelled."));
1588
+ this.pump();
1589
+ }
1590
+ async listRuns(options = {}) {
1591
+ this.assertOpen();
1592
+ return this.store.list(listOptionsSchema.parse(options));
1593
+ }
1594
+ async getRun(id) {
1595
+ this.snapshot(id);
1596
+ return new DurableRun(id, this);
1597
+ }
1598
+ /** Discover recoverable work. This does not wait for runs or approvals to finish. */
1599
+ async resume() {
1600
+ this.assertOpen();
1601
+ this.pump();
1602
+ this.tasks.resume();
1603
+ }
1604
+ /** Abort in-flight attempts, retain checkpoints, and release ownership after callbacks settle. */
1605
+ close() {
1606
+ if (this.closePromise !== void 0) return this.closePromise;
1607
+ this.closing = true;
1608
+ clearTimeout(this.wakeTimer);
1609
+ for (const { controller } of this.active.values())
1610
+ controller.abort(new Error("Durable runtime closed."));
1611
+ this.closePromise = Promise.all([
1612
+ this.tasks.close(),
1613
+ ...[...this.active.values()].map(({ task }) => task)
1614
+ ]).then(() => {
1615
+ try {
1616
+ this.store.close();
1617
+ } finally {
1618
+ this.closed = true;
1619
+ }
1620
+ });
1621
+ return this.closePromise;
1622
+ }
1623
+ snapshot(id) {
1624
+ this.assertOpen();
1625
+ return this.store.transaction((tx) => ({
1626
+ run: requireRun(tx, id),
1627
+ operations: tx.operations(id),
1628
+ cursor: tx.cursor()
1629
+ }));
1630
+ }
1631
+ events(id, after) {
1632
+ this.assertOpen();
1633
+ if (!Number.isSafeInteger(after) || after < 0) throw new TypeError("Invalid event cursor.");
1634
+ return this.store.events(id, after, 100);
1635
+ }
1636
+ respond(id, interactionId, value) {
1637
+ this.assertOpen();
1638
+ nonblank(interactionId, "Interaction ID");
1639
+ const response = parseAgentInteractionResponse(value);
1640
+ this.store.transaction((tx) => {
1641
+ const run = requireRun(tx, id);
1642
+ if (run.graphId !== void 0 && requireGraph(tx, run.graphId).cancelled)
1643
+ throw new DurableConflictError("Graph is cancelled.");
1644
+ if (Object.hasOwn(run.responses, interactionId)) {
1645
+ if (!sameJson(run.responses[interactionId], response))
1646
+ throw new DurableConflictError("Interaction already received a different response.");
1647
+ return;
1648
+ }
1649
+ assertOwnedTaskMutable(tx, run);
1650
+ if (run.status !== "waiting" || run.outcome?.type !== "interaction" || run.outcome.interaction.id !== interactionId) {
1651
+ throw new DurableConflictError("No matching pending interaction.");
1652
+ }
1653
+ try {
1654
+ assertAgentInteractionResponse(run.outcome.interaction, response);
1655
+ } catch (error) {
1656
+ throw new DurableConflictError(errorMessage(error));
1657
+ }
1658
+ run.responses[interactionId] = json(response);
1659
+ const continuation = run.outcome.continuation;
1660
+ run.history = [...run.history, ...run.outcome.messages];
1661
+ run.input = { continuation, response };
1662
+ run.epoch += 1;
1663
+ delete run.outcome;
1664
+ setStatus(tx, run, "pending");
1665
+ });
1666
+ this.pump();
1667
+ }
1668
+ resolveTool(id, operationId, output) {
1669
+ this.assertOpen();
1670
+ this.store.transaction((tx) => {
1671
+ const run = requireRun(tx, id);
1672
+ assertOwnedTaskMutable(tx, run);
1673
+ if (run.graphId !== void 0 && requireGraph(tx, run.graphId).cancelled)
1674
+ throw new DurableConflictError("Graph is cancelled.");
1675
+ if (run.status !== "needs_attention" || run.blockedOperation !== operationId) {
1676
+ throw new DurableConflictError("No matching tool awaiting reconciliation.");
1677
+ }
1678
+ const operation = tx.getOperation(id, operationId);
1679
+ if (operation?.kind !== "tool" || operation.status !== "started")
1680
+ throw new DurableConflictError("Operation cannot be reconciled.");
1681
+ const checked = parseMessage({
1682
+ role: "tool",
1683
+ content: [{ type: "tool-result", toolCallId: operationId, toolName: "reconciled", output }]
1684
+ });
1685
+ if (checked.role !== "tool" || checked.content[0]?.type !== "tool-result")
1686
+ throw new TypeError("Invalid tool result.");
1687
+ const result = json({ output: checked.content[0].output, failed: false });
1688
+ tx.putOperation(id, { ...operation, status: "completed", result });
1689
+ tx.appendEvent(id, "tool_completed", { operationId, result, reconciled: true });
1690
+ delete run.blockedOperation;
1691
+ delete run.error;
1692
+ setStatus(tx, run, "pending");
1693
+ });
1694
+ this.pump();
1695
+ }
1696
+ retry(id) {
1697
+ this.assertOpen();
1698
+ if (this.active.has(id)) throw new DurableConflictError("Run is still settling.");
1699
+ this.store.transaction((tx) => {
1700
+ const run = requireRun(tx, id);
1701
+ assertOwnedTaskMutable(tx, run);
1702
+ if (run.graphId !== void 0 && requireGraph(tx, run.graphId).cancelled)
1703
+ throw new DurableConflictError("Graph is cancelled.");
1704
+ if (run.status !== "failed" && run.status !== "needs_attention")
1705
+ throw new DurableConflictError("Only failed or blocked runs can be retried.");
1706
+ const other = tx.activeRun(run.sessionId);
1707
+ if (other !== void 0 && other.id !== id && other.status !== "queued")
1708
+ throw new DurableConflictError("Session already has unfinished work.");
1709
+ if (tx.hasLaterStartedRun(id))
1710
+ throw new DurableConflictError("Session has advanced; create a new submission.");
1711
+ for (const operation of tx.operations(id)) {
1712
+ if (operation.kind === "model" && operation.status === "started")
1713
+ tx.putOperation(id, { ...operation, attempts: 0 });
1714
+ }
1715
+ delete run.error;
1716
+ delete run.blockedOperation;
1717
+ setStatus(tx, run, "pending");
1718
+ });
1719
+ this.pump();
1720
+ }
1721
+ cancel(id) {
1722
+ this.assertOpen();
1723
+ const sessionId = this.store.transaction((tx) => {
1724
+ const run = requireRun(tx, id);
1725
+ if (["completed", "failed", "cancelled"].includes(run.status)) return run.sessionId;
1726
+ delete run.nextAttemptAt;
1727
+ run.error = "Durable run cancelled.";
1728
+ setStatus(tx, run, "cancelled");
1729
+ return run.sessionId;
1730
+ });
1731
+ this.active.get(id)?.controller.abort(new Error("Durable run cancelled."));
1732
+ if (sessionId.startsWith(TASK_SESSION_PREFIX))
1733
+ this.tasks.agentChanged(sessionId.slice(TASK_SESSION_PREFIX.length));
1734
+ this.pump();
1735
+ }
1736
+ pump() {
1737
+ if (this.closing || this.failure !== void 0) return;
1738
+ clearTimeout(this.wakeTimer);
1739
+ const capacity = this.maxConcurrentRuns - this.active.size;
1740
+ if (capacity <= 0) return;
1741
+ const candidates = this.store.schedule(
1742
+ (/* @__PURE__ */ new Date()).toISOString(),
1743
+ capacity,
1744
+ [...this.active.values()].map((active) => active.sessionId)
1745
+ );
1746
+ for (const id of candidates.ids) this.launch(id);
1747
+ if (candidates.nextAttemptAt !== void 0) {
1748
+ this.wakeTimer = setTimeout(
1749
+ () => {
1750
+ try {
1751
+ this.pump();
1752
+ } catch (error) {
1753
+ this.fail(error);
1754
+ }
1755
+ },
1756
+ Math.max(1, Math.min(2147483647, Date.parse(candidates.nextAttemptAt) - Date.now()))
1757
+ );
1758
+ this.wakeTimer.unref();
1759
+ }
1760
+ }
1761
+ launch(id) {
1762
+ if (this.active.has(id) || this.closing) return;
1763
+ const { status, sessionId } = this.snapshot(id).run;
1764
+ if (!["queued", "pending", "running", "retry_wait"].includes(status)) return;
1765
+ const controller = new AbortController();
1766
+ const task = Promise.resolve().then(() => this.execute(id, controller.signal)).catch((error) => {
1767
+ if (!(error instanceof DurableLimitError)) throw error;
1768
+ this.store.transaction((tx) => {
1769
+ const run = requireRun(tx, id);
1770
+ run.error = error.message;
1771
+ setStatus(tx, run, "needs_attention");
1772
+ });
1773
+ }).finally(() => {
1774
+ this.active.delete(id);
1775
+ if (!this.closing && this.failure === void 0 && sessionId.startsWith(TASK_SESSION_PREFIX))
1776
+ this.tasks.agentChanged(sessionId.slice(TASK_SESSION_PREFIX.length));
1777
+ if (!this.closing && this.failure === void 0) this.pump();
1778
+ }).catch((error) => {
1779
+ if (controller.signal.aborted) return;
1780
+ this.fail(error);
1781
+ });
1782
+ this.active.set(id, { sessionId, controller, task });
1783
+ }
1784
+ async execute(id, signal) {
1785
+ signal.throwIfAborted();
1786
+ const run = this.store.transaction((tx) => {
1787
+ const current = requireRun(tx, id);
1788
+ if (!["queued", "pending", "running", "retry_wait"].includes(current.status))
1789
+ return void 0;
1790
+ if (current.status === "queued") {
1791
+ if (current.graphId !== void 0) {
1792
+ current.input = { messages: [{ role: "user", content: graphInput(tx, current) }] };
1793
+ } else {
1794
+ const previous = tx.latestCompleted(current.sessionId);
1795
+ current.history = previous === void 0 ? [] : [...previous.history, ...previous.outcome?.messages ?? []];
1796
+ current.input = {
1797
+ messages: [...current.history, { role: "user", content: current.prompt }]
1798
+ };
1799
+ }
1800
+ }
1801
+ current.startedAt ??= (/* @__PURE__ */ new Date()).toISOString();
1802
+ delete current.nextAttemptAt;
1803
+ delete current.error;
1804
+ setStatus(tx, current, "running");
1805
+ return current;
1806
+ });
1807
+ if (run === void 0) return;
1808
+ if (run.sessionId.startsWith(TASK_SESSION_PREFIX))
1809
+ this.tasks.agentChanged(run.sessionId.slice(TASK_SESSION_PREFIX.length));
1810
+ try {
1811
+ const registration = this.agents.get(run.agentId);
1812
+ if (registration === void 0 || registration.version !== run.version) {
1813
+ throw new DurableRecoveryError(
1814
+ "The saved agent version is not registered. Restore it before retrying."
1815
+ );
1816
+ }
1817
+ const outcome = await registration.agent.generate(
1818
+ withInternalAgentRunOptions(
1819
+ {
1820
+ ...run.input,
1821
+ abortSignal: signal,
1822
+ retries: false
1823
+ },
1824
+ { runId: id, execution: createExecution(this.store, run, registration, signal) }
1825
+ )
1826
+ );
1827
+ signal.throwIfAborted();
1828
+ this.store.transaction((tx) => {
1829
+ const current = requireRun(tx, id);
1830
+ current.outcome = { ...outcome, usage: current.usage };
1831
+ setStatus(tx, current, outcome.type === "interaction" ? "waiting" : "completed");
1832
+ });
1833
+ } catch (error) {
1834
+ if (error instanceof DurableStorageError) throw error;
1835
+ if (signal.aborted) return;
1836
+ this.store.transaction((tx) => {
1837
+ const current = requireRun(tx, id);
1838
+ current.error = errorMessage(error);
1839
+ if (error instanceof DurableRecoveryError && error.operationId !== void 0)
1840
+ current.blockedOperation = error.operationId;
1841
+ if (error instanceof DurableModelError && current.modelRetry !== void 0 && error.attempts < current.modelRetry.maxAttempts) {
1842
+ const delay2 = Math.min(
1843
+ current.modelRetry.maxDelayMs,
1844
+ current.modelRetry.initialDelayMs * 2 ** (error.attempts - 1)
1845
+ );
1846
+ current.nextAttemptAt = new Date(Date.now() + delay2).toISOString();
1847
+ setStatus(tx, current, "retry_wait");
1848
+ } else {
1849
+ setStatus(
1850
+ tx,
1851
+ current,
1852
+ error instanceof DurableRecoveryError ? "needs_attention" : "failed"
1853
+ );
1854
+ }
1855
+ });
1856
+ }
1857
+ }
1858
+ metrics() {
1859
+ this.assertOpen();
1860
+ return this.store.metrics();
1861
+ }
1862
+ /** Does not touch storage, so supervisors can inspect a failed owner. */
1863
+ health() {
1864
+ return {
1865
+ status: this.closed ? "closed" : this.closing ? "closing" : this.failure !== void 0 ? "failed" : "ready",
1866
+ ready: !this.closing && this.failure === void 0,
1867
+ activeRuns: this.active.size,
1868
+ activeTasks: this.tasks.activeCount
1869
+ };
1870
+ }
1871
+ fail(error) {
1872
+ if (this.failure !== void 0) return;
1873
+ this.failure = error;
1874
+ clearTimeout(this.wakeTimer);
1875
+ this.tasks?.stop(error);
1876
+ for (const active of this.active.values()) active.controller.abort(error);
1877
+ try {
1878
+ void Promise.resolve(this.onFatalError?.(error)).catch(() => {
1879
+ });
1880
+ } catch {
1881
+ }
1882
+ }
1883
+ assertOpen() {
1884
+ if (this.closing) throw new Error("Durable runtime is closed.");
1885
+ if (this.failure !== void 0) throw this.failure;
1886
+ }
1887
+ };
1888
+ function requireRun(tx, id) {
1889
+ const run = tx.getRun(id);
1890
+ if (run === void 0) throw new DurableNotFoundError(`Unknown durable run: ${id}`);
1891
+ return run;
1892
+ }
1893
+ function setStatus(tx, run, status) {
1894
+ run.status = status;
1895
+ run.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
1896
+ tx.putRun(run);
1897
+ tx.appendEvent(
1898
+ run.id,
1899
+ "status",
1900
+ json({
1901
+ status,
1902
+ error: run.error,
1903
+ blockedOperation: run.blockedOperation,
1904
+ nextAttemptAt: run.nextAttemptAt,
1905
+ outcome: run.outcome
1906
+ })
1907
+ );
1908
+ }
1909
+ function assertOwnedTaskMutable(tx, run) {
1910
+ if (!run.sessionId.startsWith(TASK_SESSION_PREFIX)) return;
1911
+ const task = tx.getTask(run.sessionId.slice(TASK_SESSION_PREFIX.length));
1912
+ if (task === void 0 || ["completing", "cancelling", "completed", "failed", "cancelled"].includes(task.status))
1913
+ throw new DurableConflictError(
1914
+ "Owned task outcomes are immutable; create a new task submission."
1915
+ );
1916
+ }
1917
+ export {
1918
+ DurableConflictError,
1919
+ DurableLimitError,
1920
+ DurableNotFoundError,
1921
+ DurableRecoveryError,
1922
+ DurableRun,
1923
+ DurableRunError,
1924
+ DurableRuntime,
1925
+ DurableStorageError,
1926
+ DurableTaskGraph,
1927
+ DurableTaskHandle,
1928
+ defineTask
1929
+ };
1930
+ //# sourceMappingURL=index.js.map