@agent-native/core 0.219.0 → 0.220.0-nightly-20261023183804

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 (68) hide show
  1. package/dist/agent/execution-fence.js +5 -1
  2. package/dist/agent/production-agent.d.ts +1 -1
  3. package/dist/agent/production-agent.js +42 -9
  4. package/dist/agent/run-manager.js +81 -42
  5. package/dist/agent/run-store.d.ts +1 -0
  6. package/dist/agent/run-store.js +260 -31
  7. package/dist/agent/run-terminal-tracking.d.ts +26 -0
  8. package/dist/agent/run-terminal-tracking.js +54 -0
  9. package/dist/agent/run-turn-id-header.d.ts +9 -0
  10. package/dist/agent/run-turn-id-header.js +13 -0
  11. package/dist/chat-threads/store.d.ts +12 -6
  12. package/dist/chat-threads/store.js +35 -28
  13. package/dist/cli/amplify-stream.js +1 -1
  14. package/dist/client/agent-chat/index.d.ts +1 -0
  15. package/dist/client/agent-chat/index.js +1 -0
  16. package/dist/client/agent-chat.d.ts +2 -0
  17. package/dist/client/agent-chat.js +31 -5
  18. package/dist/client/chat/agentkit-agent-native.js +8 -1
  19. package/dist/client/chat/agentkit-protocol.js +7 -1
  20. package/dist/client/chat/run-outcome-telemetry.js +1 -0
  21. package/dist/client/chat/run-outcome.d.ts +5 -0
  22. package/dist/client/chat/runtime.d.ts +34 -1
  23. package/dist/client/chat/runtime.js +96 -12
  24. package/dist/client/chat/turn-submitted-telemetry.d.ts +4 -0
  25. package/dist/client/chat/turn-submitted-telemetry.js +39 -0
  26. package/dist/client/chat/turn-submitted.d.ts +39 -0
  27. package/dist/client/chat/turn-submitted.js +16 -0
  28. package/dist/client/route-chunk-recovery.d.ts +3 -8
  29. package/dist/client/route-chunk-recovery.js +11 -9
  30. package/dist/collab/routes.d.ts +2 -2
  31. package/dist/db/client.d.ts +1 -0
  32. package/dist/db/client.js +28 -18
  33. package/dist/file-upload/actions/upload-image.d.ts +1 -1
  34. package/dist/mcp/embed-app.js +2 -1
  35. package/dist/mcp/screen-memory-stdio.d.ts +7 -7
  36. package/dist/notifications/routes.d.ts +4 -4
  37. package/dist/observability/routes.d.ts +7 -7
  38. package/dist/observability/traces.d.ts +2 -0
  39. package/dist/observability/traces.js +4 -1
  40. package/dist/progress/routes.d.ts +1 -1
  41. package/dist/provider-api/actions/custom-provider-registration.d.ts +16 -16
  42. package/dist/provider-api/actions/provider-api.d.ts +13 -13
  43. package/dist/provider-api/corpus-jobs.d.ts +2 -2
  44. package/dist/resources/handlers.d.ts +1 -1
  45. package/dist/review/index.d.ts +1 -1
  46. package/dist/review/index.js +1 -1
  47. package/dist/review/suggestions/actions.js +25 -17
  48. package/dist/review/suggestions/store.d.ts +14 -1
  49. package/dist/review/suggestions/store.js +16 -3
  50. package/dist/review/suggestions/types.d.ts +15 -1
  51. package/dist/server/agent-chat/browser-team-tools.js +39 -6
  52. package/dist/server/agent-chat/script-entries.js +10 -4
  53. package/dist/server/agent-chat-plugin.d.ts +2 -0
  54. package/dist/server/agent-chat-plugin.js +38 -13
  55. package/dist/server/agent-engine-api-key-route.d.ts +1 -1
  56. package/dist/server/agent-engine-default-model-route.d.ts +2 -2
  57. package/dist/server/first-touch-route.d.ts +2 -2
  58. package/dist/shared/route-chunk-recovery-bootstrap.d.ts +9 -0
  59. package/dist/shared/route-chunk-recovery-bootstrap.js +25 -0
  60. package/dist/sharing/actions/list-resource-shares.d.ts +1 -1
  61. package/dist/templates/workspace-core/.agents/skills/external-agents/references/mcp-apps-embedding.md +12 -5
  62. package/dist/tracking/providers.js +3 -0
  63. package/docs/content/external-agents.mdx +1 -1
  64. package/docs/content/mcp-apps.mdx +3 -3
  65. package/docs/content/mcp-protocol.mdx +1 -1
  66. package/docs/content/template-assets-integrations.mdx +5 -3
  67. package/package.json +4 -4
  68. package/src/templates/workspace-core/.agents/skills/external-agents/references/mcp-apps-embedding.md +12 -5
