@agent-native/core 0.210.1-nightly-20261020124228 → 0.210.2

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.
Files changed (34) hide show
  1. package/dist/a2a/server.js +2 -1
  2. package/dist/agent/contain-principal.js +2 -3
  3. package/dist/agent/engine/ai-sdk-engine.js +29 -20
  4. package/dist/agent/engine/anthropic-engine.js +10 -1
  5. package/dist/agent/engine/builder-engine.js +28 -14
  6. package/dist/agent/engine/types.d.ts +11 -0
  7. package/dist/agent/engine/types.js +8 -0
  8. package/dist/agent/execution-fence.d.ts +5 -0
  9. package/dist/agent/execution-fence.js +15 -0
  10. package/dist/agent/harness/background.js +28 -7
  11. package/dist/agent/production-agent.d.ts +4 -3
  12. package/dist/agent/production-agent.js +20 -3
  13. package/dist/agent/run-manager.d.ts +8 -1
  14. package/dist/agent/run-manager.js +273 -34
  15. package/dist/agent/run-store.d.ts +3 -2
  16. package/dist/agent/run-store.fencing.worker.js +19 -1
  17. package/dist/agent/run-store.js +216 -67
  18. package/dist/client/use-agent-engine-configured.d.ts +2 -2
  19. package/dist/client/use-agent-engine-configured.js +3 -9
  20. package/dist/collab/awareness.d.ts +2 -2
  21. package/dist/collab/routes.d.ts +1 -1
  22. package/dist/collab/struct-routes.d.ts +1 -1
  23. package/dist/mcp/screen-memory-stdio.d.ts +7 -7
  24. package/dist/notifications/routes.d.ts +1 -1
  25. package/dist/observability/routes.d.ts +5 -5
  26. package/dist/progress/routes.d.ts +1 -1
  27. package/dist/secrets/routes.d.ts +4 -4
  28. package/dist/server/agent-chat-plugin.js +27 -11
  29. package/dist/server/agent-engine-api-key-route.d.ts +1 -1
  30. package/dist/server/agent-engine-default-model-route.d.ts +1 -1
  31. package/dist/server/agent-engine-ollama-models-route.d.ts +2 -2
  32. package/dist/server/agent-teams.d.ts +3 -0
  33. package/dist/server/agent-teams.js +192 -30
  34. package/package.json +4 -4
@@ -11,7 +11,7 @@ import { runErrorTelemetryProperties } from "./engine/error-telemetry.js";
11
11
  import { getAgentEngineEntry } from "./engine/registry.js";
12
12
  import { EngineError } from "./engine/types.js";
13
13
  import { currentExecutionFence, MissingExecutionFenceError, StaleExecutionFenceError, withExecutionFence, withExecutionLeaseRequested, } from "./execution-fence.js";
14
- import { insertRun, insertRunEvent, insertRunEvents, updateRunStatusIfRunning, markRunAborted, markRunAbortedAtSafeBoundary, getRunAbortState, getRunStatus, getRunEventsSince, getCurrentTurnRunEventsForThread, getRunById, getRunByThread, getRunTurnRef, markTurnAborted, cleanupOldRuns, updateRunHeartbeat, bumpRunProgress, setRunInFlightMarker, reapIfStale, reapUnclaimedBackgroundRun, shouldRedispatchUnclaimedBackgroundRun, reconcileTerminalRunFromEvents, ensureTerminalRunEvent, getLastTerminalRunEvent, resolveErroredRunTerminalEvent, setRunError, setRunTerminalReason, persistRunCheckpointEvent, recordRunDiagnostic, RUN_DIAG_STAGE, claimTurnExecutionLease, renewTurnExecutionLease, releaseTurnExecutionLease, TurnExecutionLeaseBusyError, terminalEventForAbortReason, RUN_RECORD_MISSING_ERROR_EVENT, RUN_RECORD_MISSING_GRACE_MS, RUN_TERMINAL_LOOKUP_FAILED_ERROR_EVENT, UNKNOWN_RUN_STATUS_ERROR_EVENT, } from "./run-store.js";
14
+ import { AgentRunEventNotPersistedError, insertRun, insertRunEvent, insertRunEvents, updateRunStatusIfRunning, markRunAborted, markRunAbortedAtSafeBoundary, getRunAbortState, getRunStatus, getRunEventsSince, getCurrentTurnRunEventsForThread, getRunById, getRunByThread, getRunTurnRef, markTurnAborted, cleanupOldRuns, updateRunHeartbeat, bumpRunProgress, setRunInFlightMarker, reapIfStale, reapUnclaimedBackgroundRun, shouldRedispatchUnclaimedBackgroundRun, reconcileTerminalRunFromEvents, ensureTerminalRunEvent, getLastTerminalRunEvent, resolveErroredRunTerminalEvent, setRunError, setRunTerminalReason, persistRunCheckpointEvent, recordRunDiagnostic, RUN_DIAG_STAGE, claimTurnExecutionLease, renewTurnExecutionLease, releaseTurnExecutionLease, TurnExecutionLeaseBusyError, terminalEventForAbortReason, RUN_RECORD_MISSING_ERROR_EVENT, RUN_RECORD_MISSING_GRACE_MS, RUN_TERMINAL_LOOKUP_FAILED_ERROR_EVENT, UNKNOWN_RUN_STATUS_ERROR_EVENT, } from "./run-store.js";
15
15
  import { isContinuationTerminalReason } from "./types.js";
