@agent-native/core 0.32.2 → 0.32.17

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 (240) hide show
  1. package/README.md +3 -1
  2. package/dist/agent/run-store.d.ts.map +1 -1
  3. package/dist/agent/run-store.js +48 -10
  4. package/dist/agent/run-store.js.map +1 -1
  5. package/dist/agent/thread-data-builder.d.ts +12 -0
  6. package/dist/agent/thread-data-builder.d.ts.map +1 -1
  7. package/dist/agent/thread-data-builder.js +104 -6
  8. package/dist/agent/thread-data-builder.js.map +1 -1
  9. package/dist/cli/app-skill.js +2 -2
  10. package/dist/cli/app-skill.js.map +1 -1
  11. package/dist/cli/code-agent-executor.d.ts.map +1 -1
  12. package/dist/cli/code-agent-executor.js +6 -1
  13. package/dist/cli/code-agent-executor.js.map +1 -1
  14. package/dist/cli/code-agent-output-smoother.d.ts +7 -0
  15. package/dist/cli/code-agent-output-smoother.d.ts.map +1 -0
  16. package/dist/cli/code-agent-output-smoother.js +111 -0
  17. package/dist/cli/code-agent-output-smoother.js.map +1 -0
  18. package/dist/cli/connect.d.ts.map +1 -1
  19. package/dist/cli/connect.js +5 -0
  20. package/dist/cli/connect.js.map +1 -1
  21. package/dist/cli/migrate.d.ts.map +1 -1
  22. package/dist/cli/migrate.js +17 -42
  23. package/dist/cli/migrate.js.map +1 -1
  24. package/dist/cli/skills.d.ts +23 -2
  25. package/dist/cli/skills.d.ts.map +1 -1
  26. package/dist/cli/skills.js +405 -41
  27. package/dist/cli/skills.js.map +1 -1
  28. package/dist/cli/templates-meta.d.ts.map +1 -1
  29. package/dist/cli/templates-meta.js +7 -105
  30. package/dist/cli/templates-meta.js.map +1 -1
  31. package/dist/client/AgentPanel.d.ts.map +1 -1
  32. package/dist/client/AgentPanel.js +41 -7
  33. package/dist/client/AgentPanel.js.map +1 -1
  34. package/dist/client/AgentTaskCard.d.ts.map +1 -1
  35. package/dist/client/AgentTaskCard.js +0 -28
  36. package/dist/client/AgentTaskCard.js.map +1 -1
  37. package/dist/client/AssistantChat.d.ts +8 -23
  38. package/dist/client/AssistantChat.d.ts.map +1 -1
  39. package/dist/client/AssistantChat.js +359 -205
  40. package/dist/client/AssistantChat.js.map +1 -1
  41. package/dist/client/MultiTabAssistantChat.d.ts.map +1 -1
  42. package/dist/client/MultiTabAssistantChat.js +254 -14
  43. package/dist/client/MultiTabAssistantChat.js.map +1 -1
  44. package/dist/client/agent-chat-adapter.d.ts.map +1 -1
  45. package/dist/client/agent-chat-adapter.js +14 -9
  46. package/dist/client/agent-chat-adapter.js.map +1 -1
  47. package/dist/client/agent-chat.d.ts +24 -0
  48. package/dist/client/agent-chat.d.ts.map +1 -1
  49. package/dist/client/agent-chat.js +73 -0
  50. package/dist/client/agent-chat.js.map +1 -1
  51. package/dist/client/assistant-ui-recovery.d.ts +34 -0
  52. package/dist/client/assistant-ui-recovery.d.ts.map +1 -0
  53. package/dist/client/assistant-ui-recovery.js +122 -0
  54. package/dist/client/assistant-ui-recovery.js.map +1 -0
  55. package/dist/client/composer/PromptComposer.d.ts.map +1 -1
  56. package/dist/client/composer/PromptComposer.js +7 -1
  57. package/dist/client/composer/PromptComposer.js.map +1 -1
  58. package/dist/client/composer/TiptapComposer.d.ts +7 -1
  59. package/dist/client/composer/TiptapComposer.d.ts.map +1 -1
  60. package/dist/client/composer/TiptapComposer.js +22 -2
  61. package/dist/client/composer/TiptapComposer.js.map +1 -1
  62. package/dist/client/frame-protocol.d.ts +6 -2
  63. package/dist/client/frame-protocol.d.ts.map +1 -1
  64. package/dist/client/frame-protocol.js.map +1 -1
  65. package/dist/client/index.d.ts +2 -1
  66. package/dist/client/index.d.ts.map +1 -1
  67. package/dist/client/index.js +2 -1
  68. package/dist/client/index.js.map +1 -1
  69. package/dist/client/org/OrgSwitcher.d.ts.map +1 -1
  70. package/dist/client/org/OrgSwitcher.js +2 -1
  71. package/dist/client/org/OrgSwitcher.js.map +1 -1
  72. package/dist/client/progress/RunsTray.d.ts +13 -3
  73. package/dist/client/progress/RunsTray.d.ts.map +1 -1
  74. package/dist/client/progress/RunsTray.js +105 -36
  75. package/dist/client/progress/RunsTray.js.map +1 -1
  76. package/dist/client/route-warmup.d.ts +61 -0
  77. package/dist/client/route-warmup.d.ts.map +1 -0
  78. package/dist/client/route-warmup.js +456 -0
  79. package/dist/client/route-warmup.js.map +1 -0
  80. package/dist/client/settings/SettingsPanel.d.ts.map +1 -1
  81. package/dist/client/settings/SettingsPanel.js +2 -1
  82. package/dist/client/settings/SettingsPanel.js.map +1 -1
  83. package/dist/client/settings/useBuilderStatus.d.ts +5 -0
  84. package/dist/client/settings/useBuilderStatus.d.ts.map +1 -1
  85. package/dist/client/settings/useBuilderStatus.js +10 -4
  86. package/dist/client/settings/useBuilderStatus.js.map +1 -1
  87. package/dist/client/use-action.d.ts +1 -0
  88. package/dist/client/use-action.d.ts.map +1 -1
  89. package/dist/client/use-action.js +22 -4
  90. package/dist/client/use-action.js.map +1 -1
  91. package/dist/code-agents/background-run.d.ts +2 -0
  92. package/dist/code-agents/background-run.d.ts.map +1 -1
  93. package/dist/code-agents/background-run.js.map +1 -1
  94. package/dist/db/client.d.ts +1 -1
  95. package/dist/db/client.d.ts.map +1 -1
  96. package/dist/db/client.js +25 -1
  97. package/dist/db/client.js.map +1 -1
  98. package/dist/deploy/build.d.ts +4 -0
  99. package/dist/deploy/build.d.ts.map +1 -1
  100. package/dist/deploy/build.js +171 -14
  101. package/dist/deploy/build.js.map +1 -1
  102. package/dist/deploy/immutable-assets.d.ts +1 -0
  103. package/dist/deploy/immutable-assets.d.ts.map +1 -1
  104. package/dist/deploy/immutable-assets.js +1 -0
  105. package/dist/deploy/immutable-assets.js.map +1 -1
  106. package/dist/index.browser.d.ts +1 -1
  107. package/dist/index.browser.d.ts.map +1 -1
  108. package/dist/index.browser.js +1 -1
  109. package/dist/index.browser.js.map +1 -1
  110. package/dist/index.d.ts +1 -1
  111. package/dist/index.d.ts.map +1 -1
  112. package/dist/index.js +1 -1
  113. package/dist/index.js.map +1 -1
  114. package/dist/mcp/connect-route.d.ts.map +1 -1
  115. package/dist/mcp/connect-route.js +118 -82
  116. package/dist/mcp/connect-route.js.map +1 -1
  117. package/dist/progress/routes.d.ts.map +1 -1
  118. package/dist/progress/routes.js +1 -0
  119. package/dist/progress/routes.js.map +1 -1
  120. package/dist/progress/store.d.ts +13 -0
  121. package/dist/progress/store.d.ts.map +1 -1
  122. package/dist/progress/store.js +18 -0
  123. package/dist/progress/store.js.map +1 -1
  124. package/dist/progress/types.d.ts +2 -0
  125. package/dist/progress/types.d.ts.map +1 -1
  126. package/dist/progress/types.js.map +1 -1
  127. package/dist/scripts/db/wipe-leaked-builder-keys.d.ts +2 -2
  128. package/dist/scripts/db/wipe-leaked-builder-keys.d.ts.map +1 -1
  129. package/dist/scripts/db/wipe-leaked-builder-keys.js +14 -3
  130. package/dist/scripts/db/wipe-leaked-builder-keys.js.map +1 -1
  131. package/dist/server/action-routes.d.ts +1 -0
  132. package/dist/server/action-routes.d.ts.map +1 -1
  133. package/dist/server/action-routes.js +36 -2
  134. package/dist/server/action-routes.js.map +1 -1
  135. package/dist/server/agent-chat-plugin.d.ts.map +1 -1
  136. package/dist/server/agent-chat-plugin.js +123 -25
  137. package/dist/server/agent-chat-plugin.js.map +1 -1
  138. package/dist/server/agent-discovery.d.ts.map +1 -1
  139. package/dist/server/agent-discovery.js +14 -1
  140. package/dist/server/agent-discovery.js.map +1 -1
  141. package/dist/server/agent-teams-run-queue.d.ts +80 -0
  142. package/dist/server/agent-teams-run-queue.d.ts.map +1 -0
  143. package/dist/server/agent-teams-run-queue.js +208 -0
  144. package/dist/server/agent-teams-run-queue.js.map +1 -0
  145. package/dist/server/agent-teams.d.ts +67 -0
  146. package/dist/server/agent-teams.d.ts.map +1 -1
  147. package/dist/server/agent-teams.js +607 -180
  148. package/dist/server/agent-teams.js.map +1 -1
  149. package/dist/server/auth-marketing.d.ts.map +1 -1
  150. package/dist/server/auth-marketing.js +0 -64
  151. package/dist/server/auth-marketing.js.map +1 -1
  152. package/dist/server/auth.d.ts.map +1 -1
  153. package/dist/server/auth.js +67 -14
  154. package/dist/server/auth.js.map +1 -1
  155. package/dist/server/builder-browser.d.ts +12 -2
  156. package/dist/server/builder-browser.d.ts.map +1 -1
  157. package/dist/server/builder-browser.js +24 -0
  158. package/dist/server/builder-browser.js.map +1 -1
  159. package/dist/server/core-routes-plugin.d.ts.map +1 -1
  160. package/dist/server/core-routes-plugin.js +66 -5
  161. package/dist/server/core-routes-plugin.js.map +1 -1
  162. package/dist/server/credential-provider.d.ts +10 -0
  163. package/dist/server/credential-provider.d.ts.map +1 -1
  164. package/dist/server/credential-provider.js +82 -3
  165. package/dist/server/credential-provider.js.map +1 -1
  166. package/dist/server/csrf.d.ts.map +1 -1
  167. package/dist/server/csrf.js +3 -0
  168. package/dist/server/csrf.js.map +1 -1
  169. package/dist/server/index.d.ts +1 -0
  170. package/dist/server/index.d.ts.map +1 -1
  171. package/dist/server/index.js +1 -0
  172. package/dist/server/index.js.map +1 -1
  173. package/dist/server/onboarding-html.d.ts +1 -0
  174. package/dist/server/onboarding-html.d.ts.map +1 -1
  175. package/dist/server/onboarding-html.js +14 -1
  176. package/dist/server/onboarding-html.js.map +1 -1
  177. package/dist/server/self-dispatch.d.ts +44 -0
  178. package/dist/server/self-dispatch.d.ts.map +1 -0
  179. package/dist/server/self-dispatch.js +113 -0
  180. package/dist/server/self-dispatch.js.map +1 -0
  181. package/dist/server/social-og-image.d.ts +14 -0
  182. package/dist/server/social-og-image.d.ts.map +1 -0
  183. package/dist/server/social-og-image.js +251 -0
  184. package/dist/server/social-og-image.js.map +1 -0
  185. package/dist/server/ssr-handler.d.ts +1 -1
  186. package/dist/server/ssr-handler.d.ts.map +1 -1
  187. package/dist/server/ssr-handler.js +27 -11
  188. package/dist/server/ssr-handler.js.map +1 -1
  189. package/dist/shared/cache-control.d.ts +7 -0
  190. package/dist/shared/cache-control.d.ts.map +1 -1
  191. package/dist/shared/cache-control.js +7 -0
  192. package/dist/shared/cache-control.js.map +1 -1
  193. package/dist/shared/index.d.ts +1 -1
  194. package/dist/shared/index.d.ts.map +1 -1
  195. package/dist/shared/index.js +1 -1
  196. package/dist/shared/index.js.map +1 -1
  197. package/dist/shared/route-warmup-config.d.ts +28 -0
  198. package/dist/shared/route-warmup-config.d.ts.map +1 -0
  199. package/dist/shared/route-warmup-config.js +58 -0
  200. package/dist/shared/route-warmup-config.js.map +1 -0
  201. package/dist/shared/social-meta.d.ts +5 -0
  202. package/dist/shared/social-meta.d.ts.map +1 -1
  203. package/dist/shared/social-meta.js +36 -2
  204. package/dist/shared/social-meta.js.map +1 -1
  205. package/dist/shared/streaming-text-smoothing.d.ts +12 -0
  206. package/dist/shared/streaming-text-smoothing.d.ts.map +1 -0
  207. package/dist/shared/streaming-text-smoothing.js +52 -0
  208. package/dist/shared/streaming-text-smoothing.js.map +1 -0
  209. package/dist/styles/agent-native.css +4 -4
  210. package/dist/templates/default/AGENTS.md +9 -4
  211. package/dist/templates/default/DEVELOPING.md +15 -1
  212. package/dist/templates/workspace-core/AGENTS.md +7 -3
  213. package/dist/templates/workspace-root/AGENTS.md +7 -3
  214. package/dist/vite/client.d.ts +13 -0
  215. package/dist/vite/client.d.ts.map +1 -1
  216. package/dist/vite/client.js +26 -0
  217. package/dist/vite/client.js.map +1 -1
  218. package/dist/vite/index.d.ts +1 -0
  219. package/dist/vite/index.d.ts.map +1 -1
  220. package/dist/vite/index.js.map +1 -1
  221. package/docs/content/client.md +62 -1
  222. package/docs/content/code-agents-ui.md +6 -13
  223. package/docs/content/context-awareness.md +186 -21
  224. package/docs/content/deployment.md +8 -11
  225. package/docs/content/dispatch.md +1 -1
  226. package/docs/content/external-agents.md +32 -2
  227. package/docs/content/migration-workbench.md +4 -21
  228. package/docs/content/multi-app-workspace.md +1 -1
  229. package/docs/content/recurring-jobs.md +1 -1
  230. package/docs/content/security.md +0 -1
  231. package/docs/content/sharing.md +1 -3
  232. package/docs/content/skills-guide.md +12 -10
  233. package/docs/content/template-assets.md +21 -1
  234. package/docs/content/template-design.md +23 -5
  235. package/docs/content/template-dispatch.md +1 -1
  236. package/package.json +2 -1
  237. package/src/templates/default/AGENTS.md +9 -4
  238. package/src/templates/default/DEVELOPING.md +15 -1
  239. package/src/templates/workspace-core/AGENTS.md +7 -3
  240. package/src/templates/workspace-root/AGENTS.md +7 -3
