@agent-native/core 0.122.2 → 0.122.3

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 (71) hide show
  1. package/corpus/README.md +2 -2
  2. package/corpus/core/CHANGELOG.md +6 -0
  3. package/corpus/core/package.json +1 -1
  4. package/corpus/core/src/agent/run-loop-with-resume.ts +12 -0
  5. package/corpus/core/src/integrations/a2a-continuation-processor.ts +182 -0
  6. package/corpus/core/src/integrations/a2a-continuations-store.ts +122 -12
  7. package/corpus/core/src/integrations/integration-campaign-recovery.ts +127 -0
  8. package/corpus/core/src/integrations/integration-campaigns-store.ts +1039 -0
  9. package/corpus/core/src/integrations/integration-durable-dispatch.ts +28 -3
  10. package/corpus/core/src/integrations/pending-tasks-retry-job.ts +8 -0
  11. package/corpus/core/src/integrations/plugin.ts +453 -35
  12. package/corpus/core/src/integrations/webhook-handler.ts +469 -11
  13. package/corpus/templates/content/app/components/editor/DocumentEditor.tsx +7 -3
  14. package/corpus/templates/content/app/components/editor/body-hydration.ts +10 -0
  15. package/corpus/templates/content/app/components/editor/database/DatabaseView.tsx +1 -0
  16. package/corpus/templates/content/app/root.tsx +12 -1
  17. package/corpus/templates/content/changelog/2026-07-23-new-pages-open-immediately-while-slow-network-work-continues.md +6 -0
  18. package/corpus/templates/content/changelog/2026-07-25-database-preview-actions-open-reliably.md +6 -0
  19. package/corpus/templates/content/e2e/playwright.config.ts +1 -1
  20. package/dist/agent/run-loop-with-resume.d.ts +7 -1
  21. package/dist/agent/run-loop-with-resume.d.ts.map +1 -1
  22. package/dist/agent/run-loop-with-resume.js +7 -0
  23. package/dist/agent/run-loop-with-resume.js.map +1 -1
  24. package/dist/collab/awareness.d.ts +2 -2
  25. package/dist/collab/awareness.d.ts.map +1 -1
  26. package/dist/file-upload/actions/upload-image.d.ts +2 -2
  27. package/dist/integrations/a2a-continuation-processor.d.ts +14 -0
  28. package/dist/integrations/a2a-continuation-processor.d.ts.map +1 -1
  29. package/dist/integrations/a2a-continuation-processor.js +134 -1
  30. package/dist/integrations/a2a-continuation-processor.js.map +1 -1
  31. package/dist/integrations/a2a-continuations-store.d.ts +23 -0
  32. package/dist/integrations/a2a-continuations-store.d.ts.map +1 -1
  33. package/dist/integrations/a2a-continuations-store.js +99 -13
  34. package/dist/integrations/a2a-continuations-store.js.map +1 -1
  35. package/dist/integrations/integration-campaign-recovery.d.ts +16 -0
  36. package/dist/integrations/integration-campaign-recovery.d.ts.map +1 -0
  37. package/dist/integrations/integration-campaign-recovery.js +94 -0
  38. package/dist/integrations/integration-campaign-recovery.js.map +1 -0
  39. package/dist/integrations/integration-campaigns-store.d.ts +121 -0
  40. package/dist/integrations/integration-campaigns-store.d.ts.map +1 -0
  41. package/dist/integrations/integration-campaigns-store.js +784 -0
  42. package/dist/integrations/integration-campaigns-store.js.map +1 -0
  43. package/dist/integrations/integration-durable-dispatch.d.ts +3 -0
  44. package/dist/integrations/integration-durable-dispatch.d.ts.map +1 -1
  45. package/dist/integrations/integration-durable-dispatch.js +22 -3
  46. package/dist/integrations/integration-durable-dispatch.js.map +1 -1
  47. package/dist/integrations/pending-tasks-retry-job.d.ts.map +1 -1
  48. package/dist/integrations/pending-tasks-retry-job.js +6 -0
  49. package/dist/integrations/pending-tasks-retry-job.js.map +1 -1
  50. package/dist/integrations/plugin.d.ts.map +1 -1
  51. package/dist/integrations/plugin.js +325 -18
  52. package/dist/integrations/plugin.js.map +1 -1
  53. package/dist/integrations/webhook-handler.d.ts +16 -1
  54. package/dist/integrations/webhook-handler.d.ts.map +1 -1
  55. package/dist/integrations/webhook-handler.js +368 -13
  56. package/dist/integrations/webhook-handler.js.map +1 -1
  57. package/dist/notifications/routes.d.ts +3 -3
  58. package/dist/provider-api/actions/custom-provider-registration.d.ts +14 -14
  59. package/dist/provider-api/actions/provider-api.d.ts +8 -8
  60. package/dist/resources/handlers.d.ts +1 -1
  61. package/dist/server/agent-engine-api-key-route.d.ts +1 -1
  62. package/package.json +1 -1
  63. package/src/agent/run-loop-with-resume.ts +12 -0
  64. package/src/integrations/a2a-continuation-processor.ts +182 -0
  65. package/src/integrations/a2a-continuations-store.ts +122 -12
  66. package/src/integrations/integration-campaign-recovery.ts +127 -0
  67. package/src/integrations/integration-campaigns-store.ts +1039 -0
  68. package/src/integrations/integration-durable-dispatch.ts +28 -3
  69. package/src/integrations/pending-tasks-retry-job.ts +8 -0
  70. package/src/integrations/plugin.ts +453 -35
  71. package/src/integrations/webhook-handler.ts +469 -11
@@ -69,6 +69,16 @@ export function documentBodyHydrationIsPending(
69
69
  return builderBodyHydrationIsPending(hydration);
70
70
  }
71
71
 
