@agent-native/core 0.84.67 → 0.85.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.
Files changed (149) 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/action-continuation-guidance.ts +1 -1
  16. package/corpus/core/src/agent/production-agent.ts +222 -38
  17. package/corpus/core/src/agent/run-manager.ts +79 -0
  18. package/corpus/core/src/agent/run-store.ts +122 -8
  19. package/corpus/core/src/agent/types.ts +13 -0
  20. package/corpus/core/src/client/RunStuckBanner.tsx +14 -0
  21. package/corpus/core/src/client/agent-chat-adapter.ts +475 -0
  22. package/corpus/core/src/client/analytics.ts +2 -0
  23. package/corpus/core/src/client/error-format.ts +4 -0
  24. package/corpus/core/src/client/extensions/ExtensionsSidebarSection.tsx +1 -1
  25. package/corpus/core/src/client/session-replay.ts +774 -2
  26. package/corpus/core/src/client/sse-event-processor.ts +65 -9
  27. package/corpus/core/src/client/use-run-stuck-detection.ts +31 -7
  28. package/corpus/core/src/db/runtime-diagnostics.ts +331 -0
  29. package/corpus/core/src/server/agent-chat-plugin.ts +115 -2
  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/templates/analytics/.agents/skills/session-replay/SKILL.md +53 -1
  34. package/corpus/templates/analytics/AGENTS.md +13 -0
  35. package/corpus/templates/analytics/actions/view-screen.ts +25 -0
  36. package/corpus/templates/analytics/app/components/layout/Sidebar.tsx +20 -0
  37. package/corpus/templates/analytics/app/global.css +55 -0
  38. package/corpus/templates/analytics/app/i18n/zh-TW.ts +100 -1
  39. package/corpus/templates/analytics/app/i18n-data.ts +1226 -0
  40. package/corpus/templates/analytics/app/pages/Settings.tsx +3 -0
  41. package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/EmailReportDialog.tsx +182 -144
  42. package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/index.tsx +11 -18
  43. package/corpus/templates/analytics/app/pages/analyses/AnalysisDetail.tsx +11 -18
  44. package/corpus/templates/analytics/app/pages/sessions/SessionDetailPage.tsx +49 -1
  45. package/corpus/templates/analytics/app/pages/sessions/SessionDevToolsPanel.tsx +557 -0
  46. package/corpus/templates/analytics/app/pages/sessions/session-replay-devtools.ts +274 -0
  47. package/corpus/templates/analytics/app/pages/settings/AlertRulesSettingsCard.tsx +974 -0
  48. package/corpus/templates/analytics/changelog/2026-07-02-alert-rules-can-now-be-viewed-and-managed-from-settings.md +6 -0
  49. package/corpus/templates/analytics/changelog/2026-07-02-recent-ask-chats-show-a-loading-placeholder-while-your-chat-.md +6 -0
  50. package/corpus/templates/analytics/changelog/2026-07-02-session-replays-now-capture-console-logs-and-network-request.md +6 -0
  51. package/corpus/templates/analytics/changelog/2026-07-02-sharing-status-labels-use-neutral-icons-instead-of-colored-d.md +6 -0
  52. package/corpus/templates/analytics/changelog/2026-07-02-the-session-replay-viewer-has-a-dev-tools-panel-with-console.md +6 -0
  53. package/corpus/templates/analytics/server/db/schema.ts +3 -0
  54. package/corpus/templates/analytics/server/jobs/analytics-alerts.ts +4 -0
  55. package/corpus/templates/analytics/server/lib/analytics-alerts.ts +142 -1
  56. package/corpus/templates/analytics/server/lib/session-replay-agent-context.ts +369 -8
  57. package/corpus/templates/analytics/server/lib/session-replay.ts +76 -2
  58. package/corpus/templates/analytics/server/routes/api/session-replay/agent-diagnostics.json.get.ts +101 -0
  59. package/corpus/templates/analytics/shared/session-replay-diagnostics.ts +78 -0
  60. package/corpus/templates/design/app/pages/DesignEditor.tsx +24 -12
  61. package/corpus/templates/design/changelog/2026-07-02-the-temporary-code-tab-is-hidden-from-the-design-editor-side.md +6 -0
  62. package/corpus/templates/plan/actions/create-visual-recap.ts +1 -1
  63. package/corpus/templates/plan/actions/update-local-plan-folder.ts +3 -1
  64. package/corpus/templates/plan/actions/update-visual-plan.ts +1 -1
  65. package/corpus/templates/plan/changelog/2026-07-02-trying-to-edit-a-recap-or-plan-you-can-only-view-now-explain.md +6 -0
  66. package/corpus/templates/plan/server/plans.ts +26 -9
  67. package/dist/agent/action-continuation-guidance.js +1 -1
  68. package/dist/agent/action-continuation-guidance.js.map +1 -1
  69. package/dist/agent/production-agent.d.ts +9 -1
  70. package/dist/agent/production-agent.d.ts.map +1 -1
  71. package/dist/agent/production-agent.js +168 -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 +105 -7
  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 +2 -2
  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/notifications/routes.d.ts +1 -1
  118. package/dist/observability/routes.d.ts +3 -3
  119. package/dist/provider-api/corpus-jobs.d.ts +2 -2
  120. package/dist/resources/handlers.d.ts +3 -3
  121. package/dist/secrets/routes.d.ts +3 -3
  122. package/dist/server/agent-chat-plugin.d.ts.map +1 -1
  123. package/dist/server/agent-chat-plugin.js +101 -2
  124. package/dist/server/agent-chat-plugin.js.map +1 -1
  125. package/dist/server/core-routes-plugin.d.ts +17 -0
  126. package/dist/server/core-routes-plugin.d.ts.map +1 -1
  127. package/dist/server/core-routes-plugin.js +65 -5
  128. package/dist/server/core-routes-plugin.js.map +1 -1
  129. package/dist/server/http-response-telemetry.d.ts +4 -0
  130. package/dist/server/http-response-telemetry.d.ts.map +1 -0
  131. package/dist/server/http-response-telemetry.js +174 -0
  132. package/dist/server/http-response-telemetry.js.map +1 -0
  133. package/dist/server/self-dispatch.d.ts +17 -0
  134. package/dist/server/self-dispatch.d.ts.map +1 -1
  135. package/dist/server/self-dispatch.js +11 -0
  136. package/dist/server/self-dispatch.js.map +1 -1
  137. package/dist/server/transcribe-voice.d.ts +1 -1
  138. package/docs/content/locales/ar-SA/tracking.mdx +55 -0
  139. package/docs/content/locales/de-DE/tracking.mdx +55 -0
  140. package/docs/content/locales/es-ES/tracking.mdx +55 -0
  141. package/docs/content/locales/fr-FR/tracking.mdx +55 -0
  142. package/docs/content/locales/hi-IN/tracking.mdx +55 -0
  143. package/docs/content/locales/ja-JP/tracking.mdx +55 -0
  144. package/docs/content/locales/ko-KR/tracking.mdx +55 -0
  145. package/docs/content/locales/pt-BR/tracking.mdx +55 -0
  146. package/docs/content/locales/zh-CN/tracking.mdx +55 -0
  147. package/docs/content/locales/zh-TW/tracking.mdx +55 -0
  148. package/docs/content/tracking.mdx +2 -0
  149. 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";