@@ -15,14 +15,23 @@
15
15
  * serverless cold starts and works across multiple processes.
16
16
  */
17
17
  import { actionsToEngineTools } from "../agent/production-agent.js";
18
- import { createAnthropicEngine } from "../agent/engine/anthropic-engine.js";
19
18
  import { createThread } from "../chat-threads/store.js";
20
- import { abortRun, getRun, startRun, subscribeToRun, } from "../agent/run-manager.js";
19
+ import { abortRun, getActiveRunForThreadAsync, getRun, startRun, subscribeToRun, } from "../agent/run-manager.js";
21
20
  import { getRunEventsSince } from "../agent/run-store.js";
22
- import { runAgentLoop } from "../agent/production-agent.js";
23
- import { buildAssistantMessage } from "../agent/thread-data-builder.js";
21
+ import { runAgentLoop, appendAgentLoopContinuation, } from "../agent/production-agent.js";
22
+ import { buildAssistantMessage, foldAssistantTurn, threadDataToEngineMessages, } from "../agent/thread-data-builder.js";
23
+ import { completeRun as completeProgressRun, startRun as startProgressRun, updateRunProgress, } from "../progress/registry.js";
24
+ import { enqueueAgentTeamRun, claimAgentTeamRun, touchAgentTeamRun, bumpAgentTeamContinuation, completeAgentTeamRun, getAgentTeamRunDispatchState, listActiveAgentTeamTaskIdsForOwner, MAX_AGENT_TEAM_CONTINUATIONS, RUN_DISPATCH_STUCK_AFTER_MS, RUN_PROCESSING_STUCK_AFTER_MS, } from "./agent-teams-run-queue.js";
25
+ import { fireInternalDispatch } from "./self-dispatch.js";
26
+ import { resolveOrgIdForEmail } from "../org/context.js";
24
27
  import { readAppState, writeAppState, listAppState, deleteAppState, } from "../application-state/script-helpers.js";
