@agent-native/core 0.84.66 → 0.85.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (155) hide show
  1. package/corpus/README.md +2 -2
  2. package/corpus/core/CHANGELOG.md +20 -0
  3. package/corpus/core/docs/content/locales/ar-SA/tracking.mdx +55 -0
  4. package/corpus/core/docs/content/locales/de-DE/tracking.mdx +55 -0
  5. package/corpus/core/docs/content/locales/es-ES/tracking.mdx +55 -0
  6. package/corpus/core/docs/content/locales/fr-FR/tracking.mdx +55 -0
  7. package/corpus/core/docs/content/locales/hi-IN/tracking.mdx +55 -0
  8. package/corpus/core/docs/content/locales/ja-JP/tracking.mdx +55 -0
  9. package/corpus/core/docs/content/locales/ko-KR/tracking.mdx +55 -0
  10. package/corpus/core/docs/content/locales/pt-BR/tracking.mdx +55 -0
  11. package/corpus/core/docs/content/locales/zh-CN/tracking.mdx +55 -0
  12. package/corpus/core/docs/content/locales/zh-TW/tracking.mdx +55 -0
  13. package/corpus/core/docs/content/tracking.mdx +2 -0
  14. package/corpus/core/package.json +1 -1
  15. package/corpus/core/src/agent/production-agent.ts +199 -38
  16. package/corpus/core/src/agent/run-manager.ts +79 -0
  17. package/corpus/core/src/agent/run-store.ts +117 -5
  18. package/corpus/core/src/agent/types.ts +13 -0
  19. package/corpus/core/src/client/RunStuckBanner.tsx +14 -0
  20. package/corpus/core/src/client/agent-chat-adapter.ts +475 -0
  21. package/corpus/core/src/client/analytics.ts +2 -0
  22. package/corpus/core/src/client/error-format.ts +4 -0
  23. package/corpus/core/src/client/extensions/ExtensionsSidebarSection.tsx +1 -1
  24. package/corpus/core/src/client/session-replay.ts +774 -2
  25. package/corpus/core/src/client/sse-event-processor.ts +65 -9
  26. package/corpus/core/src/client/use-run-stuck-detection.ts +31 -7
  27. package/corpus/core/src/db/runtime-diagnostics.ts +331 -0
  28. package/corpus/core/src/server/agent-chat-plugin.ts +115 -2
  29. package/corpus/core/src/server/analytics.ts +1 -34
  30. package/corpus/core/src/server/core-routes-plugin.ts +91 -4
  31. package/corpus/core/src/server/http-response-telemetry.ts +189 -0
  32. package/corpus/core/src/server/self-dispatch.ts +29 -0
  33. package/corpus/core/src/server/ssr-handler.ts +18 -377
  34. package/corpus/templates/analytics/.agents/skills/session-replay/SKILL.md +53 -1
  35. package/corpus/templates/analytics/AGENTS.md +13 -0
  36. package/corpus/templates/analytics/actions/view-screen.ts +25 -0
  37. package/corpus/templates/analytics/app/components/layout/Sidebar.tsx +20 -0
  38. package/corpus/templates/analytics/app/global.css +55 -0
  39. package/corpus/templates/analytics/app/i18n/zh-TW.ts +100 -1
  40. package/corpus/templates/analytics/app/i18n-data.ts +1226 -0
  41. package/corpus/templates/analytics/app/pages/Settings.tsx +3 -0
  42. package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/EmailReportDialog.tsx +182 -144
  43. package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/index.tsx +11 -18
  44. package/corpus/templates/analytics/app/pages/analyses/AnalysisDetail.tsx +11 -18
  45. package/corpus/templates/analytics/app/pages/sessions/SessionDetailPage.tsx +49 -1
  46. package/corpus/templates/analytics/app/pages/sessions/SessionDevToolsPanel.tsx +557 -0
  47. package/corpus/templates/analytics/app/pages/sessions/session-replay-devtools.ts +274 -0
  48. package/corpus/templates/analytics/app/pages/settings/AlertRulesSettingsCard.tsx +974 -0
  49. package/corpus/templates/analytics/changelog/2026-07-02-alert-rules-can-now-be-viewed-and-managed-from-settings.md +6 -0
  50. package/corpus/templates/analytics/changelog/2026-07-02-recent-ask-chats-show-a-loading-placeholder-while-your-chat-.md +6 -0
  51. package/corpus/templates/analytics/changelog/2026-07-02-session-replays-now-capture-console-logs-and-network-request.md +6 -0
  52. package/corpus/templates/analytics/changelog/2026-07-02-sharing-status-labels-use-neutral-icons-instead-of-colored-d.md +6 -0
  53. package/corpus/templates/analytics/changelog/2026-07-02-the-session-replay-viewer-has-a-dev-tools-panel-with-console.md +6 -0
  54. package/corpus/templates/analytics/server/db/schema.ts +3 -0
  55. package/corpus/templates/analytics/server/jobs/analytics-alerts.ts +4 -0
  56. package/corpus/templates/analytics/server/lib/analytics-alerts.ts +142 -1
  57. package/corpus/templates/analytics/server/lib/session-replay-agent-context.ts +369 -8
  58. package/corpus/templates/analytics/server/lib/session-replay.ts +76 -2
  59. package/corpus/templates/analytics/server/routes/api/session-replay/agent-diagnostics.json.get.ts +101 -0
  60. package/corpus/templates/analytics/shared/session-replay-diagnostics.ts +78 -0
  61. package/corpus/templates/clips/server/routes/api/video/[recordingId].get.ts +0 -2
  62. package/corpus/templates/design/app/pages/DesignEditor.tsx +24 -12
  63. package/corpus/templates/design/changelog/2026-07-02-the-temporary-code-tab-is-hidden-from-the-design-editor-side.md +6 -0
  64. package/corpus/templates/forms/server/lib/public-form-ssr.ts +0 -1
  65. package/corpus/templates/plan/actions/create-visual-recap.ts +1 -1
  66. package/corpus/templates/plan/actions/update-local-plan-folder.ts +3 -1
  67. package/corpus/templates/plan/actions/update-visual-plan.ts +1 -1
  68. package/corpus/templates/plan/changelog/2026-07-02-trying-to-edit-a-recap-or-plan-you-can-only-view-now-explain.md +6 -0
  69. package/corpus/templates/plan/server/plans.ts +26 -9
  70. package/dist/agent/production-agent.d.ts.map +1 -1
  71. package/dist/agent/production-agent.js +155 -34
  72. package/dist/agent/production-agent.js.map +1 -1
  73. package/dist/agent/run-manager.d.ts +29 -0
  74. package/dist/agent/run-manager.d.ts.map +1 -1
  75. package/dist/agent/run-manager.js +73 -0
  76. package/dist/agent/run-manager.js.map +1 -1
  77. package/dist/agent/run-store.d.ts +41 -0
  78. package/dist/agent/run-store.d.ts.map +1 -1
  79. package/dist/agent/run-store.js +100 -4
  80. package/dist/agent/run-store.js.map +1 -1
  81. package/dist/agent/types.d.ts +13 -0
  82. package/dist/agent/types.d.ts.map +1 -1
  83. package/dist/agent/types.js.map +1 -1
  84. package/dist/client/RunStuckBanner.d.ts.map +1 -1
  85. package/dist/client/RunStuckBanner.js +13 -0
  86. package/dist/client/RunStuckBanner.js.map +1 -1
  87. package/dist/client/agent-chat-adapter.d.ts.map +1 -1
  88. package/dist/client/agent-chat-adapter.js +410 -0
  89. package/dist/client/agent-chat-adapter.js.map +1 -1
  90. package/dist/client/analytics.d.ts +1 -1
  91. package/dist/client/analytics.d.ts.map +1 -1
  92. package/dist/client/analytics.js.map +1 -1
  93. package/dist/client/error-format.d.ts.map +1 -1
  94. package/dist/client/error-format.js +2 -0
  95. package/dist/client/error-format.js.map +1 -1
  96. package/dist/client/extensions/ExtensionsSidebarSection.js +1 -1
  97. package/dist/client/extensions/ExtensionsSidebarSection.js.map +1 -1
  98. package/dist/client/session-replay.d.ts +36 -0
  99. package/dist/client/session-replay.d.ts.map +1 -1
  100. package/dist/client/session-replay.js +579 -1
  101. package/dist/client/session-replay.js.map +1 -1
  102. package/dist/client/sse-event-processor.d.ts +20 -1
  103. package/dist/client/sse-event-processor.d.ts.map +1 -1
  104. package/dist/client/sse-event-processor.js +42 -11
  105. package/dist/client/sse-event-processor.js.map +1 -1
  106. package/dist/client/use-run-stuck-detection.d.ts +14 -4
  107. package/dist/client/use-run-stuck-detection.d.ts.map +1 -1
  108. package/dist/client/use-run-stuck-detection.js +18 -4
  109. package/dist/client/use-run-stuck-detection.js.map +1 -1
  110. package/dist/collab/awareness.d.ts +2 -2
  111. package/dist/collab/awareness.d.ts.map +1 -1
  112. package/dist/collab/routes.d.ts +1 -1
  113. package/dist/db/runtime-diagnostics.d.ts +52 -0
  114. package/dist/db/runtime-diagnostics.d.ts.map +1 -0
  115. package/dist/db/runtime-diagnostics.js +250 -0
  116. package/dist/db/runtime-diagnostics.js.map +1 -0
  117. package/dist/observability/routes.d.ts +5 -5
  118. package/dist/provider-api/corpus-jobs.d.ts +2 -2
  119. package/dist/resources/handlers.d.ts +2 -2
  120. package/dist/secrets/routes.d.ts +3 -3
  121. package/dist/server/agent-chat-plugin.d.ts.map +1 -1
  122. package/dist/server/agent-chat-plugin.js +101 -2
  123. package/dist/server/agent-chat-plugin.js.map +1 -1
  124. package/dist/server/analytics.d.ts +1 -19
  125. package/dist/server/analytics.d.ts.map +1 -1
  126. package/dist/server/analytics.js +1 -31
  127. package/dist/server/analytics.js.map +1 -1
  128. package/dist/server/core-routes-plugin.d.ts +17 -0
  129. package/dist/server/core-routes-plugin.d.ts.map +1 -1
  130. package/dist/server/core-routes-plugin.js +65 -5
  131. package/dist/server/core-routes-plugin.js.map +1 -1
  132. package/dist/server/http-response-telemetry.d.ts +4 -0
  133. package/dist/server/http-response-telemetry.d.ts.map +1 -0
  134. package/dist/server/http-response-telemetry.js +174 -0
  135. package/dist/server/http-response-telemetry.js.map +1 -0
  136. package/dist/server/self-dispatch.d.ts +17 -0
  137. package/dist/server/self-dispatch.d.ts.map +1 -1
  138. package/dist/server/self-dispatch.js +11 -0
  139. package/dist/server/self-dispatch.js.map +1 -1
  140. package/dist/server/ssr-handler.d.ts.map +1 -1
  141. package/dist/server/ssr-handler.js +18 -271
  142. package/dist/server/ssr-handler.js.map +1 -1
  143. package/dist/server/transcribe-voice.d.ts +1 -1
  144. package/docs/content/locales/ar-SA/tracking.mdx +55 -0
  145. package/docs/content/locales/de-DE/tracking.mdx +55 -0
  146. package/docs/content/locales/es-ES/tracking.mdx +55 -0
  147. package/docs/content/locales/fr-FR/tracking.mdx +55 -0
  148. package/docs/content/locales/hi-IN/tracking.mdx +55 -0
  149. package/docs/content/locales/ja-JP/tracking.mdx +55 -0
  150. package/docs/content/locales/ko-KR/tracking.mdx +55 -0
  151. package/docs/content/locales/pt-BR/tracking.mdx +55 -0
  152. package/docs/content/locales/zh-CN/tracking.mdx +55 -0
  153. package/docs/content/locales/zh-TW/tracking.mdx +55 -0
  154. package/docs/content/tracking.mdx +2 -0
  155. package/package.json +1 -1