@@ -3460,6 +3460,15 @@ export function shouldChainBackgroundContinuation(opts) {
3460
3460
  endsAtContinuationBoundary(opts.run) &&
3461
3461
  opts.continuationCount < MAX_BACKGROUND_RUN_CONTINUATIONS);
3462
3462
  }
3463
+ export async function markBackgroundContinuationChunkTerminal(opts) {
3464
+ const updateStatus = opts.deps?.updateRunStatusIfRunning ?? updateRunStatusIfRunning;
3465
+ const setTerminalReason = opts.deps?.setRunTerminalReason ?? setRunTerminalReason;
3466
+ const updated = await updateStatus(opts.runId, "completed");
3467
+ if (updated) {
3468
+ await setTerminalReason(opts.runId, opts.continuationReason);
3469
+ }
3470
+ return updated;
3471
+ }
3463
3472
  export async function claimBackgroundWorkerRunEarly(opts) {
3464
3473
  const record = opts.deps?.recordRunDiagnostic ?? recordRunDiagnostic;
3465
3474
  const insert = opts.deps?.insertRun ?? insertRun;
@@ -4246,8 +4255,13 @@ export function createProductionAgentHandler(options) {
4246
4255
  // Insert the run row up front so /runs/active sees it immediately and
4247
4256
  // the slot stays held while the background function cold-starts. Mark
4248
4257
  // it background-dispatched so the stale reaper uses the wider window.
4258
+ // The full request body is persisted ON the row (dispatch_payload) so
4259
+ // the self-POST below can carry only the tiny marker — Netlify caps
4260
+ // background-function request bodies at 256KB, and a large chat
4261
+ // history (inline attachments especially) silently exceeded that.
4249
4262
  await insertRun(runId, effectiveThreadId, effectiveTurnId, {
4250
4263
  dispatchMode: "background",
4264
+ dispatchPayload: JSON.stringify(body),
4251
4265
  });
4252
4266
  backgroundRowInserted = true;
4253
4267
  }
@@ -4275,15 +4289,29 @@ export function createProductionAgentHandler(options) {
4275
4289
  // the Authorization Bearer HMAC is preserved either way.
4276
4290
  path: backgroundDispatchPath,
4277
4291
  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,
4292
+ // When the row (and its persisted payload) landed, send only the
4293
+ // marker — the worker rehydrates the body from dispatch_payload
4294
+ // (`payloadRef`), keeping the self-POST far under Netlify's 256KB
4295
+ // background-function body cap. If the insert failed we fall back to
4296
+ // carrying the full body inline, exactly as before.
4297
+ body: backgroundRowInserted
4298
+ ? {
4299
+ [AGENT_CHAT_BACKGROUND_RUN_FIELD]: {
4300
+ runId,
4301
+ turnId: effectiveTurnId,
4302
+ backgroundFunctionRuntimeExpected: expectsNetlifyBackgroundFunction,
4303
+ payloadRef: true,
4304
+ },
4305
+ }
4306
+ : {
4307
+ ...body,
4308
+ // Carry the pre-claimed identity so the worker reuses this run.
4309
+ [AGENT_CHAT_BACKGROUND_RUN_FIELD]: {
4310
+ runId,
4311
+ turnId: effectiveTurnId,
4312
+ backgroundFunctionRuntimeExpected: expectsNetlifyBackgroundFunction,
4313
+ },
4285
4314
  },
4286
- },
4287
4315
  });