25
- import { getRequestUserEmail } from "./request-context.js";
28
+ import { getRequestUserEmail, runWithRequestContext, } from "./request-context.js";
29
+ /** Framework route the self-fire dispatch targets to run a queued sub-agent in
30
+ * a fresh function invocation. Mounted inside the agent-chat plugin (where the
31
+ * sub-agent action/prompt/engine closures live). */
32
+ export const AGENT_TEAM_PROCESS_RUN_PATH = "/_agent-native/agent-teams/_process-run";
33
+ /** Heartbeat cadence for the queue row while a chunk is actively processing. */
34
+ const RUN_QUEUE_HEARTBEAT_MS = 5_000;
26
35
  export function createAgentTeamBackgroundAgentController() {
27
36
  return {
28
37
  async list(options) {
@@ -43,6 +52,7 @@ const TASK_PREFIX = "agent-task:";
43
52
  const THREAD_PREFIX = "agent-task-thread:";
44
53
  /** Key prefix for queued orchestrator→sub-agent messages. */
45
54
  const TASK_MESSAGE_PREFIX = "task-message:";
55
+ const TASK_RUN_MISSING_GRACE_MS = 60_000;
46
56
  function taskMessageQueuePrefix(taskId) {
47
57
  return `${TASK_MESSAGE_PREFIX}${taskId}:`;
48
58
  }
@@ -176,6 +186,7 @@ function createTaskMessageFinalGuard(taskId) {
176
186
  };
177
187
  }
178
188
  async function saveTask(task) {
189
+ task.updatedAt = Date.now();
179
190
  await writeAppState(`${TASK_PREFIX}${task.taskId}`, task);
180
191
  await writeAppState(`${THREAD_PREFIX}${task.threadId}`, {
181
192
  taskId: task.taskId,
@@ -191,6 +202,160 @@ async function loadTaskByThread(threadId) {
191
202
  return null;
192
203
  return loadTask(ref.taskId);
193
204
  }
205
+ function applyDispatchMetadataToTask(task, dispatch) {
206
+ if (!dispatch)
207
+ return task;
208
+ const parentThreadId = dispatch.payload.parentThreadId?.trim();
209
+ const name = dispatch.payload.name?.trim();
210
+ if (parentThreadId && !task.parentThreadId) {
211
+ task.parentThreadId = parentThreadId;
212
+ }
213
+ if (name && !task.name) {
214
+ task.name = name;
215
+ }
216
+ return task;
217
+ }
218
+ async function completeReconciledTask(task, ownerEmail) {
219
+ task.status = "completed";
220
+ task.summary = task.summary || task.preview || "Task completed.";
221
+ task.currentStep = "";
222
+ task.completedAt = Date.now();
223
+ await saveTask(task);
224
+ if (ownerEmail) {
225
+ await completeTaskProgressRun(task, ownerEmail, "succeeded", "Task completed.");
226
+ }
227
+ return task;
228
+ }
229
+ async function failReconciledTask(task, ownerEmail, message, progressStatus = "failed") {
230
+ task.status = "errored";
231
+ task.summary = task.summary || task.preview || message;
232
+ task.error = task.error || message;
233
+ task.currentStep = "";
234
+ task.completedAt = Date.now();
235
+ await saveTask(task);
236
+ if (ownerEmail) {
237
+ await completeTaskProgressRun(task, ownerEmail, progressStatus, message);
238
+ }
239
+ // Make the dispatch row terminal too so it isn't re-fired.
240
+ await completeAgentTeamRun(task.taskId, "failed").catch(() => { });
241
+ return task;
242
+ }
243
+ /**
244
+ * Re-fire a dropped self-dispatch. When the queue row is still queued/running
245
+ * but its heartbeat has gone stale (the self-fire never landed, or the
246
+ * processing invocation died), kick the processor again before the hard
247
+ * stuck-cutoff fail path gives up. Best-effort — the fail path is the backstop.
248
+ */
249
+ async function refireStuckAgentTeamRunIfNeeded(task, dispatch, event) {
250
+ if (dispatch.status !== "queued" && dispatch.status !== "running")
251
+ return;
252
+ const idleFor = Date.now() - dispatch.updatedAt;
253
+ if (idleFor < RUN_DISPATCH_STUCK_AFTER_MS)
254
+ return; // still fresh — leave it
255
+ if (idleFor >= RUN_PROCESSING_STUCK_AFTER_MS)
256
+ return; // fail path owns this
257
+ try {
258
+ await fireInternalDispatch({
259
+ event,
260
+ path: AGENT_TEAM_PROCESS_RUN_PATH,
261
+ taskId: task.taskId,
262
+ body: { mode: dispatch.continuationCount > 0 ? "continue" : "start" },
263
+ });
264
+ }
265
+ catch {
266
+ // best-effort
267
+ }
268
+ }
269
+ /**
270
+ * Reconcile a task that still reads "running" against the actual run state.
271
+ *
272
+ * Durable-dispatch path (the normal case): the `agent_team_run_queue` row is
273
+ * the authority. While it's queued/running the task stays running (a single
274
+ * completed agent_runs chunk at a soft-timeout boundary does NOT mean the task
275
+ * finished — a continuation may be queued/in-flight). A dropped dispatch is
276
+ * re-fired; a genuinely stalled one (past the hard cutoff) is failed.
277
+ *
278
+ * Legacy fallback (no queue row — pre-upgrade tasks, or the in-process
279
+ * Cloudflare path): fall back to the in-memory/SQL run state via
280
+ * `getActiveRunForThreadAsync`, with the original missing-run grace.
281
+ */
282
+ async function reconcileTaskWithRun(task, event) {
283
+ let dispatch = null;
284
+ try {
285
+ dispatch = await getAgentTeamRunDispatchState(task.taskId);
286
+ }
287
+ catch {
288
+ dispatch = null;
289
+ }
290
+ applyDispatchMetadataToTask(task, dispatch);
291
+ if (task.status !== "running")
292
+ return task;
293
+ if (dispatch) {
294
+ const ownerEmail = dispatch.ownerEmail ?? getRequestUserEmail() ?? null;
295
+ if (dispatch.status === "queued" || dispatch.status === "running") {
296
+ const stuckFor = Date.now() - dispatch.updatedAt;
297
+ if (stuckFor < RUN_PROCESSING_STUCK_AFTER_MS) {
298
+ await refireStuckAgentTeamRunIfNeeded(task, dispatch, event);
299
+ return task;
300
+ }
301
+ return await failReconciledTask(task, ownerEmail, "Sub-agent run stalled and did not produce a result.");
302
+ }
303
+ if (dispatch.status === "failed") {
304
+ return await failReconciledTask(task, ownerEmail, task.error || task.summary || "Sub-agent run failed.");
305
+ }
306
+ // status === "done" but task still running — safety net (the processor
307
+ // normally sets the task terminal before completing the queue row).
308
+ return await completeReconciledTask(task, ownerEmail);
309
+ }
310
+ // ── Legacy fallback: no durable queue row ────────────────────────────────
311
+ if (!task.runId)
312
+ return task;
313
+ let runState;
314
+ try {
315
+ runState = await getActiveRunForThreadAsync(task.threadId);
316
+ }
317
+ catch {
318
+ return task;
319
+ }
320
+ if (runState?.status === "running")
321
+ return task;
322
+ const ownerEmail = getRequestUserEmail();
323
+ if (runState?.status === "completed") {
324
+ return await completeReconciledTask(task, ownerEmail);
325
+ }
326
+ if (runState?.status === "errored" || runState?.status === "aborted") {
327
+ return await failReconciledTask(task, ownerEmail, runState.status === "aborted" ? "Task stopped." : "Task failed.", runState.status === "aborted" ? "cancelled" : "failed");
328
+ }
329
+ const referenceAt = task.startedAt ?? task.createdAt;
330
+ if (Date.now() - referenceAt < TASK_RUN_MISSING_GRACE_MS)
331
+ return task;
332
+ return await failReconciledTask(task, ownerEmail, "Sub-agent run is no longer active and did not produce a result.");
333
+ }
334
+ /**
335
+ * Reconcile all of an owner's in-flight sub-agent runs. Wired into the RunsTray
336
+ * data path (`/_agent-native/runs`) so the tray self-heals: dropped dispatches
337
+ * are re-fired and dead runs are marked failed promptly, even when the
338
+ * orchestrator chat never polls `status`/`read-result`.
339
+ */
340
+ export async function reconcileAgentTeamRunsForOwner(owner, event) {
341
+ let taskIds;
342
+ try {
343
+ taskIds = await listActiveAgentTeamTaskIdsForOwner(owner);
344
+ }
345
+ catch {
346
+ return;
347
+ }
348
+ for (const taskId of taskIds) {
349
+ try {
350
+ const task = await loadTask(taskId);
351
+ if (task)
352
+ await reconcileTaskWithRun(task, event);
353
+ }
354
+ catch {
355
+ // best-effort per task — one bad row shouldn't block the rest
356
+ }
357
+ }
358
+ }
194
359
  function generateTaskId() {
195
360
  return `task-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
196
361
  }
@@ -214,8 +379,112 @@ function taskTimestampToIso(timestamp) {
214
379
  function latestTaskText(task) {
215
380
  return task.summary || task.preview || task.currentStep || undefined;
216
381
  }
382
+ function formatTaskPhase(task) {
383
+ if (task.status === "running")
384
+ return task.currentStep || "Running";
385
+ if (task.status === "completed")
386
+ return "Completed";
387
+ const phase = task.error || task.summary || "Task failed.";
388
+ return phase.length > 120 ? `${phase.slice(0, 117)}...` : phase;
389
+ }
390
+ function taskProgressMetadata(task) {
391
+ return {
392
+ kind: "agent-team",
393
+ source: "agent-teams",
394
+ taskId: task.taskId,
395
+ threadId: task.threadId,
396
+ description: task.description,
397
+ preview: task.preview,
398
+ summary: task.summary,
399
+ currentStep: task.currentStep,
400
+ surfaceUrl: `agent-native://threads/${encodeURIComponent(task.threadId)}`,
401
+ ...(task.parentThreadId ? { parentThreadId: task.parentThreadId } : {}),
402
+ ...(task.name ? { name: task.name } : {}),
403
+ };
404
+ }
405
+ function currentTaskProgressStep(task) {
406
+ return (task.currentStep ||
407
+ (task.preview ? "Working on response" : "Starting sub-agent"));
408
+ }
409
+ async function startTaskProgressRun(task, ownerEmail) {
410
+ const runId = task.runId ?? taskRunId(task.taskId);
411
+ task.runId = runId;
412
+ try {
413
+ await startProgressRun({
414
+ id: runId,
415
+ owner: ownerEmail,
416
+ title: task.description,
417
+ step: currentTaskProgressStep(task),
418
+ metadata: taskProgressMetadata(task),
419
+ });
420
+ }
421
+ catch {
422
+ // Progress rows are user-facing visibility. A write failure should not
423
+ // prevent the sub-agent from running or the task card from updating.
424
+ }
425
+ }
426
+ async function updateTaskProgressRun(task, ownerEmail) {
427
+ const runId = task.runId ?? taskRunId(task.taskId);
428
+ task.runId = runId;
429
+ try {
430
+ await updateRunProgress(runId, ownerEmail, {
431
+ step: currentTaskProgressStep(task),
432
+ metadata: taskProgressMetadata(task),
433
+ });
434
+ }
435
+ catch {
436
+ // best-effort
437
+ }
438
+ }
439
+ async function completeTaskProgressRun(task, ownerEmail, status, step) {
440
+ const runId = task.runId ?? taskRunId(task.taskId);
441
+ task.runId = runId;
442
+ try {
443
+ await completeProgressRun(runId, ownerEmail, status, {
444
+ step,
445
+ metadata: taskProgressMetadata(task),
446
+ });
447
+ }
448
+ catch {
449
+ // best-effort
450
+ }
451
+ }
452
+ function resolveTaskCompletion(run, accumulatedText) {
453
+ const text = accumulatedText.trim();
454
+ if (run.status === "aborted") {
455
+ const stopped = run.abortReason && run.abortReason !== "user"
456
+ ? `Task stopped: ${run.abortReason}`
457
+ : "Task stopped.";
458
+ return {
459
+ taskStatus: "errored",
460
+ summary: stopped,
461
+ progressStatus: "cancelled",
462
+ progressStep: stopped,
463
+ error: stopped,
464
+ };
465
+ }
466
+ if (run.status === "errored") {
467
+ const failed = text.slice(-500) || "Task failed.";
468
+ return {
469
+ taskStatus: "errored",
470
+ summary: failed,
471
+ progressStatus: "failed",
472
+ progressStep: "Task failed.",
473
+ error: failed,
474
+ };
475
+ }
476
+ const summary = text.slice(-1000) || "Task completed successfully.";
477
+ return {
478
+ taskStatus: "completed",
479
+ summary,
480
+ progressStatus: "succeeded",
481
+ progressStep: "Task completed.",
482
+ };
483
+ }
217
484
  export function toAgentTaskBackgroundRun(task) {
218
485
  const createdAt = taskTimestampToIso(task.createdAt);
486
+ const updatedAt = taskTimestampToIso(task.completedAt ?? task.updatedAt ?? task.createdAt);
487
+ const phase = formatTaskPhase(task);
219
488
  return {
220
489
  schemaVersion: 1,
221
490
  id: taskRunId(task.taskId),
@@ -226,19 +495,24 @@ export function toAgentTaskBackgroundRun(task) {
226
495
  type: "agent-team-task",
227
496
  id: task.taskId,
228
497
  threadId: task.threadId,
498
+ ...(task.parentThreadId ? { parentThreadId: task.parentThreadId } : {}),
499
+ ...(task.name ? { name: task.name } : {}),
229
500
  },
230
501
  title: task.description,
231
- subtitle: task.currentStep || undefined,
502
+ subtitle: task.currentStep || (task.status === "errored" ? phase : undefined),
232
503
  status: mapTaskStatusToBackgroundStatus(task.status),
233
- phase: task.currentStep || task.status,
504
+ phase,
234
505
  createdAt,
235
- updatedAt: createdAt,
506
+ updatedAt,
236
507
  goalId: "agent-team",
237
508
  needsInput: false,
238
509
  needsApproval: false,
239
510
  details: [
240
511
  { label: "Task", value: task.taskId },
241
512
  { label: "Thread", value: task.threadId },
513
+ ...(task.parentThreadId
514
+ ? [{ label: "Parent", value: task.parentThreadId }]
515
+ : []),
242
516
  ],
243
517
  surfaceUrl: `agent-native://threads/${task.threadId}`,
244
518
  metadata: {
@@ -249,6 +523,10 @@ export function toAgentTaskBackgroundRun(task) {
249
523
  summary: task.summary,
250
524
  currentStep: task.currentStep,
251
525
  latestText: latestTaskText(task),
526
+ completedAt: task.completedAt,
527
+ error: task.error,
528
+ ...(task.parentThreadId ? { parentThreadId: task.parentThreadId } : {}),
529
+ ...(task.name ? { name: task.name } : {}),
252
530
  },
253
531
  };
254
532
  }
@@ -399,17 +677,25 @@ export async function spawnTask(opts) {
399
677
  catch {
400
678
  // Best effort — thread will still work without persisted messages
401
679
  }
680
+ const runId = taskRunId(taskId);
681
+ const createdAt = Date.now();
402
682
  const task = {
403
683
  taskId,
404
684
  threadId: thread.id,
685
+ ...(opts.parentThreadId ? { parentThreadId: opts.parentThreadId } : {}),
686
+ ...(opts.name ? { name: opts.name } : {}),
405
687
  description: opts.description,
406
688
  status: "running",
407
689
  preview: "",
408
690
  summary: "",
409
- currentStep: "",
410
- createdAt: Date.now(),
691
+ currentStep: "Starting sub-agent",
692
+ createdAt,
693
+ updatedAt: createdAt,
694
+ startedAt: createdAt,
695
+ runId,
411
696
  };
412
697
  await saveTask(task);
698
+ await startTaskProgressRun(task, opts.ownerEmail);
413
699
  // Notify parent chat that a sub-agent was spawned
414
700
  opts.parentSend({
415
701
  type: "agent_task",
@@ -418,11 +704,66 @@ export async function spawnTask(opts) {
418
704
  description: opts.description,
419
705
  status: "running",
420
706
  });
421
- // Build scoped system prompt
422
- // Prepend a clear "you are a sub-agent" reminder so the agent doesn't
423
- // start exploring the file system or database before using its actions.
424
- const actionNames = Object.keys(opts.actions).join(", ");
425
- const subAgentPreamble = `## You Are a Sub-Agent
707
+ // Hand the run off to the durable dispatch queue and self-fire a fresh
708
+ // function invocation to execute it. This is what makes background
709
+ // sub-agents survive serverless: the spawning request returns immediately
710
+ // while the sub-agent runs in its OWN invocation (with its own timeout
711
+ // budget) instead of as a detached in-process promise that the host freezes
712
+ // when this response flushes. Same enqueue-to-SQL + self-fire-HTTP pattern
713
+ // as A2A async tasks (a2a/handlers.ts) and integration webhooks
714
+ // (integrations/webhook-handler.ts). Execution happens in `processAgentTeamRun`,
715
+ // invoked by the `/_agent-native/agent-teams/_process-run` route mounted
716
+ // inside the agent-chat plugin (where the action/prompt/engine closures live).
717
+ let orgId = null;
718
+ try {
719
+ orgId = (await resolveOrgIdForEmail(opts.ownerEmail)) ?? null;
720
+ }
721
+ catch {
722
+ orgId = null;
723
+ }
724
+ const payload = {
725
+ description: opts.description,
726
+ instructions: opts.instructions,
727
+ model: opts.model,
728
+ ...(opts.parentThreadId ? { parentThreadId: opts.parentThreadId } : {}),
729
+ ...(opts.name ? { name: opts.name } : {}),
730
+ // Stable across continuation chunks so the durable assistant message folds.
731
+ turnId: runId,
732
+ };
733
+ try {
734
+ await enqueueAgentTeamRun({
735
+ taskId,
736
+ threadId: thread.id,
737
+ runId,
738
+ ownerEmail: opts.ownerEmail,
739
+ orgId,
740
+ payload,
741
+ });
742
+ await fireInternalDispatch({
743
+ path: AGENT_TEAM_PROCESS_RUN_PATH,
744
+ taskId,
745
+ body: { mode: "start" },
746
+ });
747
+ }
748
+ catch (err) {
749
+ // Enqueue/dispatch failed outright — surface as an errored task rather
750
+ // than a ghost "running" one. (A dropped self-fire that still enqueued is
751
+ // recovered by the reconcile stuck-refire path.)
752
+ const message = err instanceof Error
753
+ ? `Failed to start sub-agent: ${err.message}`
754
+ : "Failed to start sub-agent.";
755
+ await failReconciledTask(task, opts.ownerEmail, message);
756
+ }
757
+ return task;
758
+ }
759
+ /**
760
+ * Build the sub-agent system prompt: a "you are a sub-agent" preamble (so it
761
+ * starts on its task instead of exploring), the base prompt, and any
762
+ * task-specific instructions.
763
+ */
764
+ function buildSubAgentSystemPrompt(baseSystemPrompt, actions, instructions) {
765
+ const actionNames = Object.keys(actions).join(", ");
766
+ const preamble = `## You Are a Sub-Agent
426
767
 
427
768
  You are a focused sub-agent with a specific task. You have been given a curated set of actions that connect directly to the app's database and services.
428
769
 
@@ -435,203 +776,274 @@ You are a focused sub-agent with a specific task. You have been given a curated
435
776
  **Your available actions (${actionNames}) work directly. Use them.**
436
777
 
437
778
  `;
438
- let systemPrompt = subAgentPreamble + opts.systemPrompt;
439
- if (opts.instructions) {
440
- systemPrompt += `\n\n## Task-Specific Instructions\n\n${opts.instructions}`;
441
- }
442
- // Resolve the engine — prefer the passed engine, fall back to Anthropic with apiKey
443
- const engine = opts.engine ?? createAnthropicEngine({ apiKey: opts.apiKey });
444
- const model = opts.model ?? engine.defaultModel;
445
- // Build tools from actions using the normalized EngineTool format
446
- const messageAwareActions = createMessageAwareActions(taskId, opts.actions);
447
- const tools = actionsToEngineTools(messageAwareActions);
448
- const messages = [
449
- { role: "user", content: [{ type: "text", text: opts.description }] },
450
- ];
451
- // Start the agent run in background
452
- const runId = `run-task-${taskId}`;
453
- let accumulatedText = "";
454
- let lastPreviewSent = 0;
455
- const PREVIEW_INTERVAL_MS = 300; // Throttle preview updates to every 300ms
456
- // Gate to prevent sendPreviewUpdate from overwriting terminal status
457
- let runFinished = false;
458
- startRun(runId, thread.id, async (send, signal) => {
459
- const sendPreviewUpdate = async (force = false) => {
460
- if (runFinished)
461
- return; // Don't overwrite completed/errored status
462
- const now = Date.now();
463
- if (!force && now - lastPreviewSent < PREVIEW_INTERVAL_MS)
464
- return;
465
- lastPreviewSent = now;
466
- task.preview = accumulatedText.slice(-800);
467
- // Persist to SQL so status checks from other processes see live state
468
- await saveTask(task);
469
- opts.parentSend({
470
- type: "agent_task_update",
471
- taskId,
472
- preview: task.preview,
473
- currentStep: task.currentStep,
779
+ let prompt = preamble + baseSystemPrompt;
780
+ if (instructions) {
781
+ prompt += `\n\n## Task-Specific Instructions\n\n${instructions}`;
782
+ }
783
+ return prompt;
784
+ }
785
+ /**
786
+ * Persist the sub-agent conversation to thread_data, folding continuation
787
+ * chunks of the same turn into one assistant message (same `foldAssistantTurn`
788
+ * mechanism the main chat uses). Returns the full folded assistant text for use
789
+ * as the task summary so multi-chunk runs don't lose earlier chunks' output.
790
+ */
791
+ async function persistTaskThreadData(task, description, run, runId, turnId) {
792
+ try {
793
+ const { getThread, updateThreadData } = await import("../chat-threads/store.js");
794
+ const thread = await getThread(task.threadId);
795
+ let repo;
796
+ try {
797
+ repo = JSON.parse(thread?.threadData || "{}");
798
+ }
799
+ catch {
800
+ repo = {};
801
+ }
802
+ if (!Array.isArray(repo.messages))
803
+ repo.messages = [];
804
+ // Ensure the seed user message exists (first chunk / fresh thread).
805
+ const userMsgId = `msg-${task.taskId}-user`;
806
+ const hasUser = repo.messages.some((m) => (m?.message ?? m)?.id === userMsgId);
807
+ if (!hasUser) {
808
+ repo.messages.unshift({
809
+ message: {
810
+ id: userMsgId,
811
+ role: "user",
812
+ content: [{ type: "text", text: description }],
813
+ metadata: {},
814
+ },
815
+ parentId: null,
474
816
  });
475
- };
476
- // Wrap the send function to also emit preview updates to parent
477
- const wrappedSend = (event) => {
478
- send(event);
479
- if (event.type === "text") {
480
- accumulatedText += event.text;
481
- sendPreviewUpdate();
482
- }
483
- else if (event.type === "tool_start") {
484
- task.currentStep = `Running ${event.tool}...`;
485
- sendPreviewUpdate(true);
486
- }
487
- else if (event.type === "tool_done") {
488
- task.currentStep = "";
489
- sendPreviewUpdate(true);
490
- }
491
- };
492
- await runAgentLoop({
493
- engine,
494
- model,
495
- systemPrompt,
496
- tools,
497
- messages,
498
- actions: messageAwareActions,
499
- send: wrappedSend,
500
- signal,
501
- finalResponseGuard: createTaskMessageFinalGuard(taskId),
817
+ if (!repo.headId)
818
+ repo.headId = userMsgId;
819
+ }
820
+ const assistantMsg = buildAssistantMessage(run.events ?? [], runId, {
821
+ suppressInternalContinuation: true,
822
+ turnId,
502
823
  });
503
- },
504
- // onComplete callback called when the run finishes (success or error)
505
- async (run) => {
506
- // Prevent any in-flight sendPreviewUpdate from overwriting terminal status
507
- runFinished = true;
508
- if (run.status === "errored") {
509
- task.status = "errored";
510
- task.summary = accumulatedText.slice(-500) || "Task failed.";
511
- await saveTask(task);
512
- // Emit error as agent_task_complete with errored status
513
- opts.parentSend({
514
- type: "agent_task",
515
- taskId,
516
- threadId: thread.id,
517
- description: task.description,
518
- status: "errored",
519
- });
824
+ if (assistantMsg) {
825
+ repo = foldAssistantTurn(repo, assistantMsg, { runId, turnId });
520
826
  }
521
- else {
522
- task.status = "completed";
523
- task.summary =
524
- accumulatedText.slice(-1000) || "Task completed successfully.";
525
- await saveTask(task);
526
- opts.parentSend({
527
- type: "agent_task_complete",
528
- taskId,
529
- summary: task.summary,
530
- });
827
+ // Extract the folded assistant text (full content across chunks).
828
+ let assistantText = "";
829
+ const headEntry = Array.isArray(repo.messages)
830
+ ? repo.messages.find((m) => (m?.message ?? m)?.id === repo.headId)
831
+ : undefined;
832
+ const headMsg = headEntry?.message ?? headEntry;
833
+ if (headMsg?.role === "assistant" && Array.isArray(headMsg.content)) {
834
+ assistantText = headMsg.content
835
+ .filter((c) => c?.type === "text" && typeof c.text === "string")
836
+ .map((c) => c.text)
837
+ .join("\n");
838
+ }
839
+ await updateThreadData(task.threadId, JSON.stringify(repo), description.slice(0, 100), assistantText.slice(0, 200), Array.isArray(repo.messages) ? repo.messages.length : 1);
840
+ return assistantText;
841
+ }
842
+ catch {
843
+ return "";
844
+ }
845
+ }
846
+ /** Mark a sub-agent task terminal: task record, progress row, and queue row. */
847
+ async function finalizeAgentTeamRun(task, run, ownerEmail, fullText) {
848
+ const terminal = resolveTaskCompletion(run, fullText);
849
+ task.status = terminal.taskStatus;
850
+ task.summary = terminal.summary;
851
+ task.error = terminal.error;
852
+ task.currentStep = "";
853
+ task.completedAt = Date.now();
854
+ await saveTask(task);
855
+ if (ownerEmail) {
856
+ await completeTaskProgressRun(task, ownerEmail, terminal.progressStatus, terminal.progressStep);
857
+ }
858
+ await completeAgentTeamRun(task.taskId, terminal.taskStatus === "completed" ? "done" : "failed");
859
+ }
860
+ /**
861
+ * Execute one chunk of a queued sub-agent run in a fresh function invocation.
862
+ * Called by the `/_agent-native/agent-teams/_process-run` route. Atomically
863
+ * claims the run (idempotent on duplicate self-fires), reconstructs the
864
+ * messages (start vs continue), runs the agent loop to completion, persists
865
+ * thread_data, and either self-fires a continuation (soft-timeout boundary,
866
+ * under the cap) or finalizes the task.
867
+ */
868
+ export async function processAgentTeamRun(opts) {
869
+ const claimed = await claimAgentTeamRun(opts.taskId);
870
+ if (!claimed)
871
+ return { ok: true, skipped: "already-claimed-or-missing" };
872
+ return await runWithRequestContext({
873
+ userEmail: claimed.ownerEmail ?? undefined,
874
+ orgId: claimed.orgId ?? undefined,
875
+ }, async () => {
876
+ const task = await loadTask(opts.taskId);
877
+ if (!task) {
878
+ await completeAgentTeamRun(opts.taskId, "failed");
879
+ return { ok: true, skipped: "task-missing" };
880
+ }
881
+ if (task.status !== "running") {
882
+ await completeAgentTeamRun(opts.taskId, task.status === "completed" ? "done" : "failed");
883
+ return { ok: true, skipped: "task-terminal" };
531
884
  }
532
- // Persist the full conversation to threadData so the sub-agent tab
533
- // can restore it later (after the in-memory run is cleaned up).
534
- // Rebuild from run.events via buildAssistantMessage so partial text
535
- // streamed in an interrupted final iteration is preserved — the
536
- // EngineMessage[] array only picks up a turn after runAgentLoop
537
- // finishes pushing, so an aborted mid-stream would otherwise be lost.
885
+ const payload = claimed.payload;
886
+ const ownerEmail = claimed.ownerEmail ?? getRequestUserEmail() ?? "";
887
+ const orgId = claimed.orgId;
888
+ const turnId = payload.turnId || taskRunId(opts.taskId);
889
+ let config;
538
890
  try {
539
- const { updateThreadData } = await import("../chat-threads/store.js");
540
- const userMsg = {
541
- id: `msg-${taskId}-user`,
542
- role: "user",
543
- content: [{ type: "text", text: opts.description }],
544
- metadata: {},
545
- };
546
- const assistantMsg = buildAssistantMessage(run.events ?? [], `task-${taskId}`);
547
- // Chain assistant → user via parentId so assistant-ui renders them
548
- // as a linked conversation, not orphaned siblings. headId points to
549
- // the leaf (assistant if present, otherwise the user message).
550
- const messages = [{ message: userMsg, parentId: null }];
551
- if (assistantMsg) {
552
- messages.push({
553
- message: {
554
- ...assistantMsg,
555
- status: { type: "complete", reason: "stop" },
556
- },
557
- parentId: userMsg.id,
558
- });
559
- }
560
- const headId = assistantMsg?.id ?? userMsg.id;
561
- const repo = { headId, messages };
562
- const title = opts.description.slice(0, 100);
563
- const preview = accumulatedText.slice(0, 200);
564
- await updateThreadData(thread.id, JSON.stringify(repo), title, preview, repo.messages.length);
891
+ config = await opts.resolveConfig({ payload, ownerEmail, orgId });
565
892
  }
566
- catch {
567
- // Best effort the in-memory replay path still works
893
+ catch (err) {
894
+ const message = err instanceof Error
895
+ ? `Failed to prepare sub-agent: ${err.message}`
896
+ : "Failed to prepare sub-agent.";
897
+ await failReconciledTask(task, ownerEmail || null, message);
898
+ return { ok: false, skipped: "config-failed" };
568
899
  }
569
- // ─── Auto-follow-up on parent thread ────────────────────────────
570
- // When the sub-agent finishes, start a short agent run on the
571
- // parent thread so the user sees a recap without having to scroll
572
- // up or manually check the sub-agent card.
573
- if (opts.parentThreadId) {
900
+ const mode = opts.mode ?? (claimed.continuationCount > 0 ? "continue" : "start");
901
+ const systemPrompt = buildSubAgentSystemPrompt(config.baseSystemPrompt, config.actions, payload.instructions);
902
+ let messages;
903
+ if (mode === "continue") {
904
+ let priorThreadData;
574
905
  try {
575
- const { getActiveRunForThread } = await import("../agent/run-manager.js");
576
- // Only auto-respond if the parent thread is idle — don't
577
- // interrupt an ongoing conversation.
578
- const activeRun = getActiveRunForThread(opts.parentThreadId);
579
- if (!activeRun || activeRun.status !== "running") {
580
- const followUpEngine = opts.engine ?? createAnthropicEngine({ apiKey: opts.apiKey });
581
- const followUpModel = opts.model ?? followUpEngine.defaultModel;
582
- const statusEmoji = task.status === "errored" ? "!" : "done";
583
- const notification = `[Sub-agent ${statusEmoji}] The sub-agent task "${task.description}" has ${task.status === "errored" ? "failed" : "completed"}.\n\n` +
584
- `Summary of what it did:\n${task.summary}\n\n` +
585
- `Briefly let the user know the sub-agent finished and highlight any key results. Be concise — 1-2 sentences.`;
586
- const followUpRunId = `run-followup-${taskId}`;
587
- startRun(followUpRunId, opts.parentThreadId, async (send, signal) => {
588
- await runAgentLoop({
589
- engine: followUpEngine,
590
- model: followUpModel,
591
- systemPrompt: opts.systemPrompt,
592
- tools: [], // No tools needed for a recap
593
- messages: [
594
- {
595
- role: "user",
596
- content: [{ type: "text", text: notification }],
597
- },
598
- ],
599
- actions: {},
600
- send,
601
- signal,
602
- });
603
- });
604
- }
906
+ const { getThread } = await import("../chat-threads/store.js");
907
+ priorThreadData = (await getThread(task.threadId))?.threadData;
605
908
  }
606
909
  catch {
607
- // Best effort — don't break the sub-agent completion
910
+ priorThreadData = undefined;
911
+ }
912
+ messages = threadDataToEngineMessages(priorThreadData);
913
+ if (messages.length === 0) {
914
+ messages = [
915
+ {
916
+ role: "user",
917
+ content: [{ type: "text", text: payload.description }],
918
+ },
919
+ ];
608
920
  }
921
+ appendAgentLoopContinuation(messages, "run_timeout");
922
+ }
923
+ else {
924
+ messages = [
925
+ {
926
+ role: "user",
927
+ content: [{ type: "text", text: payload.description }],
928
+ },
929
+ ];
609
930
  }
931
+ const messageAwareActions = createMessageAwareActions(opts.taskId, config.actions);
932
+ const tools = actionsToEngineTools(messageAwareActions);
933
+ // Fresh runId per chunk (avoids agent_runs PK collisions); stable turnId so
934
+ // the durable assistant message folds across chunks.
935
+ const runId = `${taskRunId(opts.taskId)}-c${claimed.continuationCount}`;
936
+ task.currentStep =
937
+ mode === "continue" ? "Continuing sub-agent" : "Working on response";
938
+ task.startedAt = task.startedAt ?? Date.now();
939
+ await saveTask(task);
940
+ if (ownerEmail)
941
+ await updateTaskProgressRun(task, ownerEmail);
942
+ const heartbeat = setInterval(() => {
943
+ void touchAgentTeamRun(opts.taskId);
944
+ }, RUN_QUEUE_HEARTBEAT_MS);
945
+ heartbeat.unref?.();
946
+ let accumulatedText = "";
947
+ let lastProgressSent = 0;
948
+ const PROGRESS_INTERVAL_MS = 2000;
949
+ await new Promise((resolve) => {
950
+ startRun(runId, task.threadId, async (send, signal) => {
951
+ const wrappedSend = (event) => {
952
+ send(event);
953
+ if (event.type === "text") {
954
+ accumulatedText += event.text;
955
+ task.preview = accumulatedText.slice(-800);
956
+ const now = Date.now();
957
+ if (now - lastProgressSent >= PROGRESS_INTERVAL_MS) {
958
+ lastProgressSent = now;
959
+ void saveTask(task);
960
+ if (ownerEmail)
961
+ void updateTaskProgressRun(task, ownerEmail);
962
+ }
963
+ }
964
+ else if (event.type === "tool_start") {
965
+ task.currentStep = `Running ${event.tool}...`;
966
+ }
967
+ else if (event.type === "tool_done") {
968
+ task.currentStep = "";
969
+ }
970
+ };
971
+ await runWithRequestContext({ userEmail: ownerEmail || undefined, orgId: orgId ?? undefined }, async () => {
972
+ await runAgentLoop({
973
+ engine: config.engine,
974
+ model: config.model,
975
+ systemPrompt,
976
+ tools,
977
+ messages,
978
+ actions: messageAwareActions,
979
+ send: wrappedSend,
980
+ signal,
981
+ finalResponseGuard: createTaskMessageFinalGuard(opts.taskId),
982
+ });
983
+ });
984
+ }, async (run) => {
985
+ clearInterval(heartbeat);
986
+ try {
987
+ const fullText = await persistTaskThreadData(task, payload.description, run, runId, turnId);
988
+ // A soft-timeout boundary means the host function wall is near and
989
+ // the partial turn is checkpointed in thread_data — self-fire the
990
+ // next continuation chunk (server-side analog of the client re-POST
991
+ // that continues the main chat) instead of finalizing.
992
+ const reachedBoundary = (run.events ?? []).some((e) => e.event.type === "auto_continue");
993
+ if (reachedBoundary) {
994
+ const count = await bumpAgentTeamContinuation(opts.taskId);
995
+ if (count !== null && count <= MAX_AGENT_TEAM_CONTINUATIONS) {
996
+ task.currentStep = "Continuing sub-agent";
997
+ task.preview = (fullText || accumulatedText).slice(-800);
998
+ await saveTask(task);
999
+ if (ownerEmail)
1000
+ await updateTaskProgressRun(task, ownerEmail);
1001
+ await fireInternalDispatch({
1002
+ event: opts.event,
1003
+ path: AGENT_TEAM_PROCESS_RUN_PATH,
1004
+ taskId: opts.taskId,
1005
+ body: { mode: "continue" },
1006
+ });
1007
+ return;
1008
+ }
1009
+ // Hit the cap — finalize with whatever was produced.
1010
+ }
1011
+ await finalizeAgentTeamRun(task, run, ownerEmail || null, fullText || accumulatedText);
1012
+ }
1013
+ finally {
1014
+ resolve();
1015
+ }
1016
+ }, { useHostedSoftTimeoutDefault: true, turnId });
1017
+ });
1018
+ return { ok: true };
610
1019
  });
611
- return task;
612
1020
  }
613
1021
  /** Get task by ID */
614
1022
  export async function getTask(taskId) {
615
1023
  const task = await loadTask(taskId);
616
- return task ?? undefined;
1024
+ return task ? await reconcileTaskWithRun(task) : undefined;
617
1025
  }
618
1026
  /** Get task by thread ID */
619
1027
  export async function getTaskByThread(threadId) {
620
1028
  const task = await loadTaskByThread(threadId);
621
- return task ?? undefined;
1029
+ return task ? await reconcileTaskWithRun(task) : undefined;
622
1030
  }
623
1031
  /** List all tasks (most recent first) */
624
1032
  export async function listTasks() {
625
1033
  const entries = await listAppState(TASK_PREFIX);
626
1034
  const tasks = entries.map((e) => e.value);
627
- return tasks.sort((a, b) => b.createdAt - a.createdAt);
1035
+ const reconciled = await Promise.all(tasks.map(reconcileTaskWithRun));
1036
+ return reconciled.sort((a, b) => (b.updatedAt ?? b.completedAt ?? b.createdAt) -
1037
+ (a.updatedAt ?? a.completedAt ?? a.createdAt));
628
1038
  }
629
1039
  export async function listAgentTeamBackgroundRuns() {
630
1040
  return (await listTasks()).map(toAgentTaskBackgroundRun);
631
1041
  }
632
1042
  export async function getAgentTeamBackgroundRun(runId) {
633
1043
  const task = await loadTask(taskIdFromBackgroundRunId(runId));
634
- return task ? toAgentTaskBackgroundRun(task) : null;
1044
+ return task
1045
+ ? toAgentTaskBackgroundRun(await reconcileTaskWithRun(task))
1046
+ : null;
635
1047
  }
636
1048
  export async function listAgentTeamBackgroundTranscriptEvents(runId) {
637
1049
  const normalizedRunId = taskRunId(taskIdFromBackgroundRunId(runId));
@@ -741,7 +1153,14 @@ export async function stopAgentTeamBackgroundRun(runId, reason = "user") {
741
1153
  task.status = "errored";
742
1154
  task.summary =
743
1155
  reason === "user" ? "Task stopped." : `Task stopped: ${reason}`;
1156
+ task.error = task.summary;
1157
+ task.currentStep = "";
1158
+ task.completedAt = Date.now();
744
1159
  await saveTask(task);
1160
+ const ownerEmail = getRequestUserEmail();
1161
+ if (ownerEmail) {
1162
+ await completeTaskProgressRun(task, ownerEmail, "cancelled", task.summary);
1163
+ }
745
1164
  return { ok: true };
746
1165
  }
747
1166
  /** Mark a task as errored */
@@ -750,7 +1169,14 @@ export async function markTaskErrored(taskId, error) {
750
1169
  if (task) {
751
1170
  task.status = "errored";
752
1171
  task.summary = error;
1172
+ task.error = error;
1173
+ task.currentStep = "";
1174
+ task.completedAt = Date.now();
753
1175
  await saveTask(task);
1176
+ const ownerEmail = getRequestUserEmail();
1177
+ if (ownerEmail) {
1178
+ await completeTaskProgressRun(task, ownerEmail, "failed", error);
1179
+ }
754
1180
  }
755
1181
  }
756
1182
  export const _agentTeamsQueueForTests = {
@@ -758,5 +1184,6 @@ export const _agentTeamsQueueForTests = {
758
1184
  createTaskMessageFinalGuard,
759
1185
  drainQueuedTaskMessages,
760
1186
  formatQueuedTaskMessages,
1187
+ resolveTaskCompletion,
761
1188
  };
762
1189
  //# sourceMappingURL=agent-teams.js.map