@agent-native/core 0.122.1 → 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 (114) hide show
  1. package/README.md +1 -13
  2. package/corpus/README.md +2 -2
  3. package/corpus/core/CHANGELOG.md +12 -0
  4. package/corpus/core/package.json +1 -1
  5. package/corpus/core/src/agent/production-agent.ts +65 -1
  6. package/corpus/core/src/agent/run-loop-with-resume.ts +12 -0
  7. package/corpus/core/src/agent/run-store.ts +61 -1
  8. package/corpus/core/src/integrations/a2a-continuation-processor.ts +182 -0
  9. package/corpus/core/src/integrations/a2a-continuations-store.ts +122 -12
  10. package/corpus/core/src/integrations/integration-campaign-recovery.ts +127 -0
  11. package/corpus/core/src/integrations/integration-campaigns-store.ts +1039 -0
  12. package/corpus/core/src/integrations/integration-durable-dispatch.ts +28 -3
  13. package/corpus/core/src/integrations/pending-tasks-retry-job.ts +8 -0
  14. package/corpus/core/src/integrations/plugin.ts +453 -35
  15. package/corpus/core/src/integrations/webhook-handler.ts +469 -11
  16. package/corpus/core/src/server/builder-design-systems.ts +1 -1
  17. package/corpus/templates/analytics/.agents/skills/dashboard-management/SKILL.md +15 -0
  18. package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/report-panel-window.ts +3 -3
  19. package/corpus/templates/analytics/changelog/2026-07-25-analytics-dashboards-and-scheduled-reports-are-faster-and-mo.md +6 -0
  20. package/corpus/templates/analytics/server/db/schema.ts +12 -0
  21. package/corpus/templates/analytics/server/lib/dashboard-panel-query.ts +8 -4
  22. package/corpus/templates/analytics/server/lib/dashboard-report.ts +136 -5
  23. package/corpus/templates/analytics/server/lib/dashboard-time-scope.ts +250 -1
  24. package/corpus/templates/analytics/server/lib/first-party-analytics-cache.ts +167 -0
  25. package/corpus/templates/analytics/server/lib/first-party-analytics.ts +34 -9
  26. package/corpus/templates/analytics/server/lib/first-party-dashboard-repair.ts +105 -23
  27. package/corpus/templates/analytics/server/lib/first-party-unbounded-panel-repair.ts +169 -0
  28. package/corpus/templates/analytics/server/plugins/db.ts +41 -1
  29. package/corpus/templates/analytics/shared/dashboard-report-timeouts.ts +6 -3
  30. package/corpus/templates/content/app/components/editor/DocumentEditor.tsx +7 -3
  31. package/corpus/templates/content/app/components/editor/body-hydration.ts +10 -0
  32. package/corpus/templates/content/app/components/editor/database/DatabaseView.tsx +1 -0
  33. package/corpus/templates/content/app/root.tsx +12 -1
  34. package/corpus/templates/content/changelog/2026-07-23-new-pages-open-immediately-while-slow-network-work-continues.md +6 -0
  35. package/corpus/templates/content/changelog/2026-07-25-database-preview-actions-open-reliably.md +6 -0
  36. package/corpus/templates/content/e2e/playwright.config.ts +1 -1
  37. package/corpus/templates/design/actions/edit-design.ts +66 -27
  38. package/corpus/templates/design/changelog/2026-07-25-design-edits-retry-concurrent-changes.md +6 -0
  39. package/corpus/templates/design/server/source-workspace.ts +5 -1
  40. package/corpus/templates/forms/.agents/skills/form-responses/SKILL.md +6 -4
  41. package/corpus/templates/forms/actions/create-form.ts +5 -1
  42. package/corpus/templates/forms/actions/export-responses.ts +22 -11
  43. package/corpus/templates/forms/actions/update-form.ts +5 -1
  44. package/corpus/templates/forms/changelog/2026-07-25-fields-can-be-created-without-ids.md +6 -0
  45. package/corpus/templates/forms/changelog/2026-07-25-response-exports-use-file-storage.md +6 -0
  46. package/corpus/templates/forms/server/lib/validate-fields.ts +43 -0
  47. package/corpus/templates/slides/actions/extract-pdf.ts +4 -4
  48. package/corpus/templates/slides/actions/import-file.ts +3 -4
  49. package/corpus/templates/slides/changelog/2026-07-25-gemini-image-models-updated.md +6 -0
  50. package/corpus/templates/slides/changelog/2026-07-25-pdf-imports-tolerate-missing-canvas.md +6 -0
  51. package/corpus/templates/slides/server/handlers/image-providers/gemini.ts +5 -2
  52. package/corpus/templates/slides/server/lib/pdf-parse-setup.ts +150 -0
  53. package/dist/agent/production-agent.d.ts.map +1 -1
  54. package/dist/agent/production-agent.js +63 -1
  55. package/dist/agent/production-agent.js.map +1 -1
  56. package/dist/agent/run-loop-with-resume.d.ts +7 -1
  57. package/dist/agent/run-loop-with-resume.d.ts.map +1 -1
  58. package/dist/agent/run-loop-with-resume.js +7 -0
  59. package/dist/agent/run-loop-with-resume.js.map +1 -1
  60. package/dist/agent/run-store.d.ts.map +1 -1
  61. package/dist/agent/run-store.js +49 -0
  62. package/dist/agent/run-store.js.map +1 -1
  63. package/dist/collab/routes.d.ts +1 -1
  64. package/dist/collab/struct-routes.d.ts +1 -1
  65. package/dist/file-upload/actions/upload-image.d.ts +2 -2
  66. package/dist/integrations/a2a-continuation-processor.d.ts +14 -0
  67. package/dist/integrations/a2a-continuation-processor.d.ts.map +1 -1
  68. package/dist/integrations/a2a-continuation-processor.js +134 -1
  69. package/dist/integrations/a2a-continuation-processor.js.map +1 -1
  70. package/dist/integrations/a2a-continuations-store.d.ts +23 -0
  71. package/dist/integrations/a2a-continuations-store.d.ts.map +1 -1
  72. package/dist/integrations/a2a-continuations-store.js +99 -13
  73. package/dist/integrations/a2a-continuations-store.js.map +1 -1
  74. package/dist/integrations/integration-campaign-recovery.d.ts +16 -0
  75. package/dist/integrations/integration-campaign-recovery.d.ts.map +1 -0
  76. package/dist/integrations/integration-campaign-recovery.js +94 -0
  77. package/dist/integrations/integration-campaign-recovery.js.map +1 -0
  78. package/dist/integrations/integration-campaigns-store.d.ts +121 -0
  79. package/dist/integrations/integration-campaigns-store.d.ts.map +1 -0
  80. package/dist/integrations/integration-campaigns-store.js +784 -0
  81. package/dist/integrations/integration-campaigns-store.js.map +1 -0
  82. package/dist/integrations/integration-durable-dispatch.d.ts +3 -0
  83. package/dist/integrations/integration-durable-dispatch.d.ts.map +1 -1
  84. package/dist/integrations/integration-durable-dispatch.js +22 -3
  85. package/dist/integrations/integration-durable-dispatch.js.map +1 -1
  86. package/dist/integrations/pending-tasks-retry-job.d.ts.map +1 -1
  87. package/dist/integrations/pending-tasks-retry-job.js +6 -0
  88. package/dist/integrations/pending-tasks-retry-job.js.map +1 -1
  89. package/dist/integrations/plugin.d.ts.map +1 -1
  90. package/dist/integrations/plugin.js +325 -18
  91. package/dist/integrations/plugin.js.map +1 -1
  92. package/dist/integrations/webhook-handler.d.ts +16 -1
  93. package/dist/integrations/webhook-handler.d.ts.map +1 -1
  94. package/dist/integrations/webhook-handler.js +368 -13
  95. package/dist/integrations/webhook-handler.js.map +1 -1
  96. package/dist/observability/routes.d.ts +1 -1
  97. package/dist/provider-api/actions/custom-provider-registration.d.ts +14 -14
  98. package/dist/provider-api/actions/provider-api.d.ts +8 -8
  99. package/dist/secrets/routes.d.ts +9 -9
  100. package/dist/server/builder-design-systems.js +1 -1
  101. package/dist/server/builder-design-systems.js.map +1 -1
  102. package/package.json +3 -3
  103. package/src/agent/production-agent.ts +65 -1
  104. package/src/agent/run-loop-with-resume.ts +12 -0
  105. package/src/agent/run-store.ts +61 -1
  106. package/src/integrations/a2a-continuation-processor.ts +182 -0
  107. package/src/integrations/a2a-continuations-store.ts +122 -12
  108. package/src/integrations/integration-campaign-recovery.ts +127 -0
  109. package/src/integrations/integration-campaigns-store.ts +1039 -0
  110. package/src/integrations/integration-durable-dispatch.ts +28 -3
  111. package/src/integrations/pending-tasks-retry-job.ts +8 -0
  112. package/src/integrations/plugin.ts +453 -35
  113. package/src/integrations/webhook-handler.ts +469 -11
  114. package/src/server/builder-design-systems.ts +1 -1