@@ -1,5 +1,5 @@
1
1
  import { AsyncLocalStorage } from "node:async_hooks";
2
- import { getDbExec } from "../db/client.js";
2
+ import { getDbExec, getScopedTransactionDbExec, } from "../db/client.js";
3
3
  export class ExecutionFenceError extends Error {
4
4
  code;
5
5
  constructor(code, message) {
@@ -106,6 +106,10 @@ export async function withFencedExecutionWrite(write, scope) {
106
106
  if (!scopedFence) {
107
107
  throw new StaleExecutionFenceError(fence);
108
108
  }
109
+ if (getScopedTransactionDbExec() === client) {
110
+ await lockAndAssertCurrentExecutionFences(client);
111
+ return write(client);
112
+ }
109
113
  if (!client.transaction) {
110
114
  throw new Error("Fenced execution writes require transaction support");
111
115
  }
@@ -384,7 +384,7 @@ export interface ProductionAgentOptions {
384
384
  runSoftTimeoutMs?: number;
385
385
  runNoProgressTimeoutMs?: number;
386
386
  durableBackgroundRuns?: boolean;
387
- onRunStart?: (send: (event: AgentChatEvent) => void, threadId: string, runId: string) => void | Promise<void>;
387
+ onRunStart?: (send: (event: AgentChatEvent) => void, threadId: string, runId: string, turnId: string) => void | Promise<void>;
388
388
  /**
389
389
  * Called when the run's engine, model, and effort resolve, and again before
390
390
  * each model call, because a call can run on a rate-limit fallback model or
@@ -90,6 +90,7 @@ import { ProcessorChain, TripWire, toolCallsFromContent, } from "./processors.js
90
90
  import { applyUncheckedDefaultModelReplacement } from "./provider-model-selection.js";
91
91
  import { startRun, subscribeToRun, replayCompletedTurn, getActiveRunForThread, getActiveRunForThreadAsync, getRun, abortRun, abortRunDurably, abortRunAndTurnDurably, abortRunAtSafeBoundaryDurably, abortTurnByRefDurably, abortTurnDurably, getSlotHoldingRunId, tryClaimRunSlot, isHostedRuntime, resolveRunSoftTimeoutMs, resolveRunToolTimeoutCeilingMs, endsAfterToolResultWithoutAssistantFinal, endsDuringActionPreparation, } from "./run-manager.js";
92
92
  import { writeLedgerEntry, readLedgerEntry, beginAgentExecutionStep, completeAgentExecutionStep, markAgentExecutionStepDispatched, markAgentExecutionStepUnknown, recordLateAgentExecutionStepOutcome, clearLedgerForThread, AgentTurnInitiatorMismatchError, AgentTurnInitiatorUnavailableError, insertRun, insertRunEvent, isRunAborted, isTurnAborted, markRunAborted, updateRunHeartbeat, updateRunStatusIfRunning, updateRunTerminalStateIfRunning, setRunError, setRunTerminalReason, claimBackgroundRun, readBackgroundRunClaim, recordRunDiagnostic, countRunsForTurn, RUN_DIAG_STAGE, UNCLAIMED_BACKGROUND_RUN_GRACE_MS, turnRunLedgerExhausted, } from "./run-store.js";
93
+ import { setRunTurnIdHeader } from "./run-turn-id-header.js";
93
94
  import { buildCurrentTimeUserContext } from "./runtime-context.js";
94
95
  import { claimSetupResume, setupResumeRefusedRunId, } from "./setup-resume-claim.js";
95
96
  import { getStrictToolInputValidator } from "./strict-tool-input.js";
@@ -4448,6 +4449,20 @@ async function prepareToolCall(entry, input, signal, timeoutMs) {
4448
4449
  function intentIsReadOnly(intent) {
4449
4450
  return intent?.valid === true && intent.readOnly === true;
4450
4451
  }
4452
+ function isBoundedTaskWait(toolName, input) {
4453
+ if (toolName !== "agent-teams" || !input || typeof input !== "object") {
4454
+ return false;
4455
+ }
4456
+ const args = input;
4457
+ // Only these operations honor the task reader's bounded wait contract.
4458
+ return ((args.action === "status" || args.action === "read-result") &&
4459
+ typeof args.taskId === "string" &&
4460
+ args.taskId.trim().length > 0 &&
4461
+ typeof args.waitMs === "number" &&
4462
+ Number.isInteger(args.waitMs) &&
4463
+ args.waitMs > 0 &&
4464
+ args.waitMs <= 20_000);
4465
+ }
4451
4466
  function normalizeOptionalToolPlaceholders(schema, input) {
4452
4467
  if (!schema?.properties || !input || typeof input !== "object") {
4453
4468
  return { input, changed: false };
@@ -5977,7 +5992,12 @@ export async function runAgentLoop(opts) {
5977
5992
  const actionRetrySafe = actionEntry && intent?.valid
5978
5993
  ? actionCallIsRetrySafe(actionEntry, preparedCallArguments(intent))
5979
5994
  : false;
5980
- const repeatGuardStopFromThisCall = noteRepeatedToolCall(toolCall.name, toolCall.input, toolCall.id);
5995
+ const repeatGuardStopFromThisCall = actionIsReadOnly &&
5996
+ actionEntry?.dedupe === false &&
5997
+ intent?.valid === true &&
5998
+ isBoundedTaskWait(toolCall.name, preparedCallArguments(intent))
5999
+ ? null
6000
+ : noteRepeatedToolCall(toolCall.name, toolCall.input, toolCall.id);
5981
6001
  const rollbackRepeatedToolCall = () => {
5982
6002
  const repeatKey = toolCallCacheKey(toolCall.name, toolCall.input);
5983
6003
  const repeatCall = inFlightRepeatToolCalls.get(toolCall.id);
@@ -9398,6 +9418,7 @@ export function createProductionAgentHandler(options) {
9398
9418
  setResponseHeader(event, "Cache-Control", "no-cache");
9399
9419
  setResponseHeader(event, "Connection", "keep-alive");
9400
9420
  setResponseHeader(event, "X-Run-Id", result.submittedRunId);
9421
+ setRunTurnIdHeader(event, getRun(result.submittedRunId)?.turnId);
9401
9422
  setResponseHeader(event, "X-Dispatch-Mode", "replay");
9402
9423
  return stream;
9403
9424
  }
@@ -9486,6 +9507,7 @@ export function createProductionAgentHandler(options) {
9486
9507
  setResponseHeader(event, "Cache-Control", "no-cache");
9487
9508
  setResponseHeader(event, "Connection", "keep-alive");
9488
9509
  setResponseHeader(event, "X-Run-Id", submittedRunId);
9510
+ setRunTurnIdHeader(event, getRun(submittedRunId)?.turnId);
9489
9511
  setResponseHeader(event, "X-Dispatch-Mode", "replay");
9490
9512
  return stream;
9491
9513
  }
@@ -10780,6 +10802,11 @@ export function createProductionAgentHandler(options) {
10780
10802
  !queuedMessageId &&
10781
10803
  !queuedMessageClaimId &&
10782
10804
  !isBackgroundWorker);
10805
+ const queuedSubmissionStore = shouldPreserveQueuedOrder
10806
+ ? await import("../chat-threads/store.js")
10807
+ : undefined;
10808
+ // The inbox read runs under the slot lock, but its schema must be ready first.
10809
+ await queuedSubmissionStore?.ensureChatThreadTables();
10783
10810
  slot = await tryClaimRunSlot(threadId, runId, undefined, {
10784
10811
  turnId: effectiveTurnId,
10785
10812
  ...(turnInitiator ? { turnInitiator } : {}),
@@ -10795,12 +10822,9 @@ export function createProductionAgentHandler(options) {
10795
10822
  ? { dispatchPayload: durableDispatchPayload }
10796
10823
  : {}),
10797
10824
  ...(continueOf ? { continueOf } : {}),
10798
- ...(shouldPreserveQueuedOrder
10825
+ ...(queuedSubmissionStore
10799
10826
  ? {
10800
- hasPendingQueuedSubmission: async (tx) => {
10801
- const { hasPendingQueuedSubmissionReceipt } = await import("../chat-threads/store.js");
10802
- return hasPendingQueuedSubmissionReceipt(tx, runThreadId);
10803
- },
10827
+ hasPendingQueuedSubmission: (tx) => queuedSubmissionStore.hasPendingQueuedSubmissionReceipt(tx, runThreadId),
10804
10828
  }
10805
10829
  : {}),
10806
10830
  });
@@ -10833,6 +10857,9 @@ export function createProductionAgentHandler(options) {
10833
10857
  setResponseHeader(event, "Cache-Control", "no-cache");
10834
10858
  setResponseHeader(event, "Connection", "keep-alive");
10835
10859
  setResponseHeader(event, "X-Run-Id", slot.completedRunId);
10860
+ // The client leaves a replay's turn out unless named here, and this
10861
+ // replay was found by its turn, so the stored turn is known.
10862
+ setRunTurnIdHeader(event, effectiveTurnId);
10836
10863
  setResponseHeader(event, "X-Dispatch-Mode", "replay");
10837
10864
  return stream;
10838
10865
  }
@@ -11074,6 +11101,7 @@ export function createProductionAgentHandler(options) {
11074
11101
  setResponseHeader(event, "Cache-Control", "no-cache");
11075
11102
  setResponseHeader(event, "Connection", "keep-alive");
11076
11103
  setResponseHeader(event, "X-Run-Id", duplicateRunId);
11104
+ setRunTurnIdHeader(event, getRun(duplicateRunId)?.turnId);
11077
11105
  setResponseHeader(event, "X-Dispatch-Mode", "replay");
11078
11106
  return stream;
11079
11107
  }
@@ -11174,6 +11202,7 @@ export function createProductionAgentHandler(options) {
11174
11202
  setResponseHeader(event, "Cache-Control", "no-cache");
11175
11203
  setResponseHeader(event, "Connection", "keep-alive");
11176
11204
  setResponseHeader(event, "X-Run-Id", runId);
11205
+ setRunTurnIdHeader(event, effectiveTurnId);
11177
11206
  setResponseHeader(event, "X-Dispatch-Mode", "background");
11178
11207
  return stream;
11179
11208
  }
@@ -11503,7 +11532,7 @@ export function createProductionAgentHandler(options) {
11503
11532
  void retainRequestDbPoolScope(recordRunDiagnostic(runId, RUN_DIAG_STAGE.setupTimings, setupDetail).catch(() => { })).catch(() => { });
11504
11533
  }
11505
11534
  if (options.onRunStart) {
11506
- await options.onRunStart(send, threadId ?? runId, runId);
11535
+ await options.onRunStart(send, threadId ?? runId, runId, effectiveTurnId);
11507
11536
  }
11508
11537
  const unknownOutcomes = noUnknownOutcomes();
11509
11538
  if (customAgentRefs.length > 0) {
@@ -11938,6 +11967,7 @@ export function createProductionAgentHandler(options) {
11938
11967
  loopOpts: agentLoopOpts,
11939
11968
  runId,
11940
11969
  threadId: threadId ?? null,
11970
+ turnId: effectiveTurnId,
11941
11971
  userId: ownerEmail,
11942
11972
  config: obsConfig,
11943
11973
  spanName: turnUsageLabel && turnUsageLabel !== "chat"
@@ -12047,10 +12077,12 @@ export function createProductionAgentHandler(options) {
12047
12077
  : "foreground",
12048
12078
  runRowAlreadyInserted: foregroundRunRowInserted,
12049
12079
  executionLease: true,
12080
+ // An anonymous run is not a user; `run_started` does not name one either.
12081
+ userId: turnInitiator?.anonymous
12082
+ ? undefined
12083
+ : (ownerEmail ?? undefined),
12050
12084
  // Resolved AFTER stored-model/experiment overrides — the same value
12051
12085
  // actually sent to the engine, not the raw client-requested model.
12052
- // No userId here: `ownerEmail` is the only identity known at this
12053
- // scope and is PII (email), which the terminal event must not carry.
12054
12086
  model: effectiveModel,
12055
12087
  engineName: engine.name,
12056
12088
  attemptCount: backgroundContinuationCount,
@@ -12068,6 +12100,7 @@ export function createProductionAgentHandler(options) {
12068
12100
  setResponseHeader(event, "Cache-Control", "no-cache");
12069
12101
  setResponseHeader(event, "Connection", "keep-alive");
12070
12102
  setResponseHeader(event, "X-Run-Id", runId);
12103
+ setRunTurnIdHeader(event, effectiveTurnId);
12071
12104
  setResponseHeader(event, "X-Dispatch-Mode", foregroundSelfChainEligible ? "foreground-self-chain" : "foreground");
12072
12105
  return stream;
12073
12106
  });
@@ -7,11 +7,11 @@ import { ServicePrincipalRefusedError } from "../org/service-principal-guard.js"
7
7
  import { captureError } from "../server/capture-error.js";
8
8
  import { isLlmCredentialError, LLM_MISSING_CREDENTIALS_ERROR_CODE, LLM_MISSING_CREDENTIALS_MESSAGE, } from "./engine/credential-errors.js";
9
9
  import { classifyTerminalErrorCode, describeErrorWithCauses, isProviderConnectionError, } from "./engine/error-detail.js";
10
- import { runErrorTelemetryProperties } from "./engine/error-telemetry.js";
11
10
  import { getAgentEngineEntry } from "./engine/registry.js";
12
11
  import { EngineError } from "./engine/types.js";
13
12
  import { currentExecutionFence, MissingExecutionFenceError, StaleExecutionFenceError, withExecutionFence, withExecutionLeaseRequested, } from "./execution-fence.js";
14
- import { AgentRunEventNotPersistedError, insertRun, insertRunEvent, insertRunEvents, updateRunStatusIfRunning, markRunAborted, markRunAbortedAtSafeBoundary, getRunAbortState, getRunStatus, getRunEventsSince, getCurrentTurnRunEventsForThread, getRunById, getRunByThread, getRunTurnRef, listRunsForThread, 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";
13
+ import { AgentRunEventNotPersistedError, insertRun, insertRunEvent, insertRunEvents, updateRunStatusIfRunning, markRunAborted, markRunAbortedAtSafeBoundary, getRunAbortState, getRunStatus, getRunEventsSince, getCurrentTurnRunEventsForThread, getRunById, getRunByThread, getRunTurnRef, listRunsForThread, markTurnAborted, cleanupOldRuns, updateRunHeartbeat, bumpRunProgress, setRunInFlightMarker, reapIfStale, reapUnclaimedBackgroundRun, shouldRedispatchUnclaimedBackgroundRun, reconcileTerminalRunFromEvents, ensureTerminalRunEvent, getLastTerminalRunEvent, resolveErroredRunTerminalEvent, setRunError, setRunTerminalReason, setRunTerminalReasonIfStatus, 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 { trackRunTerminal } from "./run-terminal-tracking.js";
15
15
  import { CONTINUATION_REASONS, isContinuationTerminalReason } from "./types.js";
16
16
  const activeRuns = new Map();
17
17
  const threadToRun = new Map();
@@ -435,39 +435,24 @@ function emitRunTerminalTrackingEvent(args) {
435
435
  catch {
436
436
  // Metrics must never affect the agent run or its persisted status.
437
437
  }
438
- const properties = {
439
- source: "agent_run_manager",
440
- run_id: args.runId,
441
- thread_id: args.threadId,
442
- turn_id: args.turnId,
438
+ // An abort is reported by the SQL write that ended the row, which may be
439
+ // another process's stop; reporting it here too would double-count it.
440
+ if (args.status === "aborted")
441
+ return;
442
+ void trackRunTerminal({
443
+ runId: args.runId,
444
+ threadId: args.threadId,
445
+ turnId: args.turnId,
443
446
  status: args.status,
444
- terminal_reason: args.terminalReason,
445
- error_code: args.errorCode,
446
- ...(args.errorCode ? runErrorTelemetryProperties(args.errorCode) : {}),
447
- dispatch_mode: args.dispatchMode,
448
- abort_reason: args.abortReason,
449
- duration_ms: args.durationMs,
447
+ terminalReason: args.terminalReason,
448
+ errorCode: args.errorCode,
449
+ dispatchMode: args.dispatchMode,
450
+ abortReason: args.abortReason,
451
+ durationMs: args.durationMs,
450
452
  model: args.model,
451
- engine: args.engineName,
452
- attempt_count: args.attemptCount,
453
- };
454
- for (const key of Object.keys(properties)) {
455
- if (properties[key] === undefined)
456
- delete properties[key];
457
- }
458
- try {
459
- void Promise.all([
460
- import("../tracking/registry.js"),
461
- import("../observability/tracking-identity.js"),
462
- ])
463
- .then(([{ track }, { trackingIdentityProperties }]) => {
464
- track("agent_run_terminal", { ...properties, ...trackingIdentityProperties() }, { userId: args.userId });
465
- })
466
- .catch(() => { });
467
- }
468
- catch {
469
- // Tracking must never affect the agent run or its persisted status.
470
- }
453
+ engineName: args.engineName,
454
+ attemptCount: args.attemptCount,
455
+ }, { userId: args.userId });
471
456
  }
472
457
  function abortInMemoryRun(run, reason = "user") {
473
458
  run.abortReason = reason;
@@ -1208,6 +1193,7 @@ export function startRun(runId, threadId, runFn, onComplete, options) {
1208
1193
  let completionInProgress = false;
1209
1194
  let ownTerminalStatusEstablished = false;
1210
1195
  let terminalStatusWrite = null;
1196
+ let terminalStatusWriteOutcomeUnknown = false;
1211
1197
  const checkSqlAbort = () => {
1212
1198
  const now = Date.now();
1213
1199
  if (now - lastAbortCheck < 3000)
@@ -1223,8 +1209,18 @@ export function startRun(runId, threadId, runFn, onComplete, options) {
1223
1209
  const status = await getRunStatus(runId);
1224
1210
  if (status !== null && status !== "running") {
1225
1211
  const pendingStatusWrite = terminalStatusWrite;
1226
- if (pendingStatusWrite)
1227
- await pendingStatusWrite;
1212
+ if (pendingStatusWrite) {
1213
+ try {
1214
+ await pendingStatusWrite;
1215
+ }
1216
+ catch (error) {
1217
+ if (!terminalStatusWriteOutcomeUnknown)
1218
+ throw error;
1219
+ return;
1220
+ }
1221
+ }
1222
+ if (terminalStatusWriteOutcomeUnknown)
1223
+ return;
1228
1224
  }
1229
1225
  if (status !== null &&
1230
1226
  status !== "running" &&
@@ -1758,12 +1754,21 @@ export function startRun(runId, threadId, runFn, onComplete, options) {
1758
1754
  ? updateRunStatusIfRunning(runId, persistedStatus)
1759
1755
  : updateRunStatusIfRunning(runId, persistedStatus, terminalStatusOptions);
1760
1756
  const persistOwnTerminalState = async (persist) => {
1761
- const write = withWorkerFence(persist);
1757
+ let writeStarted = false;
1758
+ const write = withWorkerFence(() => {
1759
+ writeStarted = true;
1760
+ return persist();
1761
+ }).catch((error) => {
1762
+ if (writeStarted)
1763
+ terminalStatusWriteOutcomeUnknown = true;
1764
+ throw error;
1765
+ });
1762
1766
  terminalStatusWrite = write;
1763
1767
  try {
1764
1768
  const established = await write;
1765
- if (established)
1769
+ if (established && !terminalStatusWriteOutcomeUnknown) {
1766
1770
  ownTerminalStatusEstablished = true;
1771
+ }
1767
1772
  return established;
1768
1773
  }
1769
1774
  finally {
@@ -1772,11 +1777,45 @@ export function startRun(runId, threadId, runFn, onComplete, options) {
1772
1777
  }
1773
1778
  };
1774
1779
  const persistOwnTerminalStatus = () => persistOwnTerminalState(persistTerminalStatus);
1775
- const reconcileTerminalRun = () => persistOwnTerminalState(() => threadProjectionPending
1776
- ? reconcileTerminalRunFromEvents(runId, {
1777
- threadProjectionPending: true,
1778
- })
1779
- : reconcileTerminalRunFromEvents(runId));
1780
+ const confirmAmbiguousTerminalStatus = async () => {
1781
+ const persistedRun = await getRunById(runId);
1782
+ if (persistedRun?.status !== persistedStatus)
1783
+ return false;
1784
+ const expectedTerminalReason = terminalReason.slice(0, 200);
1785
+ if (persistedRun.terminalReason === expectedTerminalReason) {
1786
+ return true;
1787
+ }
1788
+ if (persistedRun.terminalReason)
1789
+ return false;
1790
+ return withWorkerFence(() => setRunTerminalReasonIfStatus(runId, persistedStatus, expectedTerminalReason));
1791
+ };
1792
+ const reconcileTerminalRun = async () => {
1793
+ let reconciled = false;
1794
+ try {
1795
+ reconciled = await persistOwnTerminalState(() => threadProjectionPending
1796
+ ? reconcileTerminalRunFromEvents(runId, {
1797
+ threadProjectionPending: true,
1798
+ })
1799
+ : reconcileTerminalRunFromEvents(runId));
1800
+ }
1801
+ catch (error) {
1802
+ if (!terminalStatusWriteOutcomeUnknown)
1803
+ throw error;
1804
+ }
1805
+ if (reconciled) {
1806
+ terminalStatusWriteOutcomeUnknown = false;
1807
+ ownTerminalStatusEstablished = true;
1808
+ return true;
1809
+ }
1810
+ if (!terminalStatusWriteOutcomeUnknown) {
1811
+ return reconciled;
1812
+ }
1813
+ const confirmed = await confirmAmbiguousTerminalStatus();
1814
+ terminalStatusWriteOutcomeUnknown = false;
1815
+ if (confirmed)
1816
+ ownTerminalStatusEstablished = true;
1817
+ return confirmed;
1818
+ };
1780
1819
  const reportTerminalStatusPersistenceError = (error) => {
1781
1820
  captureError(error, {
1782
1821
  route: "/_agent-native/agent-chat",
@@ -276,6 +276,7 @@ export declare function tryClaimRunSlot(threadId: string, runId: string, maxStal
276
276
  }>;
277
277
  export declare function setRunError(runId: string, errorCode: string | undefined, errorDetail: string | undefined): Promise<void>;
278
278
  export declare function setRunTerminalReason(runId: string, terminalReason: string | undefined): Promise<void>;
279
+ export declare function setRunTerminalReasonIfStatus(runId: string, expectedStatus: "completed" | "truncated" | "errored" | "aborted", terminalReason: string): Promise<boolean>;
279
280
  /**
280
281
  * Repair a run whose terminal event was durably appended but whose final
281
282
  * `agent_runs.status` write lost a race with reconnect/reaper code.