@agent-native/core 0.32.1 → 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.
- package/README.md +3 -1
- package/dist/agent/run-store.d.ts.map +1 -1
- package/dist/agent/run-store.js +48 -10
- package/dist/agent/run-store.js.map +1 -1
- package/dist/agent/thread-data-builder.d.ts +12 -0
- package/dist/agent/thread-data-builder.d.ts.map +1 -1
- package/dist/agent/thread-data-builder.js +104 -6
- package/dist/agent/thread-data-builder.js.map +1 -1
- package/dist/cli/app-skill.js +2 -2
- package/dist/cli/app-skill.js.map +1 -1
- package/dist/cli/code-agent-executor.d.ts.map +1 -1
- package/dist/cli/code-agent-executor.js +6 -1
- package/dist/cli/code-agent-executor.js.map +1 -1
- package/dist/cli/code-agent-output-smoother.d.ts +7 -0
- package/dist/cli/code-agent-output-smoother.d.ts.map +1 -0
- package/dist/cli/code-agent-output-smoother.js +111 -0
- package/dist/cli/code-agent-output-smoother.js.map +1 -0
- package/dist/cli/connect.d.ts.map +1 -1
- package/dist/cli/connect.js +5 -0
- package/dist/cli/connect.js.map +1 -1
- package/dist/cli/migrate.d.ts.map +1 -1
- package/dist/cli/migrate.js +17 -42
- package/dist/cli/migrate.js.map +1 -1
- package/dist/cli/skills.d.ts +23 -2
- package/dist/cli/skills.d.ts.map +1 -1
- package/dist/cli/skills.js +405 -41
- package/dist/cli/skills.js.map +1 -1
- package/dist/cli/templates-meta.d.ts.map +1 -1
- package/dist/cli/templates-meta.js +7 -105
- package/dist/cli/templates-meta.js.map +1 -1
- package/dist/client/AgentPanel.d.ts.map +1 -1
- package/dist/client/AgentPanel.js +41 -7
- package/dist/client/AgentPanel.js.map +1 -1
- package/dist/client/AgentTaskCard.d.ts.map +1 -1
- package/dist/client/AgentTaskCard.js +0 -28
- package/dist/client/AgentTaskCard.js.map +1 -1
- package/dist/client/AssistantChat.d.ts +8 -23
- package/dist/client/AssistantChat.d.ts.map +1 -1
- package/dist/client/AssistantChat.js +359 -205
- package/dist/client/AssistantChat.js.map +1 -1
- package/dist/client/MultiTabAssistantChat.d.ts.map +1 -1
- package/dist/client/MultiTabAssistantChat.js +254 -14
- package/dist/client/MultiTabAssistantChat.js.map +1 -1
- package/dist/client/agent-chat-adapter.d.ts.map +1 -1
- package/dist/client/agent-chat-adapter.js +14 -9
- package/dist/client/agent-chat-adapter.js.map +1 -1
- package/dist/client/agent-chat.d.ts +24 -0
- package/dist/client/agent-chat.d.ts.map +1 -1
- package/dist/client/agent-chat.js +73 -0
- package/dist/client/agent-chat.js.map +1 -1
- package/dist/client/assistant-ui-recovery.d.ts +34 -0
- package/dist/client/assistant-ui-recovery.d.ts.map +1 -0
- package/dist/client/assistant-ui-recovery.js +122 -0
- package/dist/client/assistant-ui-recovery.js.map +1 -0
- package/dist/client/composer/PromptComposer.d.ts.map +1 -1
- package/dist/client/composer/PromptComposer.js +7 -1
- package/dist/client/composer/PromptComposer.js.map +1 -1
- package/dist/client/composer/TiptapComposer.d.ts +7 -1
- package/dist/client/composer/TiptapComposer.d.ts.map +1 -1
- package/dist/client/composer/TiptapComposer.js +22 -2
- package/dist/client/composer/TiptapComposer.js.map +1 -1
- package/dist/client/frame-protocol.d.ts +6 -2
- package/dist/client/frame-protocol.d.ts.map +1 -1
- package/dist/client/frame-protocol.js.map +1 -1
- package/dist/client/index.d.ts +2 -1
- package/dist/client/index.d.ts.map +1 -1
- package/dist/client/index.js +2 -1
- package/dist/client/index.js.map +1 -1
- package/dist/client/org/OrgSwitcher.d.ts.map +1 -1
- package/dist/client/org/OrgSwitcher.js +2 -1
- package/dist/client/org/OrgSwitcher.js.map +1 -1
- package/dist/client/progress/RunsTray.d.ts +13 -3
- package/dist/client/progress/RunsTray.d.ts.map +1 -1
- package/dist/client/progress/RunsTray.js +105 -36
- package/dist/client/progress/RunsTray.js.map +1 -1
- package/dist/client/route-warmup.d.ts +61 -0
- package/dist/client/route-warmup.d.ts.map +1 -0
- package/dist/client/route-warmup.js +456 -0
- package/dist/client/route-warmup.js.map +1 -0
- package/dist/client/settings/SettingsPanel.d.ts.map +1 -1
- package/dist/client/settings/SettingsPanel.js +2 -1
- package/dist/client/settings/SettingsPanel.js.map +1 -1
- package/dist/client/settings/useBuilderStatus.d.ts +5 -0
- package/dist/client/settings/useBuilderStatus.d.ts.map +1 -1
- package/dist/client/settings/useBuilderStatus.js +10 -4
- package/dist/client/settings/useBuilderStatus.js.map +1 -1
- package/dist/client/use-action.d.ts +1 -0
- package/dist/client/use-action.d.ts.map +1 -1
- package/dist/client/use-action.js +22 -4
- package/dist/client/use-action.js.map +1 -1
- package/dist/code-agents/background-run.d.ts +2 -0
- package/dist/code-agents/background-run.d.ts.map +1 -1
- package/dist/code-agents/background-run.js.map +1 -1
- package/dist/db/client.d.ts +1 -1
- package/dist/db/client.d.ts.map +1 -1
- package/dist/db/client.js +25 -1
- package/dist/db/client.js.map +1 -1
- package/dist/deploy/build.d.ts +4 -0
- package/dist/deploy/build.d.ts.map +1 -1
- package/dist/deploy/build.js +171 -14
- package/dist/deploy/build.js.map +1 -1
- package/dist/deploy/immutable-assets.d.ts +1 -0
- package/dist/deploy/immutable-assets.d.ts.map +1 -1
- package/dist/deploy/immutable-assets.js +1 -0
- package/dist/deploy/immutable-assets.js.map +1 -1
- package/dist/index.browser.d.ts +1 -1
- package/dist/index.browser.d.ts.map +1 -1
- package/dist/index.browser.js +1 -1
- package/dist/index.browser.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/mcp/connect-route.d.ts.map +1 -1
- package/dist/mcp/connect-route.js +118 -82
- package/dist/mcp/connect-route.js.map +1 -1
- package/dist/progress/routes.d.ts.map +1 -1
- package/dist/progress/routes.js +1 -0
- package/dist/progress/routes.js.map +1 -1
- package/dist/progress/store.d.ts +13 -0
- package/dist/progress/store.d.ts.map +1 -1
- package/dist/progress/store.js +18 -0
- package/dist/progress/store.js.map +1 -1
- package/dist/progress/types.d.ts +2 -0
- package/dist/progress/types.d.ts.map +1 -1
- package/dist/progress/types.js.map +1 -1
- package/dist/scripts/db/wipe-leaked-builder-keys.d.ts +2 -2
- package/dist/scripts/db/wipe-leaked-builder-keys.d.ts.map +1 -1
- package/dist/scripts/db/wipe-leaked-builder-keys.js +14 -3
- package/dist/scripts/db/wipe-leaked-builder-keys.js.map +1 -1
- package/dist/server/action-routes.d.ts +1 -0
- package/dist/server/action-routes.d.ts.map +1 -1
- package/dist/server/action-routes.js +36 -2
- package/dist/server/action-routes.js.map +1 -1
- package/dist/server/agent-chat-plugin.d.ts.map +1 -1
- package/dist/server/agent-chat-plugin.js +123 -25
- package/dist/server/agent-chat-plugin.js.map +1 -1
- package/dist/server/agent-discovery.d.ts.map +1 -1
- package/dist/server/agent-discovery.js +14 -1
- package/dist/server/agent-discovery.js.map +1 -1
- package/dist/server/agent-teams-run-queue.d.ts +80 -0
- package/dist/server/agent-teams-run-queue.d.ts.map +1 -0
- package/dist/server/agent-teams-run-queue.js +208 -0
- package/dist/server/agent-teams-run-queue.js.map +1 -0
- package/dist/server/agent-teams.d.ts +67 -0
- package/dist/server/agent-teams.d.ts.map +1 -1
- package/dist/server/agent-teams.js +607 -180
- package/dist/server/agent-teams.js.map +1 -1
- package/dist/server/auth-marketing.d.ts.map +1 -1
- package/dist/server/auth-marketing.js +0 -64
- package/dist/server/auth-marketing.js.map +1 -1
- package/dist/server/auth.d.ts.map +1 -1
- package/dist/server/auth.js +67 -14
- package/dist/server/auth.js.map +1 -1
- package/dist/server/builder-browser.d.ts +12 -2
- package/dist/server/builder-browser.d.ts.map +1 -1
- package/dist/server/builder-browser.js +24 -0
- package/dist/server/builder-browser.js.map +1 -1
- package/dist/server/core-routes-plugin.d.ts.map +1 -1
- package/dist/server/core-routes-plugin.js +66 -5
- package/dist/server/core-routes-plugin.js.map +1 -1
- package/dist/server/credential-provider.d.ts +10 -0
- package/dist/server/credential-provider.d.ts.map +1 -1
- package/dist/server/credential-provider.js +82 -3
- package/dist/server/credential-provider.js.map +1 -1
- package/dist/server/csrf.d.ts.map +1 -1
- package/dist/server/csrf.js +3 -0
- package/dist/server/csrf.js.map +1 -1
- package/dist/server/index.d.ts +1 -0
- package/dist/server/index.d.ts.map +1 -1
- package/dist/server/index.js +1 -0
- package/dist/server/index.js.map +1 -1
- package/dist/server/onboarding-html.d.ts +1 -0
- package/dist/server/onboarding-html.d.ts.map +1 -1
- package/dist/server/onboarding-html.js +14 -1
- package/dist/server/onboarding-html.js.map +1 -1
- package/dist/server/self-dispatch.d.ts +44 -0
- package/dist/server/self-dispatch.d.ts.map +1 -0
- package/dist/server/self-dispatch.js +113 -0
- package/dist/server/self-dispatch.js.map +1 -0
- package/dist/server/social-og-image.d.ts +14 -0
- package/dist/server/social-og-image.d.ts.map +1 -0
- package/dist/server/social-og-image.js +251 -0
- package/dist/server/social-og-image.js.map +1 -0
- package/dist/server/ssr-handler.d.ts +1 -1
- package/dist/server/ssr-handler.d.ts.map +1 -1
- package/dist/server/ssr-handler.js +27 -11
- package/dist/server/ssr-handler.js.map +1 -1
- package/dist/shared/cache-control.d.ts +7 -0
- package/dist/shared/cache-control.d.ts.map +1 -1
- package/dist/shared/cache-control.js +7 -0
- package/dist/shared/cache-control.js.map +1 -1
- package/dist/shared/index.d.ts +1 -1
- package/dist/shared/index.d.ts.map +1 -1
- package/dist/shared/index.js +1 -1
- package/dist/shared/index.js.map +1 -1
- package/dist/shared/route-warmup-config.d.ts +28 -0
- package/dist/shared/route-warmup-config.d.ts.map +1 -0
- package/dist/shared/route-warmup-config.js +58 -0
- package/dist/shared/route-warmup-config.js.map +1 -0
- package/dist/shared/social-meta.d.ts +5 -0
- package/dist/shared/social-meta.d.ts.map +1 -1
- package/dist/shared/social-meta.js +36 -2
- package/dist/shared/social-meta.js.map +1 -1
- package/dist/shared/streaming-text-smoothing.d.ts +12 -0
- package/dist/shared/streaming-text-smoothing.d.ts.map +1 -0
- package/dist/shared/streaming-text-smoothing.js +52 -0
- package/dist/shared/streaming-text-smoothing.js.map +1 -0
- package/dist/styles/agent-native.css +4 -4
- package/dist/templates/default/AGENTS.md +9 -4
- package/dist/templates/default/DEVELOPING.md +15 -1
- package/dist/templates/workspace-core/AGENTS.md +7 -3
- package/dist/templates/workspace-root/AGENTS.md +7 -3
- package/dist/vite/client.d.ts +13 -0
- package/dist/vite/client.d.ts.map +1 -1
- package/dist/vite/client.js +36 -1
- package/dist/vite/client.js.map +1 -1
- package/dist/vite/index.d.ts +1 -0
- package/dist/vite/index.d.ts.map +1 -1
- package/dist/vite/index.js.map +1 -1
- package/docs/content/client.md +62 -1
- package/docs/content/code-agents-ui.md +6 -13
- package/docs/content/context-awareness.md +186 -21
- package/docs/content/deployment.md +8 -11
- package/docs/content/dispatch.md +1 -1
- package/docs/content/external-agents.md +32 -2
- package/docs/content/migration-workbench.md +4 -21
- package/docs/content/multi-app-workspace.md +1 -1
- package/docs/content/recurring-jobs.md +1 -1
- package/docs/content/security.md +0 -1
- package/docs/content/sharing.md +1 -3
- package/docs/content/skills-guide.md +12 -10
- package/docs/content/template-assets.md +21 -1
- package/docs/content/template-design.md +23 -5
- package/docs/content/template-dispatch.md +1 -1
- package/package.json +2 -1
- package/src/templates/default/AGENTS.md +9 -4
- package/src/templates/default/DEVELOPING.md +15 -1
- package/src/templates/workspace-core/AGENTS.md +7 -3
- 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
|
|
504
|
+
phase,
|
|
234
505
|
createdAt,
|
|
235
|
-
updatedAt
|
|
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
|
|
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
|
-
//
|
|
422
|
-
//
|
|
423
|
-
//
|
|
424
|
-
|
|
425
|
-
|
|
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
|
|
439
|
-
if (
|
|
440
|
-
|
|
441
|
-
}
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
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
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
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
|
-
|
|
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
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
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
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
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 {
|
|
576
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|