16
16
  const activeRuns = new Map();
17
17
  const threadToRun = new Map();
@@ -37,11 +37,17 @@ function reportAbortFlushFailure(run, error) {
37
37
  }
38
38
  async function flushRunEventsBeforeAbort(run) {
39
39
  try {
40
+ await run?.waitForPersistence?.();
40
41
  await run?.flushEvents?.();
42
+ return null;
41
43
  }
42
44
  catch (error) {
43
45
  if (run)
44
46
  reportAbortFlushFailure(run, error);
47
+ if (error instanceof AgentRunEventNotPersistedError && error.terminalRun) {
48
+ return error;
49
+ }
50
+ throw error;
45
51
  }
46
52
  }
47
53
  const CLEANUP_DELAY_MS = 5 * 60 * 1000;
@@ -543,7 +549,7 @@ export function startRun(runId, threadId, runFn, onComplete, options) {
543
549
  });
544
550
  }
545
551
  const runControl = {
546
- flushEvents: () => persistenceChain,
552
+ flushEvents: () => flushEventWrites(true),
547
553
  get turnSignal() {
548
554
  return abort.signal;
549
555
  },
@@ -678,7 +684,85 @@ export function startRun(runId, threadId, runFn, onComplete, options) {
678
684
  });
679
685
  };
680
686
  let persistenceChain = Promise.resolve();