4288
4316
  dispatched = true;
4289
4317
  }
@@ -4503,6 +4531,23 @@ export function createProductionAgentHandler(options) {
4503
4531
  // foreground fallback, which is not a worker and rides the
4504
4532
  // connected client's auto_continue instead.)
4505
4533
  if (willChainBackgroundContinuation(run)) {
4534
+ // DURABLE PER-TURN LEDGER: bound the total number of runs one
4535
+ // logical turn may consume, counted in SQL. The in-marker
4536
+ // `continuationCount` resets whenever a fresh POST starts a new
4537
+ // chain for the same turn (client recovery, duplicate delivery),
4538
+ // so it cannot bound cross-chain loops — the SQL count survives
4539
+ // every recovery path and is what actually kills a pathological
4540
+ // turn (the "dozens of runs on one prompt" incident class).
4541
+ const turnRunCount = await countRunsForTurn(effectiveThreadId, effectiveTurnId).catch(() => null);
4542
+ if (turnRunCount !== null &&
4543
+ turnRunCount > MAX_BACKGROUND_RUN_CONTINUATIONS + 5) {
4544
+ console.error(`[agent-chat] turn ${effectiveTurnId} consumed ${turnRunCount} runs — refusing to chain further`, runId);
4545
+ const statusUpdated = await updateRunStatusIfRunning(runId, "errored").catch(() => false);
4546
+ if (statusUpdated) {
4547
+ await setRunTerminalReason(runId, "turn_continuation_budget_exhausted").catch(() => { });
4548
+ }
4549
+ return;
4550
+ }
4506
4551
  // Mint the next chunk's runId here and sign the dispatch token
4507
4552
  // over it, so the `_process-run` route's HMAC check and the
4508
4553
  // worker's run identity agree. Fresh runId (not this chunk's) so
@@ -4513,37 +4558,126 @@ export function createProductionAgentHandler(options) {
4513
4558
  const continuationReason = backgroundContinuationReasonForRun(run);
4514
4559
  const continuationDispatchPath = resolveAgentChatProcessRunDispatchPath();
4515
4560
  const continuationExpectsNetlifyBackgroundFunction = dispatchPathTargetsNetlifyBackgroundFunction(continuationDispatchPath);
4561
+ const continuationMarker = {
4562
+ runId: nextRunId,
4563
+ turnId: effectiveTurnId,
4564
+ continuationCount: backgroundContinuationCount + 1,
4565
+ continuationReason,
4566
+ ...(actionPreparationTool ? { actionPreparationTool } : {}),
4567
+ backgroundFunctionRuntimeExpected: continuationExpectsNetlifyBackgroundFunction,
4568
+ };
4569
+ // Strip this chunk's own marker before persisting/forwarding —
4570
+ // the next chunk gets the fresh marker above.
4571
+ const continuationBody = {
4572
+ ...body,
4573
+ internalContinuation: true,
4574
+ };
4575
+ delete continuationBody[AGENT_CHAT_BACKGROUND_RUN_FIELD];
4516
4576
  try {
4517
4577
  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,
4578
+ // ── TRANSACTIONAL HANDOFF ──────────────────────────────────
4579
+ // 1. Insert the successor row (with its rehydration payload)
4580
+ // BEFORE firing the dispatch, so:
4581
+ // - /runs/active shows an active run continuously across
4582
+ // the chunk boundary (no idle gap for the client to
4583
+ // misread as "the turn ended"), and
4584
+ // - a lost dispatch leaves a row the unclaimed-run sweep
4585
+ // reaps into a LOUD error instead of a silent hang.
4586
+ // 2. Await the dispatch response fully (`awaitResponse`) —
4587
+ // this worker's Lambda is about to finish, and the old
4588
+ // 250ms settle race let a still-in-flight handoff fetch be
4589
+ // killed by the post-return freeze WITHOUT rejecting: the
4590
+ // turn just stopped, silently. A Netlify background
4591
+ // function 202s on enqueue (normally well under a second),
4592
+ // and this chunk has minutes of budget headroom left, so
4593
+ // awaiting is cheap. Retried with backoff for transient
4594
+ // network blips.
4595
+ let nextRowInserted = false;
4596
+ try {
4597
+ await insertRun(nextRunId, effectiveThreadId, effectiveTurnId, {
4598
+ dispatchMode: "background",
4599
+ dispatchPayload: JSON.stringify(continuationBody),
4600
+ });
4601
+ nextRowInserted = true;
4602
+ }
4603
+ catch (insertErr) {
4604
+ await recordRunDiagnostic(run.runId, RUN_DIAG_STAGE.workerSetupStep, `chain_successor_insert_failed nextRunId=${nextRunId} ${insertErr instanceof Error
4605
+ ? insertErr.message
4606
+ : String(insertErr)}`).catch(() => { });
4607
+ console.error("[agent-chat] continuation insertRun failed; dispatching with inline body:", insertErr instanceof Error
4608
+ ? insertErr.message
4609
+ : insertErr);
4610
+ }
4611
+ const dispatchBody = nextRowInserted
4612
+ ? {
4530
4613
  internalContinuation: true,
4531
4614
  [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,
4615
+ ...continuationMarker,
4616
+ payloadRef: true,
4540
4617
  },
4541
- },
4542
- settleMs: continuationExpectsNetlifyBackgroundFunction
4543
- ? BACKGROUND_CLAIM_GRACE_MS
4544
- : undefined,
4545
- });
4618
+ }
4619
+ : {
4620
+ ...continuationBody,
4621
+ [AGENT_CHAT_BACKGROUND_RUN_FIELD]: continuationMarker,
4622
+ };
4623
+ let dispatched = false;
4624
+ let lastDispatchErr;
4625
+ for (let attempt = 0; attempt < 3 && !dispatched; attempt++) {
4626
+ try {
4627
+ if (attempt > 0) {
4628
+ await new Promise((resolve) => setTimeout(resolve, 500 * 2 ** attempt));
4629
+ // Keep the pre-inserted successor row visibly alive
4630
+ // while we retry: the awaited attempts + backoff can
4631
+ // outlast UNCLAIMED_BACKGROUND_RUN_GRACE_MS (25s), and
4632
+ // without a fresh heartbeat the unclaimed-run reaper /
4633
+ // sweep could error a handoff we are still delivering.
4634
+ if (nextRowInserted) {
4635
+ await updateRunHeartbeat(nextRunId).catch(() => { });
4636
+ }
4637
+ }
4638
+ await fireInternalDispatch({
4639
+ event,
4640
+ // Continuation chunks use the same path resolution as
4641
+ // the initial dispatch: on hosted Netlify the background
4642
+ // function's DEFAULT url (no custom config.path; async
4643
+ // via background:true; never shadowed because
4644
+ // /.netlify/* is excluded from the /* catch-all) so each
4645
+ // chunk keeps the 15-min budget; off-Netlify the
4646
+ // in-process framework route.
4647
+ path: continuationDispatchPath,
4648
+ taskId: nextRunId,
4649
+ body: dispatchBody,
4650
+ awaitResponse: true,
4651
+ responseTimeoutMs: 15_000,
4652
+ });
4653
+ dispatched = true;
4654
+ }
4655
+ catch (dispatchErr) {
4656
+ lastDispatchErr = dispatchErr;
4657
+ console.error(`[agent-chat] background continuation dispatch attempt ${attempt + 1} failed:`, dispatchErr instanceof Error
4658
+ ? dispatchErr.message
4659
+ : dispatchErr);
4660
+ }
4661
+ }
4662
+ if (!dispatched) {
4663
+ // The pre-inserted successor row would otherwise sit
4664
+ // unclaimed until the sweep reaps it — error it now so the
4665
+ // failure is immediate and truthful.
4666
+ if (nextRowInserted) {
4667
+ const nextStatusUpdated = await updateRunStatusIfRunning(nextRunId, "errored").catch(() => false);
4668
+ if (nextStatusUpdated) {
4669
+ await setRunTerminalReason(nextRunId, "background_continuation_dispatch_failed").catch(() => { });
4670
+ }
4671
+ }
4672
+ throw lastDispatchErr instanceof Error
4673
+ ? lastDispatchErr
4674
+ : new Error(String(lastDispatchErr));
4675
+ }
4546
4676
  await recordRunDiagnostic(run.runId, RUN_DIAG_STAGE.workerSetupStep, `chain_dispatch_sent nextRunId=${nextRunId} reason=${continuationReason}`).catch(() => { });
4677
+ await markBackgroundContinuationChunkTerminal({
4678
+ runId: run.runId,
4679
+ continuationReason,
4680
+ }).catch(() => { });
4547
4681
  }
4548
4682
  catch (chainErr) {
4549
4683
  // Chain dispatch failed — fail loud so the held row goes