@@ -32,7 +32,7 @@ import { getDefaultMaxIterations, MAX_AGENT_MAX_ITERATIONS, MIN_AGENT_MAX_ITERAT
32
32
  import { maybeCompactThread, buildObservationalContext, hasObservationalMemory, serializeObservationalMemoryBlock, } from "./observational-memory/index.js";
33
33
  import { ProcessorChain, TripWire, toolCallsFromContent, } from "./processors.js";
34
34
  import { startRun, subscribeToRun, getActiveRunForThread, getActiveRunForThreadAsync, getRun, abortRun, tryClaimRunSlot, } from "./run-manager.js";
35
- import { writeLedgerEntry, readLedgerEntry, clearLedgerForThread, getCurrentTurnEventsForThread, insertRun, updateRunHeartbeat, updateRunStatusIfRunning, setRunTerminalReason, claimBackgroundRun, readBackgroundRunClaim, recordRunDiagnostic, RUN_DIAG_STAGE, UNCLAIMED_BACKGROUND_RUN_GRACE_MS, } from "./run-store.js";
35
+ import { writeLedgerEntry, readLedgerEntry, clearLedgerForThread, getCurrentTurnEventsForThread, insertRun, updateRunHeartbeat, updateRunStatusIfRunning, setRunTerminalReason, claimBackgroundRun, readBackgroundRunClaim, recordRunDiagnostic, countRunsForTurn, RUN_DIAG_STAGE, UNCLAIMED_BACKGROUND_RUN_GRACE_MS, } from "./run-store.js";
36
36
  import { classifyToolCallJournal, findCompletedJournalEntry, } from "./tool-call-journal.js";
37
37
  import { redactSensitiveFields, sanitizeToolErrorText, sanitizeToolErrorValue, } from "./tool-error-redaction.js";
38
38
  import { createToolSearchEntry, TOOL_SEARCH_ACTION_NAME, } from "./tool-search.js";
@@ -4246,8 +4246,13 @@ export function createProductionAgentHandler(options) {
4246
4246
  // Insert the run row up front so /runs/active sees it immediately and
4247
4247
  // the slot stays held while the background function cold-starts. Mark
4248
4248
  // it background-dispatched so the stale reaper uses the wider window.
4249
+ // The full request body is persisted ON the row (dispatch_payload) so
4250
+ // the self-POST below can carry only the tiny marker — Netlify caps
4251
+ // background-function request bodies at 256KB, and a large chat
4252
+ // history (inline attachments especially) silently exceeded that.
4249
4253
  await insertRun(runId, effectiveThreadId, effectiveTurnId, {
4250
4254
  dispatchMode: "background",
4255
+ dispatchPayload: JSON.stringify(body),
4251
4256
  });
4252
4257
  backgroundRowInserted = true;
4253
4258
  }
@@ -4275,15 +4280,29 @@ export function createProductionAgentHandler(options) {
4275
4280
  // the Authorization Bearer HMAC is preserved either way.
4276
4281
  path: backgroundDispatchPath,
4277
4282
  taskId: runId,
4278
- body: {
4279
- ...body,
4280
- // Carry the pre-claimed identity so the worker reuses this run.
4281
- [AGENT_CHAT_BACKGROUND_RUN_FIELD]: {
4282
- runId,
4283
- turnId: effectiveTurnId,
4284
- backgroundFunctionRuntimeExpected: expectsNetlifyBackgroundFunction,
4283
+ // When the row (and its persisted payload) landed, send only the
4284
+ // marker — the worker rehydrates the body from dispatch_payload
4285
+ // (`payloadRef`), keeping the self-POST far under Netlify's 256KB
4286
+ // background-function body cap. If the insert failed we fall back to
4287
+ // carrying the full body inline, exactly as before.
4288
+ body: backgroundRowInserted
4289
+ ? {
4290
+ [AGENT_CHAT_BACKGROUND_RUN_FIELD]: {
4291
+ runId,
4292
+ turnId: effectiveTurnId,
4293
+ backgroundFunctionRuntimeExpected: expectsNetlifyBackgroundFunction,
4294
+ payloadRef: true,
4295
+ },
4296
+ }
4297
+ : {
4298
+ ...body,
4299
+ // Carry the pre-claimed identity so the worker reuses this run.
4300
+ [AGENT_CHAT_BACKGROUND_RUN_FIELD]: {
4301
+ runId,
4302
+ turnId: effectiveTurnId,
4303
+ backgroundFunctionRuntimeExpected: expectsNetlifyBackgroundFunction,
4304
+ },
4285
4305
  },
4286
- },
4287
4306
  });
4288
4307
  dispatched = true;
4289
4308
  }