681
- run.flushEvents = () => persistenceChain;
687
+ const pendingEventWrites = [];
688
+ let persistenceDrain = null;
689
+ let persistenceFailure;
690
+ let persistenceFailureBlocked = false;
691
+ const flushEventWrites = (retryAfterFailure) => {
692
+ if (persistenceDrain) {
693
+ const activeDrain = persistenceDrain;
694
+ const settledDrain = retryAfterFailure
695
+ ? activeDrain.catch((error) => {
696
+ if (error instanceof AgentRunEventNotPersistedError &&
697
+ error.terminalRun) {
698
+ throw error;
699
+ }
700
+ })
701
+ : activeDrain;
702
+ const flush = settledDrain.then(() => pendingEventWrites.length > 0
703
+ ? flushEventWrites(retryAfterFailure)
704
+ : undefined);
705
+ persistenceChain = flush;
706
+ return flush;
707
+ }
708
+ if (pendingEventWrites.length === 0) {
709
+ persistenceChain = Promise.resolve();
710
+ return persistenceChain;
711
+ }
712
+ if (persistenceFailureBlocked && !retryAfterFailure) {
713
+ const blocked = Promise.reject(persistenceFailure);
714
+ persistenceChain = blocked;
715
+ return blocked;
716
+ }
717
+ if (retryAfterFailure) {
718
+ persistenceFailure = undefined;
719
+ persistenceFailureBlocked = false;
720
+ }
721
+ const drain = Promise.resolve().then(async () => {
722
+ while (pendingEventWrites.length > 0) {
723
+ const pending = pendingEventWrites[0];
724
+ try {
725
+ await pending.write();
726
+ }
727
+ catch (error) {
728
+ const terminalRun = error instanceof AgentRunEventNotPersistedError &&
729
+ error.terminalRun;
730
+ persistenceFailure = terminalRun ? undefined : error;
731
+ persistenceFailureBlocked = !terminalRun;
732
+ for (const queued of pendingEventWrites) {
733
+ for (const publication of queued.publications) {
734
+ if (publication.settled)
735
+ continue;
736
+ publication.settled = true;
737
+ publication.reject(error);
738
+ }
739
+ }
740
+ if (terminalRun) {
741
+ pendingEventWrites.length = 0;
742
+ openEventBatch = null;
743
+ }
744
+ throw error;
745
+ }
746
+ pendingEventWrites.shift();
747
+ for (const publication of pending.publications) {
748
+ publishRunEvent(publication.runEvent);
749
+ if (publication.settled)
750
+ continue;
751
+ publication.settled = true;
752
+ publication.resolve();
753
+ }
754
+ }
755
+ });
756
+ const activeDrain = drain.finally(() => {
757
+ if (persistenceDrain === activeDrain)
758
+ persistenceDrain = null;
759
+ });
760
+ persistenceDrain = activeDrain;
761
+ persistenceChain = activeDrain;
762
+ return activeDrain;
763
+ };
764
+ run.waitForPersistence = () => insertRunPromise;
765
+ run.flushEvents = () => flushEventWrites(true);
682
766
  // Streamed deltas arrive hundreds per tool call, and a write tool waits for
683
767
  // every queued event to persist first; one round trip per event put tens of
684
768
  // seconds between the model finishing and the tool starting.
@@ -1189,6 +1273,28 @@ export function startRun(runId, threadId, runFn, onComplete, options) {
1189
1273
  bumpProgressIfDue();
1190
1274
  }
1191
1275
  };
1276
+ const queueRunEvent = (pendingWrite, runEvent) => {
1277
+ let resolve;
1278
+ let reject;
1279
+ const publication = new Promise((res, rej) => {
1280
+ resolve = res;
1281
+ reject = rej;
1282
+ });
1283
+ const pendingPublication = {
1284
+ runEvent,
1285
+ resolve,
1286
+ reject,
1287
+ settled: false,
1288
+ };
1289
+ pendingWrite.publications.push(pendingPublication);
1290
+ void flushEventWrites(false).catch((error) => {
1291
+ if (pendingPublication.settled)
1292
+ return;
1293
+ pendingPublication.settled = true;
1294
+ pendingPublication.reject(error);
1295
+ });
1296
+ return publication;
1297
+ };
1192
1298
  const emitRunEvent = (runEvent, options) => {
1193
1299
  const eventData = JSON.stringify(runEvent.event);
1194
1300
  const eventAt = Date.now();
@@ -1204,40 +1310,47 @@ export function startRun(runId, threadId, runFn, onComplete, options) {
1204
1310
  eventType: runEvent.event.type,
1205
1311
  });
1206
1312
  }
1313
+ if (error instanceof AgentRunEventNotPersistedError &&
1314
+ error.terminalRun) {
1315
+ throw error;
1316
+ }
1207
1317
  await write();
1208
1318
  }
1209
1319
  };
1210
- let persistence;
1320
+ let publication;
1211
1321
  if (isBatchableRunEvent(runEvent.event) && !persistEventHook) {
1212
1322
  if (!openEventBatch) {
1213
1323
  const batch = {
1214
1324
  events: [],
1215
- write: persistenceChain.then(() => {
1216
- if (openEventBatch === batch)
1217
- openEventBatch = null;
1218
- return retryOnce(() => withWorkerFence(() => insertRunEvents(runId, batch.events)));
1219
- }),
1325
+ pendingWrite: {
1326
+ publications: [],
1327
+ write: () => {
1328
+ if (openEventBatch === batch)
1329
+ openEventBatch = null;
1330
+ return retryOnce(() => withWorkerFence(() => insertRunEvents(runId, batch.events)));
1331
+ },
1332
+ },
1220
1333
  };
1221
1334
  openEventBatch = batch;
1222
- persistenceChain = batch.write;
1335
+ pendingEventWrites.push(batch.pendingWrite);
1223
1336
  }
1224
1337
  openEventBatch.events.push({
1225
1338
  seq: runEvent.seq,
1226
1339
  eventAt,
1227
1340
  eventData,
1228
1341
  });
1229
- persistence = openEventBatch.write;
1342
+ publication = queueRunEvent(openEventBatch.pendingWrite, runEvent);
1230
1343
  }