@@ -31,6 +31,7 @@ import {
31
31
  engineToProvider,
32
32
  type ActionEntry,
33
33
  } from "../agent/production-agent.js";
34
+ import { appendDurableContinuationContext } from "../agent/run-loop-with-resume.js";
34
35
  import { startRun, type ActiveRun } from "../agent/run-manager.js";
35
36
  import {
36
37
  buildCurrentTimeUserContext,
@@ -42,6 +43,7 @@ import {
42
43
  threadDataToEngineMessages,
43
44
  } from "../agent/thread-data-builder.js";
44
45
  import { attachToolSearch } from "../agent/tool-search.js";
46
+ import type { ContinuationReason } from "../agent/types.js";
45
47
  import {
46
48
  createThread,
47
49
  getThread,
@@ -58,10 +60,22 @@ import {
58
60
  import { runWithRequestContext } from "../server/request-context.js";
59
61
  import { normalizeReasoningEffortForRequest } from "../shared/reasoning-effort.js";
60
62
  import { A2A_CONTINUATION_QUEUED_MARKER } from "./a2a-continuation-marker.js";
63
+ import { hasActiveA2AContinuationsForIntegrationTask } from "./a2a-continuations-store.js";
61
64
  import {
62
65
  clearIntegrationAwaitingInput,
63
66
  setIntegrationAwaitingInput,
64
67
  } from "./awaiting-input-store.js";
68
+ import {
69
+ claimIntegrationCampaignDeliveryForTask,
70
+ claimIntegrationCampaign,
71
+ completeIntegrationCampaignTask,
72
+ createIntegrationCampaign,
73
+ failIntegrationCampaign,
74
+ heartbeatIntegrationCampaign,
75
+ scheduleNextIntegrationCampaign,
76
+ waitForA2AIntegrationCampaign,
77
+ type IntegrationCampaign,
78
+ } from "./integration-campaigns-store.js";
65
79
  import {
66
80
  dispatchPendingIntegrationTask,
67
81
  integrationDispatchScopeValue,
@@ -80,6 +94,7 @@ import type {
80
94
  IncomingMessage,
81
95
  OutgoingMessage,
82
96
  PlatformDeliveryReceipt,
97
+ PlatformRunProgressRef,
83
98
  } from "./types.js";
84
99
  import {
85
100
  listIntegrationUsageBudgets,
@@ -96,6 +111,12 @@ const EMPTY_INTEGRATION_RESPONSE_MESSAGE =
96
111
  const CUTOFF_INTEGRATION_RESPONSE_MESSAGE =
97
112
  "I ran out of time on this one before I could write up an answer — it needed more research than a single run allows. " +
98
113
  "Open the thread in Dispatch to see what I gathered, or ask me again in smaller pieces (one source at a time works best).";
114
+ const INTEGRATION_CAMPAIGN_LEASE_MS = 16 * 60_000;
115
+ const INTEGRATION_CAMPAIGN_MAX_CHUNKS = 4;
116
+ const INTEGRATION_CAMPAIGN_A2A_CHECK_MS = 30_000;
117
+ // Keep a lost handoff plus the one-minute sweep inside the two-minute messaging
118
+ // target without shortening general background-run budgets.
119
+ const INTEGRATION_CAMPAIGN_NO_PROGRESS_TIMEOUT_MS = 45_000;
99
120
 
100
121
  /**
101
122
  * True when the run stopped at a continuation boundary rather than finishing:
@@ -108,6 +129,50 @@ function endedAtContinuationBoundary(run: ActiveRun): boolean {
108
129
  return run.events.some((runEvent) => runEvent.event.type === "auto_continue");
109
130
  }
110
131
 
132
+ function continuationBoundaryReason(run: ActiveRun): ContinuationReason {
133
+ for (let index = run.events.length - 1; index >= 0; index -= 1) {
134
+ const runEvent = run.events[index];
135
+ if (!runEvent) continue;
136
+ if (runEvent.event.type === "auto_continue") return runEvent.event.reason;
137
+ }
138
+ return "run_timeout";
139
+ }
140
+
141
+ function checkpointContinuationReason(
142
+ checkpoint: string | null | undefined,
143
+ ): ContinuationReason {
144
+ if (!checkpoint) return "run_timeout";
145
+ try {
146
+ const reason = JSON.parse(checkpoint).reason;
147
+ if (
148
+ reason === "run_timeout" ||
149
+ reason === "loop_limit" ||
150
+ reason === "max_tokens" ||
151
+ reason === "no_progress" ||
152
+ reason === "stream_ended" ||
153
+ reason === "gateway_timeout" ||
154
+ reason === "network_interrupted"
155
+ ) {
156
+ return reason;
157
+ }
158
+ } catch {}
159
+ return "run_timeout";
160
+ }
161
+
162
+ function parseIntegrationProgressRef(
163
+ value: string,
164
+ ): PlatformRunProgressRef | null {
165
+ try {
166
+ const parsed = JSON.parse(value) as Partial<PlatformRunProgressRef>;
167
+ return typeof parsed.kind === "string" &&
168
+ typeof parsed.streamTs === "string"
169
+ ? { kind: parsed.kind, streamTs: parsed.streamTs }
170
+ : null;
171
+ } catch {
172
+ return null;
173
+ }
174
+ }
175
+
111
176
  type ToolDoneEvent = {
112
177
  type: "tool_done";
113
178
  tool: string;
@@ -127,14 +192,24 @@ export type IntegrationResponseDeliveryTaskPayload = {
127
192
  deliveryReceipt?: PlatformDeliveryReceipt;
128
193
  deliveredAt?: string;
129
194
  artifacts?: A2AArtifactIdentity[];
195
+ campaignTerminalStatus?: "completed" | "failed";
196
+ awaitingA2ACompletion?: true;
130
197
  };
131
198
 
132
199
  export type ProcessIntegrationTaskResult =
133
200
  | { status: "completed" }
201
+ | { status: "campaign-pending" | "campaign-active" }
202
+ | { status: "campaign-failed" }
134
203
  | {
135
204
  status: "delivery-pending";
136
205
  payload: IntegrationResponseDeliveryTaskPayload;
137
206
  errorMessage: string;
207
+ campaignLease?: {
208
+ campaignId: string;
209
+ runId: string;
210
+ leaseToken: string;
211
+ campaignStatus: "completed" | "failed" | "waiting-a2a";
212
+ };
138
213
  };
139
214
 
140
215
  /**
@@ -575,6 +650,10 @@ export function resolveBaseUrl(event: H3Event): string {
575
650
  export async function processIntegrationTask(
576
651
  task: PendingTask,
577
652
  options: WebhookHandlerOptions,
653
+ campaignOptions?: {
654
+ enabled?: boolean;
655
+ continuationInvocation?: boolean;
656
+ },
578
657
  ): Promise<ProcessIntegrationTaskResult> {
579
658
  const parsed = JSON.parse(task.payload) as {
580
659
  incoming: IncomingMessage;
@@ -582,7 +661,9 @@ export async function processIntegrationTask(
582
661
  principalType?: "user" | "service";
583
662
  };
584
663
 
585
- await recordInboundIntegrationAudit(task, parsed.incoming);
664
+ if (!campaignOptions?.continuationInvocation) {
665
+ await recordInboundIntegrationAudit(task, parsed.incoming);
666
+ }
586
667
 
587
668
  return processIncomingMessage(parsed.incoming, options, {
588
669
  taskId: task.id,
@@ -590,6 +671,7 @@ export async function processIntegrationTask(
590
671
  placeholderRef: parsed.placeholderRef,
591
672
  orgId: task.orgId ?? undefined,
592
673
  principalType: parsed.principalType ?? options.principalType ?? "user",
674
+ durableCampaign: campaignOptions?.enabled === true,
593
675
  });
594
676
  }
595
677
 
@@ -643,6 +725,7 @@ async function processIncomingMessage(
643
725
  placeholderRef?: string;
644
726
  orgId?: string;
645
727
  principalType?: "user" | "service";
728
+ durableCampaign?: boolean;
646
729
  } = {},
647
730
  ): Promise<ProcessIntegrationTaskResult> {
648
731
  const {
@@ -831,6 +914,195 @@ async function processIncomingMessage(
831
914
  );
832
915
  });
833
916
  }
917
+
918
+ let campaign:
919
+ | {
920
+ row: IntegrationCampaign;
921
+ runId: string;
922
+ leaseToken: string;
923
+ }
924
+ | undefined;
925
+ if (opts.durableCampaign && opts.taskId) {
926
+ const created = await createIntegrationCampaign({
927
+ integrationTaskId: opts.taskId,
928
+ threadId,
929
+ turnId: `integration-turn-${opts.taskId}`,
930
+ });
931
+ if (created.status === "completed") {
932
+ await releaseApplicableIntegrationBudgets(
933
+ budgetReservations.reservations,
934
+ );
935
+ return { status: "completed" };
936
+ }
937
+ if (created.status === "failed") {
938
+ await releaseApplicableIntegrationBudgets(
939
+ budgetReservations.reservations,
940
+ );
941
+ return { status: "campaign-failed" };
942
+ }
943
+ const runId = `integration-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
944
+ const leaseToken = crypto.randomUUID();
945
+ const claimed = await claimIntegrationCampaign(created.id, {
946
+ runId,
947
+ leaseToken,
948
+ leaseDurationMs: INTEGRATION_CAMPAIGN_LEASE_MS,
949
+ maxChunks: INTEGRATION_CAMPAIGN_MAX_CHUNKS,
950
+ });
951
+ if (claimed.kind === "chunk-limit") {
952
+ const deliveryRunId = `integration-delivery-${crypto.randomUUID()}`;
953
+ const deliveryLeaseToken = crypto.randomUUID();
954
+ const deliveryCampaign = await claimIntegrationCampaignDeliveryForTask(
955
+ opts.taskId,
956
+ {
957
+ runId: deliveryRunId,
958
+ leaseToken: deliveryLeaseToken,
959
+ leaseDurationMs: INTEGRATION_CAMPAIGN_LEASE_MS,
960
+ },
961
+ );
962
+ if (!deliveryCampaign) {
963
+ await releaseApplicableIntegrationBudgets(
964
+ budgetReservations.reservations,
965
+ );
966
+ return { status: "campaign-active" };
967
+ }
968
+ const exhaustedProgressRef = deliveryCampaign.progressRef
969
+ ? parseIntegrationProgressRef(deliveryCampaign.progressRef)
970
+ : null;
971
+ const exhaustedProgress = exhaustedProgressRef
972
+ ? await adapter
973
+ .resumeRunProgress?.(incoming, exhaustedProgressRef)
974
+ .catch(() => null)
975
+ : null;
976
+ const exhaustedMessage = adapter.formatAgentResponse(
977
+ "I couldn't safely finish this request after several continuation attempts. No completed write will be replayed.",
978
+ );
979
+ let deliveryPayload: IntegrationResponseDeliveryTaskPayload = {
980
+ kind: "response-delivery",
981
+ incoming,
982
+ message: exhaustedMessage,
983
+ ...(opts.placeholderRef ? { placeholderRef: opts.placeholderRef } : {}),
984
+ internalThreadId: threadId,
985
+ campaignTerminalStatus: "failed",
986
+ ...buildDeliveryHistoryMessageIds(incoming),
987
+ };
988
+ try {
989
+ await stageTaskDeliveryPayload(
990
+ opts.taskId,
991
+ JSON.stringify(deliveryPayload),
992
+ );
993
+ let receipt: void | PlatformDeliveryReceipt;
994
+ if (exhaustedProgress) {
995
+ try {
996
+ receipt = await exhaustedProgress.complete(exhaustedMessage);
997
+ } catch {
998
+ receipt = await adapter.sendResponse(exhaustedMessage, incoming, {
999
+ placeholderRef: opts.placeholderRef,
1000
+ });
1001
+ }
1002
+ } else {
1003
+ receipt = await adapter.sendResponse(exhaustedMessage, incoming, {
1004
+ placeholderRef: opts.placeholderRef,
1005
+ });
1006
+ }
1007
+ if (receipt?.status !== "delivered") {
1008
+ throw new Error(
1009
+ `${incoming.platform} exhaustion response completed without delivery proof`,
1010
+ );
1011
+ }
1012
+ deliveryPayload = {
1013
+ ...deliveryPayload,
1014
+ deliveryReceipt: receipt,
1015
+ deliveredAt: new Date().toISOString(),
1016
+ };
1017
+ await stageTaskDeliveryPayload(
1018
+ opts.taskId,
1019
+ JSON.stringify(deliveryPayload),
1020
+ );
1021
+ await recordIntegrationResponseDelivery(deliveryPayload, receipt);
1022
+ const terminalized = await failIntegrationCampaign(
1023
+ deliveryCampaign.id,
1024
+ {
1025
+ runId: deliveryRunId,
1026
+ leaseToken: deliveryLeaseToken,
1027
+ errorMessage: "Integration campaign exhausted its chunk limit",
1028
+ },
1029
+ );
1030
+ if (!terminalized) {
1031
+ await releaseApplicableIntegrationBudgets(
1032
+ budgetReservations.reservations,
1033
+ );
1034
+ return { status: "campaign-active" };
1035
+ }
1036
+ } catch (error) {
1037
+ await releaseApplicableIntegrationBudgets(
1038
+ budgetReservations.reservations,
1039
+ );
1040
+ return {
1041
+ status: "delivery-pending",
1042
+ payload: deliveryPayload,
1043
+ campaignLease: {
1044
+ campaignId: deliveryCampaign.id,
1045
+ runId: deliveryRunId,
1046
+ leaseToken: deliveryLeaseToken,
1047
+ campaignStatus: "failed",
1048
+ },
1049
+ errorMessage:
1050
+ error instanceof Error
1051
+ ? error.message.slice(0, 1000)
1052
+ : "Integration exhaustion response delivery failed",
1053
+ };
1054
+ }
1055
+ await releaseApplicableIntegrationBudgets(
1056
+ budgetReservations.reservations,
1057
+ );
1058
+ return { status: "campaign-failed" };
1059
+ }
1060
+ if (claimed.kind !== "claimed") {
1061
+ await releaseApplicableIntegrationBudgets(
1062
+ budgetReservations.reservations,
1063
+ );
1064
+ return { status: "campaign-active" };
1065
+ }
1066
+ campaign = { row: claimed.campaign, runId, leaseToken };
1067
+ if (campaign.row.checkpoint) {
1068
+ let waitingForA2A = false;
1069
+ try {
1070
+ waitingForA2A =
1071
+ JSON.parse(campaign.row.checkpoint).waitingForA2A === true;
1072
+ } catch {}
1073
+ if (waitingForA2A) {
1074
+ const activeA2A = await hasActiveA2AContinuationsForIntegrationTask(
1075
+ opts.taskId,
1076
+ );
1077
+ if (activeA2A) {
1078
+ const waiting = await waitForA2AIntegrationCampaign(campaign.row.id, {
1079
+ runId: campaign.runId,
1080
+ leaseToken: campaign.leaseToken,
1081
+ nextRunAt: Date.now() + INTEGRATION_CAMPAIGN_A2A_CHECK_MS,
1082
+ progressRef: campaign.row.progressRef,
1083
+ });
1084
+ await releaseApplicableIntegrationBudgets(
1085
+ budgetReservations.reservations,
1086
+ );
1087
+ return {
1088
+ status: waiting ? "campaign-pending" : "campaign-active",
1089
+ };
1090
+ }
1091
+ const completed = await completeIntegrationCampaignTask(
1092
+ campaign.row.id,
1093
+ {
1094
+ integrationTaskId: opts.taskId!,
1095
+ runId: campaign.runId,
1096
+ leaseToken: campaign.leaseToken,
1097
+ },
1098
+ );
1099
+ await releaseApplicableIntegrationBudgets(
1100
+ budgetReservations.reservations,
1101
+ );
1102
+ return { status: completed ? "completed" : "campaign-active" };
1103
+ }
1104
+ }
1105
+ }
834
1106
  const existingMessages: EngineMessage[] = [];
835
1107
  if (thread?.threadData) {
836
1108
  existingMessages.push(...threadDataToEngineMessages(thread.threadData));
@@ -861,15 +1133,21 @@ async function processIncomingMessage(
861
1133
  // Precise current time rides the engine-facing user message (not the cached
862
1134
  // system-prompt prefix, and not the persisted thread text) — the runtime
863
1135
  // context appended to the system prompt is day-granular only.
864
- const messages: EngineMessage[] = [
865
- ...existingMessages,
866
- {
1136
+ const messages: EngineMessage[] = [...existingMessages];
1137
+ if (campaign && campaign.row.chunkCount > 1) {
1138
+ await appendDurableContinuationContext(
1139
+ messages,
1140
+ checkpointContinuationReason(campaign.row.checkpoint),
1141
+ threadId,
1142
+ );
1143
+ } else {
1144
+ messages.push({
867
1145
  role: "user",
868
1146
  content: [
869
1147
  { type: "text", text: userText + buildCurrentTimeUserContext() },
870
1148
  ],
871
- },
872
- ];
1149
+ });
1150
+ }
873
1151
 
874
1152
  // Run agent loop via startRun, wrapped in a request context so that
875
1153
  // tools (especially call-agent) can resolve the caller's org for org-scoped
@@ -897,8 +1175,28 @@ async function processIncomingMessage(
897
1175
  throw error;
898
1176
  }
899
1177
 
900
- const runId = `integration-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
901
- const progress = await adapter.startRunProgress?.(incoming).catch(() => null);
1178
+ const runId =
1179
+ campaign?.runId ??
1180
+ `integration-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
1181
+ const storedProgressRef = campaign?.row.progressRef
1182
+ ? parseIntegrationProgressRef(campaign.row.progressRef)
1183
+ : null;
1184
+ const progress = storedProgressRef
1185
+ ? await adapter
1186
+ .resumeRunProgress?.(incoming, storedProgressRef)
1187
+ .catch(() => null)
1188
+ : await adapter.startRunProgress?.(incoming).catch(() => null);
1189
+ if (campaign && campaign.row.chunkCount > 1 && progress) {
1190
+ await Promise.resolve(
1191
+ progress.onEvent({
1192
+ type: "agent_call_progress",
1193
+ agent: "Agent Native",
1194
+ state: "working",
1195
+ elapsedSeconds: 0,
1196
+ detail: "Continuing in the background",
1197
+ }),
1198
+ ).catch(() => {});
1199
+ }
902
1200
  let usage: Awaited<ReturnType<typeof runAgentLoop>> | null = null;
903
1201
  let budgetsSettled = false;
904
1202
 
@@ -1023,7 +1321,28 @@ async function processIncomingMessage(
1023
1321
  | undefined;
1024
1322
  let outcome: ProcessIntegrationTaskResult = { status: "completed" };
1025
1323
  try {
1324
+ if (campaign) {
1325
+ const stillOwned = await heartbeatIntegrationCampaign(
1326
+ campaign.row.id,
1327
+ {
1328
+ runId: campaign.runId,
1329
+ leaseToken: campaign.leaseToken,
1330
+ leaseDurationMs: INTEGRATION_CAMPAIGN_LEASE_MS,
1331
+ },
1332
+ );
1333
+ if (!stillOwned) {
1334
+ outcome = { status: "campaign-active" };
1335
+ return;
1336
+ }
1337
+ }
1026
1338
  queuedA2AContinuation = hasQueuedA2AContinuation(completedRun);
1339
+ const durableCampaignContinuation = Boolean(
1340
+ campaign &&
1341
+ campaign.row.chunkCount < INTEGRATION_CAMPAIGN_MAX_CHUNKS &&
1342
+ completedRun.status === "completed" &&
1343
+ endedAtContinuationBoundary(completedRun) &&
1344
+ !queuedA2AContinuation,
1345
+ );
1027
1346
  const slackInputRequest =
1028
1347
  incoming.platform === "slack"
1029
1348
  ? extractSlackInputRequest(completedRun)
@@ -1049,6 +1368,7 @@ async function processIncomingMessage(
1049
1368
  let suppressPlatformReply =
1050
1369
  queuedA2AContinuation &&
1051
1370
  isQueuedA2AContinuationDeferral(responseText);
1371
+ suppressPlatformReply ||= durableCampaignContinuation;
1052
1372
 
1053
1373
  // Compute trusted tool receipts before choosing the empty-answer
1054
1374
  // fallback. A completed write must not be reported as though nothing
@@ -1156,6 +1476,14 @@ async function processIncomingMessage(
1156
1476
  artifacts: extractA2AArtifactIdentities(toolResults, {
1157
1477
  persistedArtifactSecrets: artifactSecrets,
1158
1478
  }),
1479
+ ...(campaign && queuedA2AContinuation
1480
+ ? { awaitingA2ACompletion: true as const }
1481
+ : {}),
1482
+ ...(campaign &&
1483
+ !queuedA2AContinuation &&
1484
+ !durableCampaignContinuation
1485
+ ? { campaignTerminalStatus: "completed" as const }
1486
+ : {}),
1159
1487
  };
1160
1488
  if (opts.taskId) {
1161
1489
  await stageTaskDeliveryPayload(
@@ -1256,6 +1584,9 @@ async function processIncomingMessage(
1256
1584
  }
1257
1585
 
1258
1586
  // Persist thread data
1587
+ const historyMessageIds =
1588
+ stagedDeliveryPayload ??
1589
+ (campaign ? buildDeliveryHistoryMessageIds(incoming) : undefined);
1259
1590
  threadCheckpoint = await persistThreadData(
1260
1591
  threadId,
1261
1592
  incoming.text,
@@ -1263,8 +1594,10 @@ async function processIncomingMessage(
1263
1594
  thread,
1264
1595
  deliveredResponse,
1265
1596
  toolResults,
1266
- stagedDeliveryPayload,
1597
+ historyMessageIds,
1267
1598
  artifactSecrets,
1599
+ Boolean(campaign),
1600
+ !durableCampaignContinuation,
1268
1601
  );
1269
1602
  if (outgoingForDelivery && stagedDeliveryPayload) {
1270
1603
  if (!threadCheckpoint) {
@@ -1299,6 +1632,69 @@ async function processIncomingMessage(
1299
1632
  usage,
1300
1633
  );
1301
1634
  budgetsSettled = true;
1635
+ if (campaign && queuedA2AContinuation) {
1636
+ const waiting = await waitForA2AIntegrationCampaign(
1637
+ campaign.row.id,
1638
+ {
1639
+ runId: campaign.runId,
1640
+ leaseToken: campaign.leaseToken,
1641
+ nextRunAt: Date.now() + INTEGRATION_CAMPAIGN_A2A_CHECK_MS,
1642
+ progressRef: progress?.ref
1643
+ ? JSON.stringify(progress.ref)
1644
+ : undefined,
1645
+ },
1646
+ );
1647
+ if (!waiting) {
1648
+ throw new Error("Integration campaign lease was superseded");
1649
+ }
1650
+ outcome = { status: "campaign-pending" };
1651
+ } else if (campaign && durableCampaignContinuation) {
1652
+ const scheduled = await scheduleNextIntegrationCampaign(
1653
+ campaign.row.id,
1654
+ {
1655
+ runId: campaign.runId,
1656
+ leaseToken: campaign.leaseToken,
1657
+ nextRunAt: Date.now(),
1658
+ progressRef: progress?.ref
1659
+ ? JSON.stringify(progress.ref)
1660
+ : undefined,
1661
+ checkpoint: JSON.stringify({
1662
+ reason: continuationBoundaryReason(completedRun),
1663
+ threadId,
1664
+ ...(threadCheckpoint?.assistantMessageId
1665
+ ? {
1666
+ assistantMessageId: threadCheckpoint.assistantMessageId,
1667
+ }
1668
+ : {}),
1669
+ }),
1670
+ },
1671
+ );
1672
+ if (!scheduled) {
1673
+ throw new Error("Integration campaign lease was superseded");
1674
+ }
1675
+ await dispatchPendingIntegrationTask({
1676
+ taskId: opts.taskId!,
1677
+ task: {
1678
+ platform: incoming.platform,
1679
+ externalThreadId: incoming.externalThreadId,
1680
+ platformContext: incoming.platformContext,
1681
+ },
1682
+ campaignContinuation: true,
1683
+ });
1684
+ outcome = { status: "campaign-pending" };
1685
+ } else if (campaign) {
1686
+ const completed = await completeIntegrationCampaignTask(
1687
+ campaign.row.id,
1688
+ {
1689
+ integrationTaskId: opts.taskId!,
1690
+ runId: campaign.runId,
1691
+ leaseToken: campaign.leaseToken,
1692
+ },
1693
+ );
1694
+ if (!completed) {
1695
+ throw new Error("Integration campaign lease was superseded");
1696
+ }
1697
+ }
1302
1698
  } catch (err) {
1303
1699
  console.error(
1304
1700
  `[integrations] Error sending response to ${incoming.platform}:`,
@@ -1317,8 +1713,12 @@ async function processIncomingMessage(
1317
1713
  thread,
1318
1714
  undefined,
1319
1715
  collectToolResultSummaries(completedRun),
1320
- stagedDeliveryPayload,
1716
+ stagedDeliveryPayload ??
1717
+ (campaign
1718
+ ? buildDeliveryHistoryMessageIds(incoming)
1719
+ : undefined),
1321
1720
  artifactSecrets,
1721
+ Boolean(campaign),
1322
1722
  );
1323
1723
  }
1324
1724
  if (usage) {
@@ -1356,10 +1756,31 @@ async function processIncomingMessage(
1356
1756
  ? { assistantMessageId: threadCheckpoint.assistantMessageId }
1357
1757
  : {}),
1358
1758
  },
1759
+ ...(campaign &&
1760
+ (stagedDeliveryPayload?.campaignTerminalStatus ||
1761
+ stagedDeliveryPayload?.awaitingA2ACompletion)
1762
+ ? {
1763
+ campaignLease: {
1764
+ campaignId: campaign.row.id,
1765
+ runId: campaign.runId,
1766
+ leaseToken: campaign.leaseToken,
1767
+ campaignStatus:
1768
+ stagedDeliveryPayload.campaignTerminalStatus ??
1769
+ ("waiting-a2a" as const),
1770
+ },
1771
+ }
1772
+ : {}),
1359
1773
  errorMessage,
1360
1774
  };
1361
1775
  return;
1362
1776
  }
1777
+ if (campaign) {
1778
+ // A campaign-owned, no-delivery path must never fall through to
1779
+ // the default completed outcome. Its lease/checkpoint recovery
1780
+ // remains the sole owner of this logical turn.
1781
+ outcome = { status: "campaign-active" };
1782
+ return;
1783
+ }
1363
1784
  // A queued continuation owns the final platform response. Later
1364
1785
  // bookkeeping failures must not close its stream with a false failure.
1365
1786
  if (queuedA2AContinuation) return;
@@ -1406,6 +1827,12 @@ async function processIncomingMessage(
1406
1827
  {
1407
1828
  useHostedSoftTimeoutDefault: true,
1408
1829
  backgroundFunction: isInBackgroundFunctionRuntime(),
1830
+ ...(campaign
1831
+ ? {
1832
+ turnId: campaign.row.turnId,
1833
+ noProgressTimeoutMs: INTEGRATION_CAMPAIGN_NO_PROGRESS_TIMEOUT_MS,
1834
+ }
1835
+ : {}),
1409
1836
  },
1410
1837
  );
1411
1838
  });
@@ -1815,6 +2242,8 @@ async function persistThreadData(
1815
2242
  "userMessageId" | "assistantMessageId"
1816
2243
  >,
1817
2244
  artifactSecrets: readonly string[] = [],
2245
+ mergeRunContent = false,
2246
+ deliveryAttempted = true,
1818
2247
  ): Promise<{ userMessageId: string; assistantMessageId?: string } | undefined> {
1819
2248
  try {
1820
2249
  let repo: any;
@@ -1846,9 +2275,38 @@ async function persistThreadData(
1846
2275
  (message: any) => message?.id === builtAssistantMsg.id,
1847
2276
  )
1848
2277
  : undefined;
2278
+ if (builtAssistantMsg && mergeRunContent) {
2279
+ builtAssistantMsg.metadata.integrationRunIds = [completedRun.runId];
2280
+ }
2281
+ if (mergeRunContent && existingAssistantMsg && builtAssistantMsg) {
2282
+ const previousRunIds = Array.isArray(
2283
+ existingAssistantMsg.metadata?.integrationRunIds,
2284
+ )
2285
+ ? existingAssistantMsg.metadata.integrationRunIds.filter(
2286
+ (value: unknown): value is string => typeof value === "string",
2287
+ )
2288
+ : [];
2289
+ if (!previousRunIds.includes(completedRun.runId)) {
2290
+ existingAssistantMsg.content = [
2291
+ ...(Array.isArray(existingAssistantMsg.content)
2292
+ ? existingAssistantMsg.content
2293
+ : []),
2294
+ ...(Array.isArray(builtAssistantMsg.content)
2295
+ ? builtAssistantMsg.content
2296
+ : []),
2297
+ ];
2298
+ existingAssistantMsg.metadata = {
2299
+ ...existingAssistantMsg.metadata,
2300
+ ...builtAssistantMsg.metadata,
2301
+ integrationRunIds: [...previousRunIds, completedRun.runId],
2302
+ };
2303
+ }
2304
+ }
1849
2305
  const assistantMsg = existingAssistantMsg ?? builtAssistantMsg;
1850
- if (assistantMsg) {
2306
+ if (assistantMsg && deliveryAttempted) {
1851
2307
  assistantMsg.metadata.integrationDeliveryAttempted = true;
2308
+ }
2309
+ if (assistantMsg) {
1852
2310
  const artifactIdentities = extractA2AArtifactIdentities(toolResults, {
1853
2311
  persistedArtifactSecrets: artifactSecrets,
1854
2312
  });
@@ -140,7 +140,7 @@ function trimTrailingSlash(value: string): string {
140
140
  export function getBuilderDesignSystemsBaseUrl(): string {
141
141
  return (
142
142
  process.env.BUILDER_DESIGN_SYSTEMS_BASE_URL ||
143
- `${trimTrailingSlash(getBuilderProxyOrigin())}/design-systems/v1`
143
+ `${trimTrailingSlash(getBuilderProxyOrigin())}/agent-native/design-systems/v1`
144
144
  );
145
145
  }
146
146