@@ -4503,6 +4522,23 @@ export function createProductionAgentHandler(options) {
4503
4522
  // foreground fallback, which is not a worker and rides the
4504
4523
  // connected client's auto_continue instead.)
4505
4524
  if (willChainBackgroundContinuation(run)) {
4525
+ // DURABLE PER-TURN LEDGER: bound the total number of runs one
4526
+ // logical turn may consume, counted in SQL. The in-marker
4527
+ // `continuationCount` resets whenever a fresh POST starts a new
4528
+ // chain for the same turn (client recovery, duplicate delivery),
4529
+ // so it cannot bound cross-chain loops — the SQL count survives
4530
+ // every recovery path and is what actually kills a pathological
4531
+ // turn (the "dozens of runs on one prompt" incident class).
4532
+ const turnRunCount = await countRunsForTurn(effectiveThreadId, effectiveTurnId).catch(() => null);
4533
+ if (turnRunCount !== null &&
4534
+ turnRunCount > MAX_BACKGROUND_RUN_CONTINUATIONS + 5) {
4535
+ console.error(`[agent-chat] turn ${effectiveTurnId} consumed ${turnRunCount} runs — refusing to chain further`, runId);
4536
+ const statusUpdated = await updateRunStatusIfRunning(runId, "errored").catch(() => false);
4537
+ if (statusUpdated) {
4538
+ await setRunTerminalReason(runId, "turn_continuation_budget_exhausted").catch(() => { });
4539
+ }
4540
+ return;
4541
+ }
4506
4542
  // Mint the next chunk's runId here and sign the dispatch token
4507
4543
  // over it, so the `_process-run` route's HMAC check and the
4508
4544
  // worker's run identity agree. Fresh runId (not this chunk's) so
@@ -4513,36 +4549,121 @@ export function createProductionAgentHandler(options) {
4513
4549
  const continuationReason = backgroundContinuationReasonForRun(run);
4514
4550
  const continuationDispatchPath = resolveAgentChatProcessRunDispatchPath();
4515
4551
  const continuationExpectsNetlifyBackgroundFunction = dispatchPathTargetsNetlifyBackgroundFunction(continuationDispatchPath);
4552
+ const continuationMarker = {
4553
+ runId: nextRunId,
4554
+ turnId: effectiveTurnId,
4555
+ continuationCount: backgroundContinuationCount + 1,
4556
+ continuationReason,
4557
+ ...(actionPreparationTool ? { actionPreparationTool } : {}),
4558
+ backgroundFunctionRuntimeExpected: continuationExpectsNetlifyBackgroundFunction,
4559
+ };
4560
+ // Strip this chunk's own marker before persisting/forwarding —
4561
+ // the next chunk gets the fresh marker above.
4562
+ const continuationBody = {
4563
+ ...body,
4564
+ internalContinuation: true,
4565
+ };
4566
+ delete continuationBody[AGENT_CHAT_BACKGROUND_RUN_FIELD];
4516
4567
  try {
4517
4568
  await recordRunDiagnostic(run.runId, RUN_DIAG_STAGE.workerSetupStep, `chain_dispatch_start nextRunId=${nextRunId} reason=${continuationReason} path=${continuationDispatchPath}`).catch(() => { });
4518
- await fireInternalDispatch({
4519
- event,
4520
- // Continuation chunks use the same path resolution as the
4521
- // initial dispatch: on hosted Netlify the background
4522
- // function's DEFAULT url (no custom config.path; async via
4523
- // background:true; never shadowed because /.netlify/* is
4524
- // excluded from the /* catch-all) so each chunk keeps the
4525
- // 15-min budget; off-Netlify the in-process framework route.
4526
- path: continuationDispatchPath,
4527
- taskId: nextRunId,
4528
- body: {
4529
- ...body,
4569
+ // ── TRANSACTIONAL HANDOFF ──────────────────────────────────
4570
+ // 1. Insert the successor row (with its rehydration payload)
4571
+ // BEFORE firing the dispatch, so:
4572
+ // - /runs/active shows an active run continuously across
4573
+ // the chunk boundary (no idle gap for the client to
4574
+ // misread as "the turn ended"), and
4575
+ // - a lost dispatch leaves a row the unclaimed-run sweep
4576
+ // reaps into a LOUD error instead of a silent hang.
4577
+ // 2. Await the dispatch response fully (`awaitResponse`) —
4578
+ // this worker's Lambda is about to finish, and the old
4579
+ // 250ms settle race let a still-in-flight handoff fetch be
4580
+ // killed by the post-return freeze WITHOUT rejecting: the
4581
+ // turn just stopped, silently. A Netlify background
4582
+ // function 202s on enqueue (normally well under a second),
4583
+ // and this chunk has minutes of budget headroom left, so
4584
+ // awaiting is cheap. Retried with backoff for transient
4585
+ // network blips.
4586
+ let nextRowInserted = false;
4587
+ try {
4588
+ await insertRun(nextRunId, effectiveThreadId, effectiveTurnId, {
4589
+ dispatchMode: "background",
4590
+ dispatchPayload: JSON.stringify(continuationBody),
4591
+ });
4592
+ nextRowInserted = true;
4593
+ }
4594
+ catch (insertErr) {
4595
+ await recordRunDiagnostic(run.runId, RUN_DIAG_STAGE.workerSetupStep, `chain_successor_insert_failed nextRunId=${nextRunId} ${insertErr instanceof Error
4596
+ ? insertErr.message
4597
+ : String(insertErr)}`).catch(() => { });
4598
+ console.error("[agent-chat] continuation insertRun failed; dispatching with inline body:", insertErr instanceof Error
4599
+ ? insertErr.message
4600
+ : insertErr);
4601
+ }
4602
+ const dispatchBody = nextRowInserted
4603
+ ? {
4530
4604
  internalContinuation: true,
4531
4605
  [AGENT_CHAT_BACKGROUND_RUN_FIELD]: {
4532
- runId: nextRunId,
4533
- turnId: effectiveTurnId,
4534
- continuationCount: backgroundContinuationCount + 1,
4535
- continuationReason,
4536
- ...(actionPreparationTool
4537
- ? { actionPreparationTool }
4538
- : {}),
4539
- backgroundFunctionRuntimeExpected: continuationExpectsNetlifyBackgroundFunction,
4606
+ ...continuationMarker,
4607
+ payloadRef: true,
4540
4608
  },
4541
- },
4542
- settleMs: continuationExpectsNetlifyBackgroundFunction
4543
- ? BACKGROUND_CLAIM_GRACE_MS
4544
- : undefined,
4545
- });
4609
+ }
4610
+ : {
4611
+ ...continuationBody,
4612
+ [AGENT_CHAT_BACKGROUND_RUN_FIELD]: continuationMarker,
4613
+ };
4614
+ let dispatched = false;
4615
+ let lastDispatchErr;
4616
+ for (let attempt = 0; attempt < 3 && !dispatched; attempt++) {
4617
+ try {
4618
+ if (attempt > 0) {
4619
+ await new Promise((resolve) => setTimeout(resolve, 500 * 2 ** attempt));
4620
+ // Keep the pre-inserted successor row visibly alive
4621
+ // while we retry: the awaited attempts + backoff can
4622
+ // outlast UNCLAIMED_BACKGROUND_RUN_GRACE_MS (25s), and
4623
+ // without a fresh heartbeat the unclaimed-run reaper /
4624
+ // sweep could error a handoff we are still delivering.
4625
+ if (nextRowInserted) {
4626
+ await updateRunHeartbeat(nextRunId).catch(() => { });
4627
+ }
4628
+ }
4629
+ await fireInternalDispatch({
4630
+ event,
4631
+ // Continuation chunks use the same path resolution as
4632
+ // the initial dispatch: on hosted Netlify the background
4633
+ // function's DEFAULT url (no custom config.path; async
4634
+ // via background:true; never shadowed because
4635
+ // /.netlify/* is excluded from the /* catch-all) so each
4636
+ // chunk keeps the 15-min budget; off-Netlify the
4637
+ // in-process framework route.
4638
+ path: continuationDispatchPath,
4639
+ taskId: nextRunId,
4640
+ body: dispatchBody,
4641
+ awaitResponse: true,
4642
+ responseTimeoutMs: 15_000,
4643
+ });
4644
+ dispatched = true;
4645
+ }
4646
+ catch (dispatchErr) {
4647
+ lastDispatchErr = dispatchErr;
4648
+ console.error(`[agent-chat] background continuation dispatch attempt ${attempt + 1} failed:`, dispatchErr instanceof Error
4649
+ ? dispatchErr.message
4650
+ : dispatchErr);
4651
+ }
4652
+ }
4653
+ if (!dispatched) {
4654
+ // The pre-inserted successor row would otherwise sit
4655
+ // unclaimed until the sweep reaps it — error it now so the
4656
+ // failure is immediate and truthful.
4657
+ if (nextRowInserted) {
4658
+ const nextStatusUpdated = await updateRunStatusIfRunning(nextRunId, "errored").catch(() => false);
4659
+ if (nextStatusUpdated) {
4660
+ await setRunTerminalReason(nextRunId, "background_continuation_dispatch_failed").catch(() => { });
4661
+ }
4662
+ }
4663
+ throw lastDispatchErr instanceof Error
4664
+ ? lastDispatchErr
4665
+ : new Error(String(lastDispatchErr));
4666
+ }
4546
4667
  await recordRunDiagnostic(run.runId, RUN_DIAG_STAGE.workerSetupStep, `chain_dispatch_sent nextRunId=${nextRunId} reason=${continuationReason}`).catch(() => { });
4547
4668
  }
4548
4669
  catch (chainErr) {