1231
1344
  else {
1232
1345
  // Anything emitted after this event must not persist before it.
1233
1346
  openEventBatch = null;
1234
- persistence = persistenceChain.then(() => retryOnce(() => persistRunEvent(runEvent, eventData, eventAt)));
1235
- persistenceChain = persistence;
1236
- }
1237
- if (!options?.surfacePersistenceError) {
1238
- persistence.catch(() => { });
1347
+ const pendingWrite = {
1348
+ publications: [],
1349
+ write: () => retryOnce(() => persistRunEvent(runEvent, eventData, eventAt)),
1350
+ };
1351
+ pendingEventWrites.push(pendingWrite);
1352
+ publication = queueRunEvent(pendingWrite, runEvent);
1239
1353
  }
1240
- const publication = persistence.then(() => publishRunEvent(runEvent));
1241
1354
  if (!options?.surfacePersistenceError)
1242
1355
  publication.catch(() => { });
1243
1356
  checkSqlAbort();
@@ -2207,11 +2320,33 @@ export async function getActiveRunForThreadAsync(threadId) {
2207
2320
  const isUnclaimedBackgroundDispatch = sqlRun.dispatchMode === "background";
2208
2321
  const stillInsideRedispatchBound = shouldRedispatchUnclaimedBackgroundRun({ startedAt: sqlRun.startedAt });
2209
2322
  if (isUnclaimedBackgroundDispatch && !stillInsideRedispatchBound) {
2210
- const recovered = await reapUnclaimedBackgroundRun(sqlRun.id).catch(() => false);
2323
+ const recovered = await reapUnclaimedBackgroundRun(sqlRun.id).catch((error) => {
2324
+ captureError(error, {
2325
+ route: "/_agent-native/agent-chat/runs/active",
2326
+ aiTraceId: sqlRun.id,
2327
+ tags: {
2328
+ source: "agent-run-manager",
2329
+ phase: "unclaimed-background-reap",
2330
+ },
2331
+ extra: { runId: sqlRun.id, threadId },
2332
+ });
2333
+ return false;
2334
+ });
2211
2335
  if (recovered)
2212
2336
  return null;
2213
2337
  }
2214
- const reaped = await reapIfStale(sqlRun.id).catch(() => false);
2338
+ const reaped = await reapIfStale(sqlRun.id).catch((error) => {
2339
+ captureError(error, {
2340
+ route: "/_agent-native/agent-chat/runs/active",
2341
+ aiTraceId: sqlRun.id,
2342
+ tags: {
2343
+ source: "agent-run-manager",
2344
+ phase: "stale-run-reap",
2345
+ },
2346
+ extra: { runId: sqlRun.id, threadId },
2347
+ });
2348
+ return false;
2349
+ });
2215
2350
  if (reaped)
2216
2351
  return null;
2217
2352
  return {
@@ -2299,16 +2434,17 @@ function abortRunInMemory(runId, reason) {
2299
2434
  export function abortRun(runId, reason = "user") {
2300
2435
  const run = activeRuns.get(runId);
2301
2436
  const abortedInMemory = abortRunInMemory(runId, reason);
2437
+ const markAborted = () => markRunAborted(runId, reason);
2302
2438
  void flushRunEventsBeforeAbort(run)
2303
- .then(() => markRunAborted(runId, reason))
2439
+ .then(markAborted, markAborted)
2304
2440
  .catch((error) => {
2305
2441
  captureError(error, {
2306
2442
  route: "/_agent-native/agent-chat/runs/:id/abort",
2307
2443
  aiTraceId: runId,
2308
- tags: { source: "agent-run-manager", phase: "abort-run" },
2444
+ tags: { source: "agent-run-manager", phase: "abort-run-status" },
2309
2445
  extra: { runId, threadId: run?.threadId, reason },
2310
2446
  });
2311
- console.error("[run-manager] durable abort persistence failed:", error instanceof Error ? error.message : error);
2447
+ console.error("[run-manager] durable abort status write failed:", error instanceof Error ? error.message : error);
2312
2448
  });
2313
2449
  return abortedInMemory;
2314
2450
  }
@@ -2318,12 +2454,27 @@ export function abortRun(runId, reason = "user") {
2318
2454
  * must use this path; otherwise the recovery POST can race the old row while
2319
2455
  * it is still marked running.
2320
2456
  */
2321
- export async function abortRunDurably(runId, reason = "user") {
2457
+ export async function abortRunDurably(runId, reason = "user", options) {
2322
2458
  const run = activeRuns.get(runId);
2323
2459
  const abortedInMemory = abortRunInMemory(runId, reason);
2324
2460
  try {
2325
- await flushRunEventsBeforeAbort(run);
2326
- await markRunAborted(runId, reason);
2461
+ const eventFlushFailure = await flushRunEventsBeforeAbort(run);
2462
+ const abortPersisted = await markRunAborted(runId, reason);
2463
+ if (!abortPersisted || eventFlushFailure) {
2464
+ const persistedStatus = await getRunStatus(runId);
2465
+ if (persistedStatus === null) {
2466
+ // Agent Teams persists Stop intent before run registration.
2467
+ // Workers recheck it before dispatch.
2468
+ if (options?.allowNotStarted && !run && !eventFlushFailure)
2469
+ return false;
2470
+ throw new Error(`Cannot confirm durable abort for missing run ${runId}`);
2471
+ }
2472
+ if (!abortPersisted && persistedStatus === "running") {
2473
+ throw new Error(`Durable abort did not transition run ${runId}`);
2474
+ }
2475
+ }
2476
+ if (eventFlushFailure)
2477
+ throw eventFlushFailure;
2327
2478
  }
2328
2479
  catch (error) {
2329
2480
  captureError(error, {
@@ -2341,19 +2492,57 @@ export async function abortRunDurably(runId, reason = "user") {
2341
2492
  },
2342
2493
  });
2343
2494
  console.error("[run-manager] durable abort persistence failed:", error instanceof Error ? error.message : error);
2495
+ throw error;
2344
2496
  }
2345
2497
  return abortedInMemory;
2346
2498
  }
2347
- /** Abort after the durable run store confirms no model or tool work is in flight. */
2348
- export async function abortRunAtSafeBoundaryDurably(runId, reason = "user") {
2499
+ async function abortRunAtSafeBoundaryDurablyResult(runId, reason = "user") {
2349
2500
  const run = activeRuns.get(runId);
2350
- await flushRunEventsBeforeAbort(run);
2351
- const aborted = await markRunAbortedAtSafeBoundary(runId, reason);
2352
- if (!aborted)
2353
- return false;
2501
+ const eventFlushFailure = await flushRunEventsBeforeAbort(run);
2502
+ let aborted;
2503
+ try {
2504
+ aborted = await markRunAbortedAtSafeBoundary(runId, reason);
2505
+ }
2506
+ catch (error) {
2507
+ try {
2508
+ const state = await getRunAbortState(runId);
2509
+ if (state.aborted && run) {
2510
+ abortRunInMemory(runId, state.reason ?? reason);
2511
+ }
2512
+ }
2513
+ catch (stateError) {
2514
+ captureError(stateError, {
2515
+ route: "/_agent-native/agent-chat/runs/:id/abort",
2516
+ aiTraceId: runId,
2517
+ tags: {
2518
+ source: "agent-run-manager",
2519
+ phase: "safe-boundary-abort-state-read",
2520
+ },
2521
+ extra: {
2522
+ runId,
2523
+ reason,
2524
+ abortError: error instanceof Error ? error.message : String(error),
2525
+ },
2526
+ });
2527
+ }
2528
+ if (eventFlushFailure) {
2529
+ throw new AggregateError([eventFlushFailure, error], "Run abort event flush and marker persistence failed");
2530
+ }
2531
+ throw error;
2532
+ }
2533
+ if (!aborted) {
2534
+ return { aborted: false, eventFlushFailure };
2535
+ }
2354
2536
  if (run)
2355
2537
  abortInMemoryRun(run, reason);
2356
- return true;
2538
+ return { aborted: true, eventFlushFailure };
2539
+ }
2540
+ /** Abort after the durable run store confirms no model or tool work is in flight. */
2541
+ export async function abortRunAtSafeBoundaryDurably(runId, reason = "user") {
2542
+ const result = await abortRunAtSafeBoundaryDurablyResult(runId, reason);
2543
+ if (result.eventFlushFailure)
2544
+ throw result.eventFlushFailure;
2545
+ return result.aborted;
2357
2546
  }
2358
2547
  export async function abortTurnByRefDurably(threadId, turnId, reason = "user") {
2359
2548
  const fence = currentExecutionFence();
@@ -2369,8 +2558,22 @@ export async function abortTurnByRefDurably(threadId, turnId, reason = "user") {
2369
2558
  runsToFlush.push(run);
2370
2559
  }
2371
2560
  }
2372
- await Promise.all(runsToFlush.map((run) => flushRunEventsBeforeAbort(run)));
2373
- return markTurnAborted(threadId, turnId, reason);
2561
+ const flushFailures = await Promise.all(runsToFlush.map((run) => flushRunEventsBeforeAbort(run)));
2562
+ let result;
2563
+ try {
2564
+ result = await markTurnAborted(threadId, turnId, reason);
2565
+ }
2566
+ catch (error) {
2567
+ const eventFlushFailure = flushFailures.find(Boolean);
2568
+ if (eventFlushFailure) {
2569
+ throw new AggregateError([eventFlushFailure, error], "Turn abort event flush and marker persistence failed");
2570
+ }
2571
+ throw error;
2572
+ }
2573
+ const eventFlushFailure = flushFailures.find(Boolean);
2574
+ if (eventFlushFailure)
2575
+ throw eventFlushFailure;
2576
+ return result;
2374
2577
  }
2375
2578
  export async function abortTurnDurably(runId, reason = "user") {
2376
2579
  const memRun = activeRuns.get(runId);
@@ -2393,4 +2596,40 @@ export async function abortTurnDurably(runId, reason = "user") {
2393
2596
  throw error;
2394
2597
  }
2395
2598
  }
2599
+ /** Persist a Stop's turn marker even when a terminal event flush must be reported. */
2600
+ export async function abortRunAndTurnDurably(runId, reason = "user", options) {
2601
+ let abortedInMemory = false;
2602
+ let terminalFlushFailure;
2603
+ try {
2604
+ if (options?.safeBoundary) {
2605
+ const result = await abortRunAtSafeBoundaryDurablyResult(runId, reason);
2606
+ abortedInMemory = result.aborted;
2607
+ terminalFlushFailure = result.eventFlushFailure ?? undefined;
2608
+ }
2609
+ else {
2610
+ abortedInMemory = await abortRunDurably(runId, reason);
2611
+ }
2612
+ }
2613
+ catch (error) {
2614
+ if (!(error instanceof AgentRunEventNotPersistedError) ||
2615
+ !error.terminalRun) {
2616
+ throw error;
2617
+ }
2618
+ terminalFlushFailure = error;
2619
+ }
2620
+ if (!options?.safeBoundary || abortedInMemory) {
2621
+ try {
2622
+ await abortTurnDurably(runId, reason);
2623
+ }
2624
+ catch (error) {
2625
+ if (terminalFlushFailure) {
2626
+ throw new AggregateError([terminalFlushFailure, error], "Run event flush and turn abort persistence failed");
2627
+ }
2628
+ throw error;
2629
+ }
2630
+ }
2631
+ if (terminalFlushFailure)
2632
+ throw terminalFlushFailure;
2633
+ return abortedInMemory;
2634
+ }
2396
2635
  export { getSlotHoldingRunId, tryClaimRunSlot } from "./run-store.js";
@@ -353,7 +353,7 @@ export declare function failRunBeforeDispatch(runId: string, errorCode: string,
353
353
  export declare function releaseBackgroundRunBeforeStart(runId: string, errorCode: string, errorDetail: string): Promise<void>;
354
354
  export declare function getRunStatus(runId: string): Promise<string | null>;
355
355
  export declare function terminalEventForAbortReason(reason: string | undefined): AgentChatEvent;
356
- export declare function markRunAborted(runId: string, reason?: string): Promise<void>;
356
+ export declare function markRunAborted(runId: string, reason?: string): Promise<boolean>;
357
357
  /** Abort only when no model or tool work has crossed its dispatch boundary. */
358
358
  export declare function markRunAbortedAtSafeBoundary(runId: string, reason?: string): Promise<boolean>;
359
359
  /** Serialize a task lifecycle change with worker lease claims and writes. */
@@ -372,7 +372,8 @@ export declare function getRunAbortState(runId: string): Promise<{
372
372
  reason?: string;
373
373
  }>;
374
374
  export declare class AgentRunEventNotPersistedError extends Error {
375
- constructor(runId: string, seq: number);
375
+ readonly terminalRun: boolean;
376
+ constructor(runId: string, seq: number | null, terminalRun?: boolean, cause?: unknown);
376
377
  }
377
378
  export declare function insertRunEvent(runId: string, seq: number, eventData: string, options?: {
378
379
  toolInputSource?: "execution";
@@ -4,6 +4,7 @@ import { ensureChatThreadTables, getThread, markQueuedSubmissionSubmitted, updat
4
4
  import { toPostgresParams, withDbExec } from "../db/client.js";
5
5
  import { withMigrationRuntime } from "../db/migration-runtime.js";
6
6
  import { withExecutionFence } from "./execution-fence.js";
7
+ import { abortRunAtSafeBoundaryDurably } from "./run-manager.js";
7
8
  import { beginAgentExecutionStep, claimTurnExecutionLease, completeAgentExecutionStep, ensureRunTables, insertRun, insertRunEvent, markAgentExecutionStepDispatched, } from "./run-store.js";
8
9
  import { applySubmittedUserMessage, buildUserMessage, extractThreadMeta, } from "./thread-data-builder.js";
9
10
  function executor(sql) {
@@ -38,6 +39,7 @@ function waitForContinue() {
38
39
  process.stdin.resume();
39
40
  });
40
41
  }
42
+ let databaseBackendPid;
41
43
  async function main() {
42
44
  const databaseUrl = process.env.AGENT_RUN_POSTGRES_TEST_URL; // config-ok: isolated integration-test database selector, not app runtime config
43
45
  if (!databaseUrl)
@@ -112,6 +114,17 @@ async function main() {
112
114
  }));
113
115
  return;
114
116
  }
117
+ if (mode === "durable-abort") {
118
+ const [{ pid }] = await sql `SELECT pg_backend_pid() AS pid`;
119
+ databaseBackendPid = Number(pid);
120
+ const aborted = await abortRunAtSafeBoundaryDurably(runId, stepId ?? "user");
121
+ process.stdout.write(JSON.stringify({
122
+ aborted,
123
+ processId: process.pid,
124
+ databaseBackendPid,
125
+ }));
126
+ return;
127
+ }
115
128
  await insertRun(runId, threadId, turnId);
116
129
  const fence = await claimTurnExecutionLease(threadId, turnId, runId);
117
130
  if (mode === "claim") {
@@ -170,6 +183,11 @@ main().catch((error) => {
170
183
  const code = error && typeof error === "object" && "code" in error
171
184
  ? String(error.code)
172
185
  : undefined;
173
- process.stdout.write(JSON.stringify({ error: String(error), code }));
186
+ process.stdout.write(JSON.stringify({
187
+ error: String(error),
188
+ code,
189
+ processId: process.pid,
190
+ databaseBackendPid,
191
+ }));
174
192
  process.exitCode = code === "turn_execution_lease_busy" ? 3 : 1;
175
193
  });