72
+ export function newDocumentPageChoiceIsDisabled(args: {
73
+ canEdit: boolean;
74
+ bodyHydrationPending: boolean;
75
+ databaseCreationPending: boolean;
76
+ }) {
77
+ return (
78
+ !args.canEdit || args.bodyHydrationPending || args.databaseCreationPending
79
+ );
80
+ }
81
+
72
82
  export function previewBodyHydrationIsPending(args: {
73
83
  item: Pick<ContentDatabaseItem, "bodyHydration" | "document">;
74
84
  document: Pick<Document, "content" | "databaseMembership"> | null | undefined;
@@ -4781,6 +4781,7 @@ function DatabaseItemPreview({
4781
4781
  </Button>
4782
4782
  {canEdit || canManage || removesFavoriteMembership ? (
4783
4783
  <DropdownMenu
4784
+ modal={false}
4784
4785
  open={actionsMenuOpen}
4785
4786
  onOpenChange={setActionsMenuOpen}
4786
4787
  >
@@ -49,7 +49,11 @@ import {
49
49
  useRouteLoaderData,
50
50
  useRouteError,
51
51
  } from "react-router";
52
- import type { LinksFunction, LoaderFunctionArgs } from "react-router";
52
+ import type {
53
+ LinksFunction,
54
+ LoaderFunctionArgs,
55
+ ShouldRevalidateFunctionArgs,
56
+ } from "react-router";
53
57
 
54
58
  // Styled sonner wrapper — passed via AppProviders `toaster` prop to avoid duplicate.
55
59
  import { Toaster as Sonner } from "@/components/ui/sonner";
@@ -105,6 +109,13 @@ export async function loader({
105
109
  };
106
110
  }
107
111
 
112
+ export function shouldRevalidate({
113
+ defaultShouldRevalidate,
114
+ formMethod,
115
+ }: ShouldRevalidateFunctionArgs) {
116
+ return formMethod ? defaultShouldRevalidate : false;
117
+ }
118
+
108
119
  // Pass args to match content's 3-way theme-cycle UX (no disableTransitionOnChange).
109
120
  const THEME_INIT_SCRIPT = getThemeInitScript("system", true);
110
121
 
@@ -0,0 +1,6 @@
1
+ ---
2
+ type: fixed
3
+ date: 2026-07-23
4
+ ---
5
+
6
+ New pages open immediately while slow network work continues
@@ -0,0 +1,6 @@
1
+ ---
2
+ type: fixed
3
+ date: 2026-07-25
4
+ ---
5
+
6
+ Database page preview action menus open reliably with pointer and keyboard.
@@ -14,7 +14,7 @@ import { defineConfig, devices } from "@playwright/test";
14
14
  */
15
15
  export default defineConfig({
16
16
  testDir: ".",
17
- testMatch: /(registry-blocks|local-files)\.spec\.ts/,
17
+ testMatch: /(registry-blocks|local-files|database-preview-menu)\.spec\.ts/,
18
18
  fullyParallel: true,
19
19
  workers: process.env.CI ? 2 : 3,
20
20
  retries: 2,
@@ -20,8 +20,14 @@
20
20
  * Both paths route through `appendAgentLoopContinuation` so the agent sees a
21
21
  * uniform "continue" instruction regardless of which recovery fired.
22
22
  */
23
- import { runAgentLoop } from "./production-agent.js";
23
+ import type { EngineMessage } from "./engine/types.js";
24
+ import { runAgentLoop, type AgentLoopContinuationReason } from "./production-agent.js";
24
25
  import type { ResolveRunSoftTimeoutOptions } from "./run-manager.js";
26
+ /**
27
+ * Rebuild the same safe continuation context for a logical turn that resumes
28
+ * in a fresh hosted invocation.
29
+ */
30
+ export declare function appendDurableContinuationContext(messages: EngineMessage[], reason: AgentLoopContinuationReason, threadId: string): Promise<void>;
25
31
  /**
26
32
  * Cap on continuation iterations inside a single
27
33
  * `runAgentLoopDirectWithSoftTimeout` invocation. The host's hard function
@@ -1 +1 @@
1
- {"version":3,"file":"run-loop-with-resume.d.ts","sourceRoot":"","sources":["../../src/agent/run-loop-with-resume.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAGH,OAAO,EACL,YAAY,EAOb,MAAM,uBAAuB,CAAC;AAE/B,OAAO,KAAK,EAAE,4BAA4B,EAAE,MAAM,kBAAkB,CAAC;AAgIrE;;;;;;;;;GASG;AACH,eAAO,MAAM,0BAA0B,IAAI,CAAC;AAE5C;;;;wDAIwD;AACxD,eAAO,MAAM,+BAA+B,yBAAyB,CAAC;AAEtE;;;;;iCAKiC;AACjC,eAAO,MAAM,4BAA4B,QAGkD,CAAC;AAE5F;;;;;;;;GAQG;AACH,wBAAsB,iCAAiC,CACrD,IAAI,EAAE,UAAU,CAAC,OAAO,YAAY,CAAC,CAAC,CAAC,CAAC,EACxC,aAAa,CAAC,EAAE,MAAM,EACtB,cAAc,CAAC,EAAE,4BAA4B,GAC5C,OAAO,CAAC,OAAO,CAAC,UAAU,CAAC,OAAO,YAAY,CAAC,CAAC,CAAC,CA4LnD"}
1
+ {"version":3,"file":"run-loop-with-resume.d.ts","sourceRoot":"","sources":["../../src/agent/run-loop-with-resume.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AACvD,OAAO,EACL,YAAY,EAMZ,KAAK,2BAA2B,EACjC,MAAM,uBAAuB,CAAC;AAE/B,OAAO,KAAK,EAAE,4BAA4B,EAAE,MAAM,kBAAkB,CAAC;AA4FrE;;;GAGG;AACH,wBAAsB,gCAAgC,CACpD,QAAQ,EAAE,aAAa,EAAE,EACzB,MAAM,EAAE,2BAA2B,EACnC,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,IAAI,CAAC,CAEf;AAsCD;;;;;;;;;GASG;AACH,eAAO,MAAM,0BAA0B,IAAI,CAAC;AAE5C;;;;wDAIwD;AACxD,eAAO,MAAM,+BAA+B,yBAAyB,CAAC;AAEtE;;;;;iCAKiC;AACjC,eAAO,MAAM,4BAA4B,QAGkD,CAAC;AAE5F;;;;;;;;GAQG;AACH,wBAAsB,iCAAiC,CACrD,IAAI,EAAE,UAAU,CAAC,OAAO,YAAY,CAAC,CAAC,CAAC,CAAC,EACxC,aAAa,CAAC,EAAE,MAAM,EACtB,cAAc,CAAC,EAAE,4BAA4B,GAC5C,OAAO,CAAC,OAAO,CAAC,UAAU,CAAC,OAAO,YAAY,CAAC,CAAC,CAAC,CA4LnD"}
@@ -91,6 +91,13 @@ async function appendContinuationAndJournal(messages, reason, threadId, localEve
91
91
  appendAgentLoopContinuation(messages, reason, actionPreparationContinuationOptions(events));
92
92
  appendToolCallJournalNote(messages, events);
93
93
  }
94
+ /**
95
+ * Rebuild the same safe continuation context for a logical turn that resumes
96
+ * in a fresh hosted invocation.
97
+ */
98
+ export async function appendDurableContinuationContext(messages, reason, threadId) {
99
+ await appendContinuationAndJournal(messages, reason, threadId);
100
+ }
94
101
  async function hasCompletedSideEffectToolCallInCurrentTurn(threadId, localEvents = []) {
95
102
  try {
96
103
  const events = await readCurrentTurnEventsForResume(threadId, localEvents);
@@ -1 +1 @@
1
- {"version":3,"file":"run-loop-with-resume.js","sourceRoot":"","sources":["../../src/agent/run-loop-with-resume.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Wraps `runAgentLoop` with two layered recovery mechanisms so a single hosted\n * invocation can survive interruptions without showing the user a dead chat:\n *\n * 1. **Soft timeout** — an inner timer that aborts the LLM call before the\n * hosting function's hard limit (Lambda 75s, Vercel 60s, etc.) so we have a\n * chance to gracefully wind down and append a continuation nudge. Without\n * this the function gets killed mid-stream and the user sees a frozen\n * spinner.\n *\n * 2. **Resumable-error continuation** — when the LLM call errors with a\n * transport- or gateway-level interruption (Builder gateway 45s timeout,\n * socket hang up, ECONNRESET, upstream 5xx that survived engine retries),\n * we save the conversation prefix, append a \"continue from where you left\n * off\" message, and run another LLM call. Anthropic's prompt cache makes\n * the resume call dramatically faster than the cold first attempt, and the\n * agent gets explicit context that it was cut off so it doesn't re-do\n * completed work.\n *\n * Both paths route through `appendAgentLoopContinuation` so the agent sees a\n * uniform \"continue\" instruction regardless of which recovery fired.\n */\n\nimport type { EngineMessage } from \"./engine/types.js\";\nimport {\n runAgentLoop,\n appendAgentLoopContinuation,\n isResumableEngineError,\n continuationReasonForResumableError,\n lastUnfinishedPreparingActionToolFromEvents,\n resolveFinalResponseGuardRequestText,\n type AgentLoopContinuationReason,\n} from \"./production-agent.js\";\nimport { resolveRunSoftTimeoutMs } from \"./run-manager.js\";\nimport type { ResolveRunSoftTimeoutOptions } from \"./run-manager.js\";\nimport { getCurrentTurnEventsForThread } from \"./run-store.js\";\nimport {\n classifyToolCallJournal,\n buildResumeJournalNote,\n} from \"./tool-call-journal.js\";\nimport type { AgentChatEvent } from \"./types.js\";\n\nasync function readCurrentTurnEventsForResume(\n threadId: string | undefined,\n localEvents: readonly AgentChatEvent[] = [],\n): Promise<AgentChatEvent[]> {\n let persistedEvents: AgentChatEvent[] = [];\n try {\n persistedEvents = threadId\n ? await getCurrentTurnEventsForThread(threadId)\n : [];\n } catch {\n persistedEvents = [];\n }\n if (localEvents.length === 0) return persistedEvents;\n const seen = new Set(persistedEvents.map((event) => JSON.stringify(event)));\n return [\n ...persistedEvents,\n ...localEvents.filter((event) => !seen.has(JSON.stringify(event))),\n ];\n}\n\nfunction actionPreparationContinuationOptions(\n events: readonly AgentChatEvent[],\n): { actionPreparationTool?: string } {\n const actionPreparationTool =\n lastUnfinishedPreparingActionToolFromEvents(events);\n return actionPreparationTool ? { actionPreparationTool } : {};\n}\n\n/**\n * Derive the per-turn tool-call journal from the durable run-event ledger and,\n * when there is anything to report, append a STRUCTURED note to the message\n * prefix so the resumed model:\n * - does NOT re-execute tool calls that already completed (avoiding duplicate\n * side effects like re-sending an email or re-creating a ticket), and\n * - is explicitly told about any tool call that started but whose outcome was\n * never recorded (\"interrupted, unknown outcome\") so it can decide.\n *\n * This is additive to the existing \"continue from where you left off\" nudge —\n * it is appended right after it. When the journal is empty (no completed or\n * interrupted tool calls — e.g. a turn with no tool activity, or a clean\n * continuation), nothing extra is appended and resume behavior is byte-for-byte\n * what it was before. Best-effort: any ledger read/parse failure is swallowed so\n * a journal hiccup can never block a recovery that would otherwise succeed.\n *\n * This prompt-level journal is paired with tool-layer enforcement in\n * production-agent.ts/runToolCall, which refuses to re-execute a journaled-\n * complete write tool (returning the journaled result instead). See\n * `tool-call-journal.ts` (`findCompletedJournalEntry`) for the keying used.\n */\nfunction appendToolCallJournalNote(\n messages: EngineMessage[],\n events: readonly AgentChatEvent[],\n): void {\n try {\n if (events.length === 0) return;\n const journal = classifyToolCallJournal(events);\n const note = buildResumeJournalNote(journal);\n if (!note) return;\n messages.push({\n role: \"user\",\n content: [{ type: \"text\", text: note }],\n });\n } catch {\n // The journal is a hardening layer, never a gate. A failed ledger read or\n // parse must not break the resume that the continuation nudge already set\n // up — the model still continues, just without the structured journal.\n }\n}\n\nasync function appendContinuationAndJournal(\n messages: EngineMessage[],\n reason: AgentLoopContinuationReason,\n threadId: string | undefined,\n localEvents: readonly AgentChatEvent[] = [],\n): Promise<void> {\n const events = await readCurrentTurnEventsForResume(threadId, localEvents);\n appendAgentLoopContinuation(\n messages,\n reason,\n actionPreparationContinuationOptions(events),\n );\n appendToolCallJournalNote(messages, events);\n}\n\nasync function hasCompletedSideEffectToolCallInCurrentTurn(\n threadId: string | undefined,\n localEvents: readonly AgentChatEvent[] = [],\n): Promise<boolean> {\n try {\n const events = await readCurrentTurnEventsForResume(threadId, localEvents);\n if (events.length === 0) return false;\n return events.some(\n (event) =>\n event.type === \"tool_done\" &&\n event.completedSideEffect === true &&\n event.isError !== true,\n );\n } catch {\n return false;\n }\n}\n\nfunction internalContinuationReasonForAttempt(\n events: readonly AgentChatEvent[],\n): AgentLoopContinuationReason | undefined {\n const last = events.at(-1);\n if (last?.type !== \"auto_continue\") return undefined;\n if (\n last.reason === \"run_timeout\" ||\n last.reason === \"loop_limit\" ||\n last.reason === \"no_progress\" ||\n last.reason === \"stream_ended\" ||\n last.reason === \"gateway_timeout\" ||\n last.reason === \"network_interrupted\"\n ) {\n return last.reason;\n }\n return undefined;\n}\n\n/**\n * Cap on continuation iterations inside a single\n * `runAgentLoopDirectWithSoftTimeout` invocation. The host's hard function\n * timeout usually bounds this naturally — but a defensive cap prevents an\n * instant-error spiral from looping forever inside hosting environments with a\n * generous budget.\n *\n * 6 leaves room for: 1 normal completion + a few resume rounds for design\n * generation (prompt + 3 variants ≈ 4 LLM calls), with a small safety margin.\n */\nexport const MAX_RUN_LOOP_CONTINUATIONS = 6;\n\n/** Machine-readable code carried on the give-up terminal `error` event so the\n * client renders a loud \"stopped before finishing\" terminal instead of an\n * ambiguous silent stall. Deliberately NOT in the client's auto-recoverable\n * allow-list (`isAutoRecoverableError`) so it terminates the chain rather than\n * looping another POST that would hit the same wall. */\nexport const RUN_BUDGET_EXHAUSTED_ERROR_CODE = \"run_budget_exhausted\";\n\n/** User-facing terminal message when a hosted run is cut off mid-step and\n * exhausts its in-invocation continuation budget without finishing. Generic and\n * framework-level (not app-specific). Mirrors the `reliable-mutations` skill's\n * \"fail loud, retry as a single bulk action\" guidance so the user understands\n * the turn stopped before finishing without implying that earlier completed\n * tool calls did not persist. */\nexport const RUN_BUDGET_EXHAUSTED_MESSAGE =\n \"I ran out of time before finishing this step. \" +\n \"I stopped rather than keep retrying silently. \" +\n \"Check any completed tool cards above before retrying, ideally as one smaller follow-up.\";\n\n/**\n * Internal entry point used by the agent-chat plugin's run handler. Wraps\n * `runAgentLoop` with soft-timeout + resumable-error continuation recovery.\n *\n * The `softTimeoutMs` argument falls back to `resolveRunSoftTimeoutMs(...)` so\n * different hosting environments (Lambda, Vercel, Cloudflare, local dev) get\n * an appropriate inner budget. Setting it to <= 0 disables both layers — the\n * call goes straight to `runAgentLoop` with no wrapping.\n */\nexport async function runAgentLoopDirectWithSoftTimeout(\n opts: Parameters<typeof runAgentLoop>[0],\n softTimeoutMs?: number,\n timeoutOptions?: ResolveRunSoftTimeoutOptions,\n): Promise<Awaited<ReturnType<typeof runAgentLoop>>> {\n const finalResponseGuardRequestText =\n opts.finalResponseGuardRequestText ??\n resolveFinalResponseGuardRequestText(opts.messages);\n const stableOpts = { ...opts, finalResponseGuardRequestText };\n const timeoutMs = resolveRunSoftTimeoutMs(softTimeoutMs, timeoutOptions);\n if (timeoutMs <= 0) return runAgentLoop(stableOpts);\n\n const upstreamSignal = opts.signal;\n const usage: Awaited<ReturnType<typeof runAgentLoop>> = {\n inputTokens: 0,\n outputTokens: 0,\n cacheReadTokens: 0,\n cacheWriteTokens: 0,\n model: opts.model,\n };\n\n const addUsage = (next: Awaited<ReturnType<typeof runAgentLoop>>) => {\n usage.inputTokens += next.inputTokens;\n usage.outputTokens += next.outputTokens;\n usage.cacheReadTokens += next.cacheReadTokens;\n usage.cacheWriteTokens += next.cacheWriteTokens;\n usage.model = next.model;\n };\n\n const localTurnEvents: AgentChatEvent[] = [];\n let attempts = 0;\n // Tracks whether the most recent attempt ended by scheduling another\n // continuation (soft-timeout or resumable error → `continue`) rather than\n // returning a finished turn. When the loop then exits because the budget is\n // exhausted (NOT because the user aborted and NOT because the turn finished),\n // this is the silent give-up case: emit a loud terminal so the user sees an\n // unambiguous \"stopped before finishing\" instead of a bare done/\"…\".\n let lastAttemptWasUnfinishedContinuation = false;\n while (!upstreamSignal.aborted && attempts < MAX_RUN_LOOP_CONTINUATIONS) {\n attempts++;\n lastAttemptWasUnfinishedContinuation = false;\n const controller = new AbortController();\n const abortFromUpstream = () => controller.abort();\n if (upstreamSignal.aborted) {\n controller.abort();\n } else {\n upstreamSignal.addEventListener(\"abort\", abortFromUpstream, {\n once: true,\n });\n }\n\n let softTimedOut = false;\n const timer = setTimeout(() => {\n if (controller.signal.aborted) return;\n softTimedOut = true;\n controller.abort();\n }, timeoutMs);\n\n const attemptStartIndex = localTurnEvents.length;\n const send = (event: AgentChatEvent) => {\n localTurnEvents.push(event);\n opts.send(event);\n };\n\n try {\n const nextUsage = await runAgentLoop({\n ...stableOpts,\n send,\n signal: controller.signal,\n });\n addUsage(nextUsage);\n const attemptEvents = localTurnEvents.slice(attemptStartIndex);\n const internalContinuationReason =\n internalContinuationReasonForAttempt(attemptEvents);\n if (internalContinuationReason && !upstreamSignal.aborted) {\n lastAttemptWasUnfinishedContinuation = true;\n const continuationEvents = [...localTurnEvents];\n if (\n !(await hasCompletedSideEffectToolCallInCurrentTurn(\n opts.threadId,\n continuationEvents,\n ))\n ) {\n opts.send({ type: \"clear\" });\n }\n await appendContinuationAndJournal(\n opts.messages,\n internalContinuationReason,\n opts.threadId,\n continuationEvents,\n );\n continue;\n }\n if (softTimedOut && !upstreamSignal.aborted) {\n lastAttemptWasUnfinishedContinuation = true;\n await appendContinuationAndJournal(\n opts.messages,\n \"run_timeout\",\n opts.threadId,\n localTurnEvents,\n );\n continue;\n }\n return usage;\n } catch (err) {\n if (softTimedOut && !upstreamSignal.aborted) {\n // Clear partial text the client received before the abort so the\n // resumed model doesn't re-emit it and produce duplicated output.\n lastAttemptWasUnfinishedContinuation = true;\n if (\n !(await hasCompletedSideEffectToolCallInCurrentTurn(\n opts.threadId,\n localTurnEvents,\n ))\n ) {\n opts.send({ type: \"clear\" });\n }\n await appendContinuationAndJournal(\n opts.messages,\n \"run_timeout\",\n opts.threadId,\n localTurnEvents,\n );\n continue;\n }\n // Resumable transport / gateway interruptions: the LLM call was cut off\n // mid-stream (gateway 45s timeout, socket hang up, function-level\n // timeout that didn't trip our soft timer first). Treat it the same way\n // as a soft timeout — append a \"continue from where you left off\" nudge\n // and let the loop run another LLM call. The conversation prefix up to\n // the cut-off is preserved in opts.messages, and Anthropic's prompt\n // cache makes the resume call much faster.\n //\n // Emit 'clear' so any partial streamed text is discarded on the client\n // before the model resumes. Without this the model restarts its sentence\n // from scratch and the fold produces duplicated text in one message\n // (the partial text was already sent to the client but never entered\n // the in-memory messages array, so the next attempt re-emits it).\n if (!upstreamSignal.aborted && isResumableEngineError(err)) {\n lastAttemptWasUnfinishedContinuation = true;\n if (\n !(await hasCompletedSideEffectToolCallInCurrentTurn(\n opts.threadId,\n localTurnEvents,\n ))\n ) {\n opts.send({ type: \"clear\" });\n }\n await appendContinuationAndJournal(\n opts.messages,\n continuationReasonForResumableError(err),\n opts.threadId,\n localTurnEvents,\n );\n continue;\n }\n throw err;\n } finally {\n clearTimeout(timer);\n upstreamSignal.removeEventListener(\"abort\", abortFromUpstream);\n }\n }\n\n // The loop exited without a clean return. If the user aborted, that's a Stop —\n // stay silent. Otherwise we only get here by exhausting\n // MAX_RUN_LOOP_CONTINUATIONS while the last attempt was still trying to\n // continue (soft-timeout / resumable error). That is the genuinely-silent\n // give-up the run-manager would otherwise report as a clean `done`: emit a\n // loud, non-auto-continuing terminal so the user knows the turn stopped\n // before finishing and nothing was partially saved by the run itself.\n if (!upstreamSignal.aborted && lastAttemptWasUnfinishedContinuation) {\n // Discard any partial text already streamed for the unfinished attempt so\n // the terminal message stands alone instead of trailing a half sentence.\n // Preserve completed tool cards: they are the user's only durable proof\n // that a side effect landed before the final assistant note timed out.\n if (\n !(await hasCompletedSideEffectToolCallInCurrentTurn(\n opts.threadId,\n localTurnEvents,\n ))\n ) {\n opts.send({ type: \"clear\" });\n }\n opts.send({\n type: \"error\",\n error: RUN_BUDGET_EXHAUSTED_MESSAGE,\n errorCode: RUN_BUDGET_EXHAUSTED_ERROR_CODE,\n recoverable: true,\n });\n }\n\n return usage;\n}\n"]}
1
+ {"version":3,"file":"run-loop-with-resume.js","sourceRoot":"","sources":["../../src/agent/run-loop-with-resume.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Wraps `runAgentLoop` with two layered recovery mechanisms so a single hosted\n * invocation can survive interruptions without showing the user a dead chat:\n *\n * 1. **Soft timeout** — an inner timer that aborts the LLM call before the\n * hosting function's hard limit (Lambda 75s, Vercel 60s, etc.) so we have a\n * chance to gracefully wind down and append a continuation nudge. Without\n * this the function gets killed mid-stream and the user sees a frozen\n * spinner.\n *\n * 2. **Resumable-error continuation** — when the LLM call errors with a\n * transport- or gateway-level interruption (Builder gateway 45s timeout,\n * socket hang up, ECONNRESET, upstream 5xx that survived engine retries),\n * we save the conversation prefix, append a \"continue from where you left\n * off\" message, and run another LLM call. Anthropic's prompt cache makes\n * the resume call dramatically faster than the cold first attempt, and the\n * agent gets explicit context that it was cut off so it doesn't re-do\n * completed work.\n *\n * Both paths route through `appendAgentLoopContinuation` so the agent sees a\n * uniform \"continue\" instruction regardless of which recovery fired.\n */\n\nimport type { EngineMessage } from \"./engine/types.js\";\nimport {\n runAgentLoop,\n appendAgentLoopContinuation,\n isResumableEngineError,\n continuationReasonForResumableError,\n lastUnfinishedPreparingActionToolFromEvents,\n resolveFinalResponseGuardRequestText,\n type AgentLoopContinuationReason,\n} from \"./production-agent.js\";\nimport { resolveRunSoftTimeoutMs } from \"./run-manager.js\";\nimport type { ResolveRunSoftTimeoutOptions } from \"./run-manager.js\";\nimport { getCurrentTurnEventsForThread } from \"./run-store.js\";\nimport {\n classifyToolCallJournal,\n buildResumeJournalNote,\n} from \"./tool-call-journal.js\";\nimport type { AgentChatEvent } from \"./types.js\";\n\nasync function readCurrentTurnEventsForResume(\n threadId: string | undefined,\n localEvents: readonly AgentChatEvent[] = [],\n): Promise<AgentChatEvent[]> {\n let persistedEvents: AgentChatEvent[] = [];\n try {\n persistedEvents = threadId\n ? await getCurrentTurnEventsForThread(threadId)\n : [];\n } catch {\n persistedEvents = [];\n }\n if (localEvents.length === 0) return persistedEvents;\n const seen = new Set(persistedEvents.map((event) => JSON.stringify(event)));\n return [\n ...persistedEvents,\n ...localEvents.filter((event) => !seen.has(JSON.stringify(event))),\n ];\n}\n\nfunction actionPreparationContinuationOptions(\n events: readonly AgentChatEvent[],\n): { actionPreparationTool?: string } {\n const actionPreparationTool =\n lastUnfinishedPreparingActionToolFromEvents(events);\n return actionPreparationTool ? { actionPreparationTool } : {};\n}\n\n/**\n * Derive the per-turn tool-call journal from the durable run-event ledger and,\n * when there is anything to report, append a STRUCTURED note to the message\n * prefix so the resumed model:\n * - does NOT re-execute tool calls that already completed (avoiding duplicate\n * side effects like re-sending an email or re-creating a ticket), and\n * - is explicitly told about any tool call that started but whose outcome was\n * never recorded (\"interrupted, unknown outcome\") so it can decide.\n *\n * This is additive to the existing \"continue from where you left off\" nudge —\n * it is appended right after it. When the journal is empty (no completed or\n * interrupted tool calls — e.g. a turn with no tool activity, or a clean\n * continuation), nothing extra is appended and resume behavior is byte-for-byte\n * what it was before. Best-effort: any ledger read/parse failure is swallowed so\n * a journal hiccup can never block a recovery that would otherwise succeed.\n *\n * This prompt-level journal is paired with tool-layer enforcement in\n * production-agent.ts/runToolCall, which refuses to re-execute a journaled-\n * complete write tool (returning the journaled result instead). See\n * `tool-call-journal.ts` (`findCompletedJournalEntry`) for the keying used.\n */\nfunction appendToolCallJournalNote(\n messages: EngineMessage[],\n events: readonly AgentChatEvent[],\n): void {\n try {\n if (events.length === 0) return;\n const journal = classifyToolCallJournal(events);\n const note = buildResumeJournalNote(journal);\n if (!note) return;\n messages.push({\n role: \"user\",\n content: [{ type: \"text\", text: note }],\n });\n } catch {\n // The journal is a hardening layer, never a gate. A failed ledger read or\n // parse must not break the resume that the continuation nudge already set\n // up — the model still continues, just without the structured journal.\n }\n}\n\nasync function appendContinuationAndJournal(\n messages: EngineMessage[],\n reason: AgentLoopContinuationReason,\n threadId: string | undefined,\n localEvents: readonly AgentChatEvent[] = [],\n): Promise<void> {\n const events = await readCurrentTurnEventsForResume(threadId, localEvents);\n appendAgentLoopContinuation(\n messages,\n reason,\n actionPreparationContinuationOptions(events),\n );\n appendToolCallJournalNote(messages, events);\n}\n\n/**\n * Rebuild the same safe continuation context for a logical turn that resumes\n * in a fresh hosted invocation.\n */\nexport async function appendDurableContinuationContext(\n messages: EngineMessage[],\n reason: AgentLoopContinuationReason,\n threadId: string,\n): Promise<void> {\n await appendContinuationAndJournal(messages, reason, threadId);\n}\n\nasync function hasCompletedSideEffectToolCallInCurrentTurn(\n threadId: string | undefined,\n localEvents: readonly AgentChatEvent[] = [],\n): Promise<boolean> {\n try {\n const events = await readCurrentTurnEventsForResume(threadId, localEvents);\n if (events.length === 0) return false;\n return events.some(\n (event) =>\n event.type === \"tool_done\" &&\n event.completedSideEffect === true &&\n event.isError !== true,\n );\n } catch {\n return false;\n }\n}\n\nfunction internalContinuationReasonForAttempt(\n events: readonly AgentChatEvent[],\n): AgentLoopContinuationReason | undefined {\n const last = events.at(-1);\n if (last?.type !== \"auto_continue\") return undefined;\n if (\n last.reason === \"run_timeout\" ||\n last.reason === \"loop_limit\" ||\n last.reason === \"no_progress\" ||\n last.reason === \"stream_ended\" ||\n last.reason === \"gateway_timeout\" ||\n last.reason === \"network_interrupted\"\n ) {\n return last.reason;\n }\n return undefined;\n}\n\n/**\n * Cap on continuation iterations inside a single\n * `runAgentLoopDirectWithSoftTimeout` invocation. The host's hard function\n * timeout usually bounds this naturally — but a defensive cap prevents an\n * instant-error spiral from looping forever inside hosting environments with a\n * generous budget.\n *\n * 6 leaves room for: 1 normal completion + a few resume rounds for design\n * generation (prompt + 3 variants ≈ 4 LLM calls), with a small safety margin.\n */\nexport const MAX_RUN_LOOP_CONTINUATIONS = 6;\n\n/** Machine-readable code carried on the give-up terminal `error` event so the\n * client renders a loud \"stopped before finishing\" terminal instead of an\n * ambiguous silent stall. Deliberately NOT in the client's auto-recoverable\n * allow-list (`isAutoRecoverableError`) so it terminates the chain rather than\n * looping another POST that would hit the same wall. */\nexport const RUN_BUDGET_EXHAUSTED_ERROR_CODE = \"run_budget_exhausted\";\n\n/** User-facing terminal message when a hosted run is cut off mid-step and\n * exhausts its in-invocation continuation budget without finishing. Generic and\n * framework-level (not app-specific). Mirrors the `reliable-mutations` skill's\n * \"fail loud, retry as a single bulk action\" guidance so the user understands\n * the turn stopped before finishing without implying that earlier completed\n * tool calls did not persist. */\nexport const RUN_BUDGET_EXHAUSTED_MESSAGE =\n \"I ran out of time before finishing this step. \" +\n \"I stopped rather than keep retrying silently. \" +\n \"Check any completed tool cards above before retrying, ideally as one smaller follow-up.\";\n\n/**\n * Internal entry point used by the agent-chat plugin's run handler. Wraps\n * `runAgentLoop` with soft-timeout + resumable-error continuation recovery.\n *\n * The `softTimeoutMs` argument falls back to `resolveRunSoftTimeoutMs(...)` so\n * different hosting environments (Lambda, Vercel, Cloudflare, local dev) get\n * an appropriate inner budget. Setting it to <= 0 disables both layers — the\n * call goes straight to `runAgentLoop` with no wrapping.\n */\nexport async function runAgentLoopDirectWithSoftTimeout(\n opts: Parameters<typeof runAgentLoop>[0],\n softTimeoutMs?: number,\n timeoutOptions?: ResolveRunSoftTimeoutOptions,\n): Promise<Awaited<ReturnType<typeof runAgentLoop>>> {\n const finalResponseGuardRequestText =\n opts.finalResponseGuardRequestText ??\n resolveFinalResponseGuardRequestText(opts.messages);\n const stableOpts = { ...opts, finalResponseGuardRequestText };\n const timeoutMs = resolveRunSoftTimeoutMs(softTimeoutMs, timeoutOptions);\n if (timeoutMs <= 0) return runAgentLoop(stableOpts);\n\n const upstreamSignal = opts.signal;\n const usage: Awaited<ReturnType<typeof runAgentLoop>> = {\n inputTokens: 0,\n outputTokens: 0,\n cacheReadTokens: 0,\n cacheWriteTokens: 0,\n model: opts.model,\n };\n\n const addUsage = (next: Awaited<ReturnType<typeof runAgentLoop>>) => {\n usage.inputTokens += next.inputTokens;\n usage.outputTokens += next.outputTokens;\n usage.cacheReadTokens += next.cacheReadTokens;\n usage.cacheWriteTokens += next.cacheWriteTokens;\n usage.model = next.model;\n };\n\n const localTurnEvents: AgentChatEvent[] = [];\n let attempts = 0;\n // Tracks whether the most recent attempt ended by scheduling another\n // continuation (soft-timeout or resumable error → `continue`) rather than\n // returning a finished turn. When the loop then exits because the budget is\n // exhausted (NOT because the user aborted and NOT because the turn finished),\n // this is the silent give-up case: emit a loud terminal so the user sees an\n // unambiguous \"stopped before finishing\" instead of a bare done/\"…\".\n let lastAttemptWasUnfinishedContinuation = false;\n while (!upstreamSignal.aborted && attempts < MAX_RUN_LOOP_CONTINUATIONS) {\n attempts++;\n lastAttemptWasUnfinishedContinuation = false;\n const controller = new AbortController();\n const abortFromUpstream = () => controller.abort();\n if (upstreamSignal.aborted) {\n controller.abort();\n } else {\n upstreamSignal.addEventListener(\"abort\", abortFromUpstream, {\n once: true,\n });\n }\n\n let softTimedOut = false;\n const timer = setTimeout(() => {\n if (controller.signal.aborted) return;\n softTimedOut = true;\n controller.abort();\n }, timeoutMs);\n\n const attemptStartIndex = localTurnEvents.length;\n const send = (event: AgentChatEvent) => {\n localTurnEvents.push(event);\n opts.send(event);\n };\n\n try {\n const nextUsage = await runAgentLoop({\n ...stableOpts,\n send,\n signal: controller.signal,\n });\n addUsage(nextUsage);\n const attemptEvents = localTurnEvents.slice(attemptStartIndex);\n const internalContinuationReason =\n internalContinuationReasonForAttempt(attemptEvents);\n if (internalContinuationReason && !upstreamSignal.aborted) {\n lastAttemptWasUnfinishedContinuation = true;\n const continuationEvents = [...localTurnEvents];\n if (\n !(await hasCompletedSideEffectToolCallInCurrentTurn(\n opts.threadId,\n continuationEvents,\n ))\n ) {\n opts.send({ type: \"clear\" });\n }\n await appendContinuationAndJournal(\n opts.messages,\n internalContinuationReason,\n opts.threadId,\n continuationEvents,\n );\n continue;\n }\n if (softTimedOut && !upstreamSignal.aborted) {\n lastAttemptWasUnfinishedContinuation = true;\n await appendContinuationAndJournal(\n opts.messages,\n \"run_timeout\",\n opts.threadId,\n localTurnEvents,\n );\n continue;\n }\n return usage;\n } catch (err) {\n if (softTimedOut && !upstreamSignal.aborted) {\n // Clear partial text the client received before the abort so the\n // resumed model doesn't re-emit it and produce duplicated output.\n lastAttemptWasUnfinishedContinuation = true;\n if (\n !(await hasCompletedSideEffectToolCallInCurrentTurn(\n opts.threadId,\n localTurnEvents,\n ))\n ) {\n opts.send({ type: \"clear\" });\n }\n await appendContinuationAndJournal(\n opts.messages,\n \"run_timeout\",\n opts.threadId,\n localTurnEvents,\n );\n continue;\n }\n // Resumable transport / gateway interruptions: the LLM call was cut off\n // mid-stream (gateway 45s timeout, socket hang up, function-level\n // timeout that didn't trip our soft timer first). Treat it the same way\n // as a soft timeout — append a \"continue from where you left off\" nudge\n // and let the loop run another LLM call. The conversation prefix up to\n // the cut-off is preserved in opts.messages, and Anthropic's prompt\n // cache makes the resume call much faster.\n //\n // Emit 'clear' so any partial streamed text is discarded on the client\n // before the model resumes. Without this the model restarts its sentence\n // from scratch and the fold produces duplicated text in one message\n // (the partial text was already sent to the client but never entered\n // the in-memory messages array, so the next attempt re-emits it).\n if (!upstreamSignal.aborted && isResumableEngineError(err)) {\n lastAttemptWasUnfinishedContinuation = true;\n if (\n !(await hasCompletedSideEffectToolCallInCurrentTurn(\n opts.threadId,\n localTurnEvents,\n ))\n ) {\n opts.send({ type: \"clear\" });\n }\n await appendContinuationAndJournal(\n opts.messages,\n continuationReasonForResumableError(err),\n opts.threadId,\n localTurnEvents,\n );\n continue;\n }\n throw err;\n } finally {\n clearTimeout(timer);\n upstreamSignal.removeEventListener(\"abort\", abortFromUpstream);\n }\n }\n\n // The loop exited without a clean return. If the user aborted, that's a Stop —\n // stay silent. Otherwise we only get here by exhausting\n // MAX_RUN_LOOP_CONTINUATIONS while the last attempt was still trying to\n // continue (soft-timeout / resumable error). That is the genuinely-silent\n // give-up the run-manager would otherwise report as a clean `done`: emit a\n // loud, non-auto-continuing terminal so the user knows the turn stopped\n // before finishing and nothing was partially saved by the run itself.\n if (!upstreamSignal.aborted && lastAttemptWasUnfinishedContinuation) {\n // Discard any partial text already streamed for the unfinished attempt so\n // the terminal message stands alone instead of trailing a half sentence.\n // Preserve completed tool cards: they are the user's only durable proof\n // that a side effect landed before the final assistant note timed out.\n if (\n !(await hasCompletedSideEffectToolCallInCurrentTurn(\n opts.threadId,\n localTurnEvents,\n ))\n ) {\n opts.send({ type: \"clear\" });\n }\n opts.send({\n type: \"error\",\n error: RUN_BUDGET_EXHAUSTED_MESSAGE,\n errorCode: RUN_BUDGET_EXHAUSTED_ERROR_CODE,\n recoverable: true,\n });\n }\n\n return usage;\n}\n"]}
@@ -62,11 +62,11 @@ export declare const postAwareness: import("h3").EventHandlerWithFetch<import("h
62
62
  error: string;
63
63
  states?: undefined;
64
64
  } | {
65
- error?: undefined;
66
65
  states: {
67
66
  clientId: number;
68
67
  state: string;
69
68
  }[];
69
+ error?: undefined;
70
70
  }>>;
71
71
  /**
72
72
  * GET /_agent-native/collab/:docId/users
@@ -77,10 +77,10 @@ export declare const getActiveUsers: import("h3").EventHandlerWithFetch<import("
77
77
  error: string;
78
78
  users?: undefined;
79
79
  } | {
80
- error?: undefined;
81
80
  users: {
82
81
  clientId: number;
83
82
  lastSeen: number;
84
83
  }[];
84
+ error?: undefined;
85
85
  }>>;
86
86
  //# sourceMappingURL=awareness.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"awareness.d.ts","sourceRoot":"","sources":["../../src/collab/awareness.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAgB3C,MAAM,WAAW,cAAc;IAC7B,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,CAAC;CAClB;AAOD,eAAO,MAAM,sBAAsB,EAAG,kBAA2B,CAAC;AAElE,MAAM,WAAW,oBAAoB;IACnC,MAAM,EAAE,WAAW,CAAC;IACpB,IAAI,EAAE,kBAAkB,CAAC;IACzB,KAAK,EAAE,MAAM,CAAC;IACd,2EAA2E;IAC3E,MAAM,EAAE,KAAK,CAAC;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IACnD,gFAAgF;IAChF,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,sCAAsC;IACtC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,2EAA2E;IAC3E,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,yEAAyE;IACzE,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,cAAc;IAC7B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAOD,wBAAgB,mBAAmB,IAAI,YAAY,CAElD;AAED,wBAAgB,sBAAsB,CACpC,KAAK,EAAE,MAAM,EACb,KAAK,EAAE,cAAc,GAAG,SAAS,GAChC,IAAI,CAON;AAED,wBAAgB,mBAAmB,CACjC,KAAK,EAAE,MAAM,EACb,MAAM,EAAE,KAAK,CAAC;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC,EAClD,KAAK,CAAC,EAAE,cAAc,GACrB,IAAI,CAgBN;AAoBD,wBAAgB,sBAAsB,CACpC,KAAK,EAAE,MAAM,EACb,QAAQ,EAAE,MAAM,EAChB,SAAS,GAAE,MAAmB,GAC7B,IAAI,CAON;AAED,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,IAAI,CAE1E;AAiBD,wBAAgB,eAAe,CAAC,KAAK,EAAE,MAAM,GAAG,GAAG,CAAC,MAAM,EAAE,cAAc,CAAC,CAO1E;AAED,wBAAgB,YAAY,CAAC,GAAG,EAAE,GAAG,CAAC,MAAM,EAAE,cAAc,CAAC,GAAG,IAAI,CAOnE;AAkCD;;;;;;;GAOG;AACH,eAAO,MAAM,aAAa;;;;;;kBAwDa,MAAM;eAAS,MAAM;;GAoB1D,CAAC;AAEH;;;;GAIG;AACH,eAAO,MAAM,cAAc;;;;;;kBAYM,MAAM;kBAAY,MAAM;;GAMvD,CAAC"}
1
+ {"version":3,"file":"awareness.d.ts","sourceRoot":"","sources":["../../src/collab/awareness.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAgB3C,MAAM,WAAW,cAAc;IAC7B,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,CAAC;CAClB;AAOD,eAAO,MAAM,sBAAsB,EAAG,kBAA2B,CAAC;AAElE,MAAM,WAAW,oBAAoB;IACnC,MAAM,EAAE,WAAW,CAAC;IACpB,IAAI,EAAE,kBAAkB,CAAC;IACzB,KAAK,EAAE,MAAM,CAAC;IACd,2EAA2E;IAC3E,MAAM,EAAE,KAAK,CAAC;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IACnD,gFAAgF;IAChF,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,sCAAsC;IACtC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,2EAA2E;IAC3E,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,yEAAyE;IACzE,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,cAAc;IAC7B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAOD,wBAAgB,mBAAmB,IAAI,YAAY,CAElD;AAED,wBAAgB,sBAAsB,CACpC,KAAK,EAAE,MAAM,EACb,KAAK,EAAE,cAAc,GAAG,SAAS,GAChC,IAAI,CAON;AAED,wBAAgB,mBAAmB,CACjC,KAAK,EAAE,MAAM,EACb,MAAM,EAAE,KAAK,CAAC;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC,EAClD,KAAK,CAAC,EAAE,cAAc,GACrB,IAAI,CAgBN;AAoBD,wBAAgB,sBAAsB,CACpC,KAAK,EAAE,MAAM,EACb,QAAQ,EAAE,MAAM,EAChB,SAAS,GAAE,MAAmB,GAC7B,IAAI,CAON;AAED,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,IAAI,CAE1E;AAiBD,wBAAgB,eAAe,CAAC,KAAK,EAAE,MAAM,GAAG,GAAG,CAAC,MAAM,EAAE,cAAc,CAAC,CAO1E;AAED,wBAAgB,YAAY,CAAC,GAAG,EAAE,GAAG,CAAC,MAAM,EAAE,cAAc,CAAC,GAAG,IAAI,CAOnE;AAkCD;;;;;;;GAOG;AACH,eAAO,MAAM,aAAa;;;;;kBAwDa,MAAM;eAAS,MAAM;;;GAoB1D,CAAC;AAEH;;;;GAIG;AACH,eAAO,MAAM,cAAc;;;;;kBAYM,MAAM;kBAAY,MAAM;;;GAMvD,CAAC"}
@@ -3,18 +3,18 @@ declare const _default: import("../../action.js").ActionDefinition<{
3
3
  url?: string;
4
4
  filename?: string;
5
5
  }, {
6
+ id?: undefined;
6
7
  error: string;
7
8
  configured?: undefined;
8
9
  connectPath?: undefined;
9
10
  url?: undefined;
10
- id?: undefined;
11
11
  provider?: undefined;
12
12
  } | {
13
+ id?: undefined;
13
14
  error: string;
14
15
  configured: boolean;
15
16
  connectPath: string;
16
17
  url?: undefined;
17
- id?: undefined;
18
18
  provider?: undefined;
19
19
  } | {
20
20
  error?: undefined;
@@ -7,4 +7,18 @@ export declare function processDueA2AContinuations(options: {
7
7
  adapters: Map<string, PlatformAdapter>;
8
8
  limit?: number;
9
9
  }): Promise<void>;
10
+ /**
11
+ * Durable scheduler wake-up only: make a bounded set of due/stale rows
12
+ * eligible, then invoke their normal processors. It never polls remote A2A
13
+ * tasks or runs a mutation itself, keeping the scheduled route within its
14
+ * short execution budget. Duplicate wake-ups are safe because each processor
15
+ * still takes the store's atomic claim before it can progress or deliver.
16
+ */
17
+ export declare function recoverDueA2AContinuations(options?: {
18
+ limit?: number;
19
+ webhookBaseUrl?: string;
20
+ }): Promise<{
21
+ dispatched: number;
22
+ failed: number;
23
+ }>;
10
24
  //# sourceMappingURL=a2a-continuation-processor.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"a2a-continuation-processor.d.ts","sourceRoot":"","sources":["../../src/integrations/a2a-continuation-processor.ts"],"names":[],"mappings":"AA4BA,OAAO,KAAK,EAEV,eAAe,EAGhB,MAAM,YAAY,CAAC;AAkBpB,wBAAsB,uBAAuB,CAC3C,cAAc,EAAE,MAAM,EACtB,cAAc,CAAC,EAAE,MAAM,GACtB,OAAO,CAAC,IAAI,CAAC,CAqDf;AAmBD,wBAAsB,0BAA0B,CAC9C,cAAc,EAAE,MAAM,EACtB,OAAO,EAAE;IAAE,QAAQ,EAAE,GAAG,CAAC,MAAM,EAAE,eAAe,CAAC,CAAA;CAAE,GAClD,OAAO,CAAC,IAAI,CAAC,CAMf;AAED,wBAAsB,0BAA0B,CAAC,OAAO,EAAE;IACxD,QAAQ,EAAE,GAAG,CAAC,MAAM,EAAE,eAAe,CAAC,CAAC;IACvC,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB,GAAG,OAAO,CAAC,IAAI,CAAC,CAUhB"}
1
+ {"version":3,"file":"a2a-continuation-processor.d.ts","sourceRoot":"","sources":["../../src/integrations/a2a-continuation-processor.ts"],"names":[],"mappings":"AA8CA,OAAO,KAAK,EAEV,eAAe,EAGhB,MAAM,YAAY,CAAC;AAkBpB,wBAAsB,uBAAuB,CAC3C,cAAc,EAAE,MAAM,EACtB,cAAc,CAAC,EAAE,MAAM,GACtB,OAAO,CAAC,IAAI,CAAC,CAqDf;AAmBD,wBAAsB,0BAA0B,CAC9C,cAAc,EAAE,MAAM,EACtB,OAAO,EAAE;IAAE,QAAQ,EAAE,GAAG,CAAC,MAAM,EAAE,eAAe,CAAC,CAAA;CAAE,GAClD,OAAO,CAAC,IAAI,CAAC,CAMf;AAED,wBAAsB,0BAA0B,CAAC,OAAO,EAAE;IACxD,QAAQ,EAAE,GAAG,CAAC,MAAM,EAAE,eAAe,CAAC,CAAC;IACvC,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB,GAAG,OAAO,CAAC,IAAI,CAAC,CAUhB;AAED;;;;;;GAMG;AACH,wBAAsB,0BAA0B,CAAC,OAAO,CAAC,EAAE;IACzD,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB,GAAG,OAAO,CAAC;IAAE,UAAU,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC,CAuClD"}
@@ -5,8 +5,11 @@ import { extractThreadMeta } from "../agent/thread-data-builder.js";
5
5
  import { getThread, updateThreadData } from "../chat-threads/store.js";
6
6
  import { withConfiguredAppBasePath } from "../server/app-base-path.js";
7
7
  import { FRAMEWORK_ROUTE_PREFIX } from "../server/core-routes-plugin.js";
8
- import { claimA2AContinuation, claimA2AContinuationDelivery, claimDueA2AContinuations, completeA2AContinuation, failA2AContinuation, getA2AContinuation, rescheduleA2AContinuation, } from "./a2a-continuations-store.js";
8
+ import { claimA2AContinuation, claimA2AContinuationDelivery, claimDueA2AContinuations, completeA2AContinuation, failA2AContinuationsForIntegrationTask, failA2AContinuation, getA2AContinuation, hasActiveA2AContinuationsForIntegrationTask, listRecoverableA2AIntegrationTasks, recoverDueA2AContinuationIds, rescheduleA2AContinuation, } from "./a2a-continuations-store.js";
9
+ import { completeIntegrationCampaignTaskAfterA2A, failDisabledIntegrationCampaignTask, getIntegrationCampaignForTask, } from "./integration-campaigns-store.js";
10
+ import { dispatchPendingIntegrationTask, isIntegrationDurableDispatchEnabledForTask, } from "./integration-durable-dispatch.js";
9
11
  import { signInternalToken } from "./internal-token.js";
12
+ import { getNextPendingTaskForThread, getPendingTask, } from "./pending-tasks-store.js";
10
13
  import { getThreadMapping } from "./thread-mapping-store.js";
11
14
  const PROCESSOR_PATH = `${FRAMEWORK_ROUTE_PREFIX}/integrations/process-a2a-continuation`;
12
15
  const TERMINAL_STATES = new Set(["completed", "failed", "canceled"]);
@@ -90,7 +93,52 @@ export async function processDueA2AContinuations(options) {
90
93
  await processClaimedContinuation(continuation, options).catch((err) => console.error(`[integrations] A2A continuation ${continuation.id} failed:`, err));
91
94
  }
92
95
  }
96
+ /**
97
+ * Durable scheduler wake-up only: make a bounded set of due/stale rows
98
+ * eligible, then invoke their normal processors. It never polls remote A2A
99
+ * tasks or runs a mutation itself, keeping the scheduled route within its
100
+ * short execution budget. Duplicate wake-ups are safe because each processor
101
+ * still takes the store's atomic claim before it can progress or deliver.
102
+ */
103
+ export async function recoverDueA2AContinuations(options) {
104
+ const limit = options?.limit ?? 5;
105
+ const candidateTasks = await listRecoverableA2AIntegrationTasks(200);
106
+ const eligibleTaskIds = [];
107
+ for (const task of candidateTasks) {
108
+ const enabled = isIntegrationDurableDispatchEnabledForTask({
109
+ platform: task.platform,
110
+ externalThreadId: task.externalThreadId,
111
+ platformContext: task.dispatchScope
112
+ ? { channelId: task.dispatchScope }
113
+ : undefined,
114
+ });
115
+ if (enabled) {
116
+ eligibleTaskIds.push(task.id);
117
+ if (eligibleTaskIds.length >= limit)
118
+ break;
119
+ }
120
+ else {
121
+ await failDisabledDurableA2ATask(task);
122
+ }
123
+ }
124
+ const ids = await recoverDueA2AContinuationIds(limit, eligibleTaskIds);
125
+ let dispatched = 0;
126
+ let failed = 0;
127
+ await Promise.all(ids.map(async (id) => {
128
+ try {
129
+ await dispatchA2AContinuation(id, options?.webhookBaseUrl);
130
+ dispatched += 1;
131
+ }
132
+ catch (err) {
133
+ failed += 1;
134
+ console.error(`[integrations] Failed to recover A2A continuation ${id}:`, err);
135
+ }
136
+ }));
137
+ return { dispatched, failed };
138
+ }
93
139
  async function processClaimedContinuation(continuation, options) {
140
+ if (!(await durableContinuationScopeStillEnabled(continuation)))
141
+ return;
94
142
  const adapter = options.adapters.get(continuation.platform);
95
143
  if (!adapter) {
96
144
  await failA2AContinuation(continuation.id, `Unknown platform: ${continuation.platform}`);
@@ -172,6 +220,52 @@ async function processClaimedContinuation(continuation, options) {
172
220
  }
173
221
  await deliverAndCompleteA2AContinuation(continuation, adapter, text, progress);
174
222
  }
223
+ async function durableContinuationScopeStillEnabled(continuation) {
224
+ const campaign = await getIntegrationCampaignForTask(continuation.integrationTaskId);
225
+ if (!campaign)
226
+ return true;
227
+ if (campaign.status === "completed" || campaign.status === "failed") {
228
+ await failA2AContinuation(continuation.id, "Owning integration campaign is already terminal");
229
+ return false;
230
+ }
231
+ const task = await getPendingTask(continuation.integrationTaskId);
232
+ const enabled = task?.status === "processing" &&
233
+ isIntegrationDurableDispatchEnabledForTask({
234
+ platform: task.platform,
235
+ externalThreadId: task.externalThreadId,
236
+ platformContext: task.dispatchScope
237
+ ? { channelId: task.dispatchScope }
238
+ : undefined,
239
+ });
240
+ if (enabled)
241
+ return true;
242
+ await failDisabledDurableA2ATask({
243
+ id: continuation.integrationTaskId,
244
+ platform: task?.platform ?? continuation.platform,
245
+ externalThreadId: task?.externalThreadId ?? continuation.externalThreadId,
246
+ dispatchScope: task?.dispatchScope ?? null,
247
+ status: task?.status ?? "missing",
248
+ });
249
+ return false;
250
+ }
251
+ async function failDisabledDurableA2ATask(task) {
252
+ const message = "Durable integration campaign was disabled for this scope";
253
+ await failA2AContinuationsForIntegrationTask(task.id, message);
254
+ await failDisabledIntegrationCampaignTask(task.id, message);
255
+ const nextTask = await getNextPendingTaskForThread(task.platform, task.externalThreadId);
256
+ if (nextTask) {
257
+ await dispatchPendingIntegrationTask({
258
+ taskId: nextTask.id,
259
+ task: {
260
+ platform: task.platform,
261
+ externalThreadId: task.externalThreadId,
262
+ platformContext: nextTask.dispatchScope
263
+ ? { channelId: nextTask.dispatchScope }
264
+ : undefined,
265
+ },
266
+ });
267
+ }
268
+ }
175
269
  async function resumeA2AContinuationProgress(continuation, adapter) {
176
270
  if (!continuation.progressRef || !adapter.resumeRunProgress)
177
271
  return null;
@@ -223,6 +317,8 @@ async function waitForContinuationDue(continuationId) {
223
317
  return true;
224
318
  }
225
319
  async function notifyAndFailA2AContinuation(continuation, adapter, reason, progress = null) {
320
+ if (!(await durableContinuationScopeStillEnabled(continuation)))
321
+ return;
226
322
  const deliveryContinuation = await claimA2AContinuationDelivery(continuation.id);
227
323
  if (!deliveryContinuation)
228
324
  return;
@@ -233,10 +329,20 @@ async function notifyAndFailA2AContinuation(continuation, adapter, reason, progr
233
329
  }
234
330
  catch (err) {
235
331
  console.error(`[integrations] Failed to notify ${deliveryContinuation.platform} about failed A2A continuation ${deliveryContinuation.id}:`, err);
332
+ if (deliveryContinuation.attempts >= MAX_ATTEMPTS) {
333
+ await failA2AContinuation(deliveryContinuation.id, reason);
334
+ await completeParentCampaignAfterTerminalA2A(deliveryContinuation);
335
+ return;
336
+ }
337
+ await rescheduleAndRedispatchA2AContinuation(deliveryContinuation.id);
338
+ return;
236
339
  }
237
340
  await failA2AContinuation(deliveryContinuation.id, reason);
341
+ await completeParentCampaignAfterTerminalA2A(deliveryContinuation);
238
342
  }
239
343
  async function deliverAndCompleteA2AContinuation(continuation, adapter, text, progress = null) {
344
+ if (!(await durableContinuationScopeStillEnabled(continuation)))
345
+ return;
240
346
  const deliveryContinuation = await claimA2AContinuationDelivery(continuation.id);
241
347
  if (!deliveryContinuation)
242
348
  return;
@@ -358,6 +464,7 @@ async function completeAfterSuccessfulDelivery(continuation) {
358
464
  for (let attempt = 0; attempt < COMPLETE_AFTER_DELIVERY_ATTEMPTS; attempt++) {
359
465
  try {
360
466
  await completeA2AContinuation(continuation.id);
467
+ await completeParentCampaignAfterTerminalA2A(continuation);
361
468
  return;
362
469
  }
363
470
  catch (err) {
@@ -367,6 +474,32 @@ async function completeAfterSuccessfulDelivery(continuation) {
367
474
  console.error(`[integrations] ${continuation.platform} accepted A2A continuation ${continuation.id}, ` +
368
475
  "but marking it completed failed. Leaving it in delivering for stale-delivery recovery.", lastError);
369
476
  }
477
+ async function completeParentCampaignAfterTerminalA2A(continuation) {
478
+ const campaign = await getIntegrationCampaignForTask(continuation.integrationTaskId);
479
+ if (!campaign)
480
+ return;
481
+ if (await hasActiveA2AContinuationsForIntegrationTask(continuation.integrationTaskId)) {
482
+ return;
483
+ }
484
+ const completed = await completeIntegrationCampaignTaskAfterA2A(continuation.integrationTaskId);
485
+ if (!completed)
486
+ return;
487
+ const nextTask = await getNextPendingTaskForThread(continuation.platform, continuation.externalThreadId);
488
+ if (!nextTask)
489
+ return;
490
+ await dispatchPendingIntegrationTask({
491
+ taskId: nextTask.id,
492
+ task: {
493
+ platform: continuation.platform,
494
+ externalThreadId: continuation.externalThreadId,
495
+ platformContext: nextTask.dispatchScope
496
+ ? { channelId: nextTask.dispatchScope }
497
+ : undefined,
498
+ },
499
+ }).catch((err) => {
500
+ console.error(`[integrations] Failed to wake successor ${nextTask.id} after A2A parent completion:`, err);
501
+ });
502
+ }
370
503
  function formatContinuationFailureMessage(continuation, reason) {
371
504
  const explicitCode = extractFailureCode(reason);
372
505
  const diagnostics = formatContinuationFailureDiagnostics(continuation, reason);