@sentry/junior 0.242.0 → 0.244.0

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 (67) hide show
  1. package/dist/{admin-C54kvHVC.js → admin-CF9lQg0M.js} +1 -1
  2. package/dist/{agent-hooks-BVttjZvb.js → agent-hooks-Cj_rSeWd.js} +22 -12
  3. package/dist/agent-invocations-BO6ddZax.js +177 -0
  4. package/dist/{agent-runner-BTgnxJ7g.js → agent-runner-DtEtbaOb.js} +646 -70
  5. package/dist/api/schema.d.ts +67 -1
  6. package/dist/api/schema.js +4 -4
  7. package/dist/api.js +53 -22
  8. package/dist/{app-MEd7wbF0.d.ts → app-DRLh-8zs.d.ts} +5 -0
  9. package/dist/app.d.ts +1 -1
  10. package/dist/app.js +41 -24
  11. package/dist/{briefs-CKUsv9F1.js → briefs-BMzTiGJO.js} +4 -4
  12. package/dist/{catalog-runtime-C2YkfMZz.js → catalog-runtime-CpxhLwZG.js} +1 -1
  13. package/dist/cli/chat.js +12 -12
  14. package/dist/cli/check.js +3 -3
  15. package/dist/cli/main.js +2 -2
  16. package/dist/cli/plugins.js +4 -4
  17. package/dist/cli/snapshot-create.js +3 -3
  18. package/dist/cli/upgrade.js +3 -3
  19. package/dist/{client-D51G-Jdg.js → client-BVaGEb1-.js} +2 -2
  20. package/dist/{config-C6Uu3Re9.js → config-q9xXVN_5.js} +7 -2
  21. package/dist/{conversation-DwD-vquD.js → conversation-BxvgH64Q.js} +5 -1
  22. package/dist/{conversation-privacy-CzL47nXs.js → conversation-privacy-Dyh_63Ee.js} +1 -1
  23. package/dist/{credential-sync-DvDaE-Uj.js → credential-sync-Bsn-6m16.js} +14 -14
  24. package/dist/{executor-CGUukwus.js → executor-DA7eHhmC.js} +5 -124
  25. package/dist/{history-BLTUgFYO.js → history-Nte1oVed.js} +4 -0
  26. package/dist/{images-DSxZt5vk.js → images-CvMreau5.js} +3 -3
  27. package/dist/instrumentation.js +2 -2
  28. package/dist/{job-queue-hLylM9cy.js → job-queue-mVyF9jgv.js} +1 -1
  29. package/dist/{logging-BdZ_mK3q.js → logging-z7_w-KN_.js} +1 -1
  30. package/dist/nitro.d.ts +1 -1
  31. package/dist/nitro.js +3 -3
  32. package/dist/{oauth-authorization-BokpeHv-.js → oauth-authorization-Db1yGVvG.js} +1 -1
  33. package/dist/{oauth-callback-server-C3BOSmT_.js → oauth-callback-server-_MfOtSxS.js} +2 -2
  34. package/dist/{pending-auth-DOzL4D24.js → pending-auth-OndKPW4M.js} +2 -2
  35. package/dist/{profile-2gPzzMib.js → profile-yeKVNECl.js} +4 -4
  36. package/dist/{prompt-CMU-B4FA.js → prompt-CX36ZlbV.js} +1 -1
  37. package/dist/{registry-XJkXDNK2.js → registry-DvzzL8-v.js} +1 -1
  38. package/dist/{resolve-DhcdtDt6.js → resolve-4u5gEU04.js} +2 -2
  39. package/dist/{runner-CGyrnIPm.js → runner-Y6A6pww4.js} +10 -10
  40. package/dist/{sandbox-egress-signals-D6F3OZJP.js → sandbox-egress-signals-D7AOkuoe.js} +1 -1
  41. package/dist/{schema-Cabm4jcj.js → schema-Dyyi9oRc.js} +35 -3
  42. package/dist/{skills-C6PqcBPU.js → skills-DFduSvuO.js} +2 -2
  43. package/dist/{spawn-DL9x3T1o.js → spawn-DYg7jhhR.js} +7 -5
  44. package/dist/{task-queue-vZnBGu9B.js → task-queue-BQ0vnumm.js} +3 -3
  45. package/dist/{task-runner-C-c0t5F5.js → task-runner-D4jvUpFV.js} +12 -11
  46. package/dist/{tasks-EqCtmUJ2.js → tasks-DBZAgTx7.js} +49 -17
  47. package/dist/{thread-state-B8IKs_J8.js → thread-state-hj70P2Cp.js} +7 -7
  48. package/dist/{turn-failure-response-BS7OO1BE.js → turn-failure-response-S2pwtR8b.js} +9 -9
  49. package/dist/{turn-lifecycle-Cxrt8qMD.js → turn-lifecycle-CNFOMAJq.js} +1 -1
  50. package/dist/{turn-session-routing-BlGt2AUb.js → turn-session-routing-1eTgv6ns.js} +1 -1
  51. package/dist/{validation-C8hci6pj.js → validation-DcsBjzdi.js} +1 -1
  52. package/dist/{version-CjnHQ8aY.js → version-DVCHJYcY.js} +1 -1
  53. package/dist/version.js +1 -1
  54. package/dist/{viewer-BOgEy5zC.js → viewer-BBnCMR1g.js} +6 -8
  55. package/dist/{web-authorization-DSMV5tMK.js → web-authorization-CTltRLsB.js} +6 -6
  56. package/dist/{web-input-TD5P9jdo.js → web-input-CIsRGF07.js} +227 -31
  57. package/dist/{work-D39OAwP1.js → work-fje9VXgQ.js} +111 -225
  58. package/dist/{worker-CWiLgCWH.js → worker-Byd2Et9r.js} +5 -5
  59. package/migrations/0050_agent_invocation_parent_mailbox.sql +4 -0
  60. package/migrations/0051_agent_invocation_parent_tool_call.sql +1 -0
  61. package/migrations/0052_agent_invocation_title.sql +1 -0
  62. package/migrations/meta/0050_snapshot.json +3753 -0
  63. package/migrations/meta/0051_snapshot.json +3759 -0
  64. package/migrations/meta/0052_snapshot.json +3765 -0
  65. package/migrations/meta/_journal.json +22 -1
  66. package/package.json +5 -5
  67. package/dist/destinations-BeBiJeEM.js +0 -29
@@ -1,204 +1,11 @@
1
1
  import { t as __exportAll } from "./rolldown-runtime-B4FMCO8f.js";
2
- import { G as AgentInvocationBusyError, H as agentBindingSchema, K as AgentInvocationLimitError, U as agentInvocationSchema, W as createAgentInvocationSchema, it as appendAndEnqueueInboundMessage, m as PluginCredentialFailureError, tn as AuthorizationFlowDisabledError } from "./agent-runner-BTgnxJ7g.js";
3
- import { c as getConversationEventStore, f as getSqlExecutor, u as getConversationStore } from "./viewer-BOgEy5zC.js";
4
- import { m as juniorAgentInvocations, p as juniorAgentBindings } from "./executor-CGUukwus.js";
5
- import { Dt as openConversationProjection, K as saveTurnCheckpoint, X as getTurnRecord, Y as failTurnRecord, ot as getTerminalAssistantMessages } from "./tasks-EqCtmUJ2.js";
6
- import { d as getConversationTurnBoundaryError, l as TurnInputCommitLostError, p as isTurnInputCommitLostError, r as getAssistantReplyText } from "./oauth-authorization-BokpeHv-.js";
7
- import { t as ConversationTurnLifecycleService } from "./turn-lifecycle-Cxrt8qMD.js";
8
- import { n as getPersistedSandboxState, o as persistThreadStateById, r as getPersistedThreadState } from "./thread-state-B8IKs_J8.js";
2
+ import { $ as getLatestAgentInvocationForConversation, G as completeAgentInvocation, H as stopChildAgentsOf, J as getActiveAgentInvocationForConversation, K as createAgentInvocation, Q as getAgentInvocationTurnId, U as recordSubagentEnded, W as recordSubagentStarted, X as getAgentInvocationMessageId, Y as getAgentInvocation, _n as AuthorizationFlowDisabledError, bt as appendAndEnqueueInboundMessage, ct as reportReplacedAgentInvocation, et as isTerminalAgentInvocation, it as markAgentInvocationMailboxAppended, m as PluginCredentialFailureError, ot as markAgentInvocationPaused, st as markAgentInvocationRunning } from "./agent-runner-DtEtbaOb.js";
3
+ import { c as getConversationEventStore, u as getConversationStore } from "./viewer-BBnCMR1g.js";
4
+ import { K as saveTurnCheckpoint, Ot as openConversationProjection, X as getTurnRecord, Y as failTurnRecord, ot as getTerminalAssistantMessages, q as abandonTurnRecord } from "./tasks-DBZAgTx7.js";
5
+ import { d as getConversationTurnBoundaryError, l as TurnInputCommitLostError, p as isTurnInputCommitLostError, r as getAssistantReplyText } from "./oauth-authorization-Db1yGVvG.js";
6
+ import { t as ConversationTurnLifecycleService } from "./turn-lifecycle-CNFOMAJq.js";
7
+ import { n as getPersistedSandboxState, o as persistThreadStateById, r as getPersistedThreadState } from "./thread-state-hj70P2Cp.js";
9
8
  import { z } from "zod";
10
- import { createHash } from "node:crypto";
11
- import { and, asc, count, eq, inArray } from "drizzle-orm";
12
- //#region src/chat/agent-invocations/store.ts
13
- const CREATE_LOCK_PREFIX = "junior:agent_invocation:create";
14
- const TERMINAL_AGENT_INVOCATION_STATUSES = [
15
- "blocked",
16
- "completed",
17
- "failed"
18
- ];
19
- const NON_TERMINAL_AGENT_INVOCATION_STATUSES = [
20
- "pending",
21
- "running",
22
- "awaiting_resume"
23
- ];
24
- function stableId(prefix, ...parts) {
25
- return `${prefix}:${createHash("sha256").update(parts.join("\0")).digest("hex").slice(0, 32)}`;
26
- }
27
- /** Return the stable identity for one retry-safe delegated task. */
28
- function getAgentInvocationId(parentConversationId, idempotencyKey) {
29
- return stableId("agent-invocation", parentConversationId, idempotencyKey);
30
- }
31
- /** Return the stable child identity for one invocation without a named binding. */
32
- function getUnnamedAgentConversationId(invocationId) {
33
- return stableId("agent", invocationId);
34
- }
35
- /** Return the stable child identity for one named binding. */
36
- function getNamedAgentConversationId(parentConversationId, name) {
37
- return stableId("agent", parentConversationId, name);
38
- }
39
- /** Return the stable turn identity advanced by one agent invocation. */
40
- function getAgentInvocationTurnId(invocationId) {
41
- return `agent-invocation:${invocationId}`;
42
- }
43
- /** Return the stable mailbox identity for one agent invocation. */
44
- function getAgentInvocationMessageId(invocationId) {
45
- return `agent-invocation:${invocationId}:input`;
46
- }
47
- function bindingFromRow(row) {
48
- return agentBindingSchema.parse({
49
- childConversationId: row.childConversationId,
50
- name: row.name,
51
- parentConversationId: row.parentConversationId
52
- });
53
- }
54
- function invocationFromRow(row) {
55
- return agentInvocationSchema.parse({
56
- actor: row.actor,
57
- ...row.agentName ? { agentName: row.agentName } : void 0,
58
- childConversationId: row.childConversationId,
59
- createdAtMs: row.createdAt.getTime(),
60
- ...row.credentialContext ? { credentialContext: row.credentialContext } : void 0,
61
- destination: row.destination,
62
- ...row.errorMessage !== null ? { errorMessage: row.errorMessage } : void 0,
63
- input: row.input,
64
- invocationId: row.invocationId,
65
- mailboxStatus: row.mailboxStatus,
66
- parentConversationId: row.parentConversationId,
67
- ...row.reasoningLevel ? { reasoningLevel: row.reasoningLevel } : void 0,
68
- ...row.result !== null ? { result: row.result } : void 0,
69
- source: row.source,
70
- status: row.status,
71
- ...row.terminalAt ? { terminalAtMs: row.terminalAt.getTime() } : void 0,
72
- updatedAtMs: row.updatedAt.getTime()
73
- });
74
- }
75
- /** Require every durable creation input to match before replaying one key. */
76
- function sameCreateInput(invocation, input) {
77
- return invocation.parentConversationId === input.parentConversationId && invocation.agentName === input.agentName && invocation.input === input.input && invocation.reasoningLevel === input.reasoningLevel && JSON.stringify(invocation.actor) === JSON.stringify(input.actor) && JSON.stringify(invocation.credentialContext) === JSON.stringify(input.credentialContext) && JSON.stringify(invocation.destination) === JSON.stringify(input.destination);
78
- }
79
- /** Read one named child binding in its parent-agent scope. */
80
- async function getAgentBinding(args) {
81
- const rows = await getSqlExecutor().db().select().from(juniorAgentBindings).where(and(eq(juniorAgentBindings.parentConversationId, args.parentConversationId), eq(juniorAgentBindings.name, args.name)));
82
- return rows[0] ? bindingFromRow(rows[0]) : void 0;
83
- }
84
- /** Read one durable agent invocation. */
85
- async function getAgentInvocation(invocationId) {
86
- const rows = await getSqlExecutor().db().select().from(juniorAgentInvocations).where(eq(juniorAgentInvocations.invocationId, invocationId));
87
- return rows[0] ? invocationFromRow(rows[0]) : void 0;
88
- }
89
- /** Resolve the single invocation that may resume for one child conversation. */
90
- async function getActiveAgentInvocationForConversation(childConversationId) {
91
- const rows = await getSqlExecutor().db().select().from(juniorAgentInvocations).where(and(eq(juniorAgentInvocations.childConversationId, childConversationId), inArray(juniorAgentInvocations.status, ["running", "awaiting_resume"]))).orderBy(asc(juniorAgentInvocations.createdAt)).limit(2);
92
- if (rows.length > 1) throw new Error(`Child conversation ${childConversationId} has multiple active agent invocations`);
93
- return rows[0] ? invocationFromRow(rows[0]) : void 0;
94
- }
95
- async function getNonTerminalAgentInvocationForConversation(childConversationId) {
96
- const rows = await getSqlExecutor().db().select().from(juniorAgentInvocations).where(and(eq(juniorAgentInvocations.childConversationId, childConversationId), inArray(juniorAgentInvocations.status, NON_TERMINAL_AGENT_INVOCATION_STATUSES))).orderBy(asc(juniorAgentInvocations.createdAt)).limit(1);
97
- return rows[0] ? invocationFromRow(rows[0]) : void 0;
98
- }
99
- async function countNonTerminalAgentInvocationsForParent(parentConversationId) {
100
- return (await getSqlExecutor().db().select({ value: count() }).from(juniorAgentInvocations).where(and(eq(juniorAgentInvocations.parentConversationId, parentConversationId), inArray(juniorAgentInvocations.status, NON_TERMINAL_AGENT_INVOCATION_STATUSES))))[0]?.value ?? 0;
101
- }
102
- /**
103
- * Create or replay one invocation, reusing named child conversations and
104
- * keeping unnamed child identities scoped to the invocation.
105
- */
106
- async function createAgentInvocation(rawInput, nowMs = Date.now()) {
107
- const input = createAgentInvocationSchema.parse(rawInput);
108
- const invocationId = getAgentInvocationId(input.parentConversationId, input.idempotencyKey);
109
- const childConversationId = input.agentName ? getNamedAgentConversationId(input.parentConversationId, input.agentName) : getUnnamedAgentConversationId(invocationId);
110
- const lockName = `${CREATE_LOCK_PREFIX}:${input.parentConversationId}`;
111
- return await getSqlExecutor().withLock(lockName, async () => {
112
- const existing = await getAgentInvocation(invocationId);
113
- if (existing) {
114
- if (!sameCreateInput(existing, input)) throw new Error(`Agent invocation idempotency key was reused with different input for ${invocationId}`);
115
- return existing;
116
- }
117
- if (await countNonTerminalAgentInvocationsForParent(input.parentConversationId) >= 8) throw new AgentInvocationLimitError(8);
118
- await getConversationStore().createChild({
119
- childConversationId,
120
- parentConversationId: input.parentConversationId,
121
- nowMs,
122
- source: "internal"
123
- });
124
- if (input.agentName) {
125
- await getSqlExecutor().db().insert(juniorAgentBindings).values({
126
- childConversationId,
127
- name: input.agentName,
128
- parentConversationId: input.parentConversationId
129
- }).onConflictDoNothing();
130
- const binding = await getAgentBinding({
131
- name: input.agentName,
132
- parentConversationId: input.parentConversationId
133
- });
134
- if (!binding || binding.childConversationId !== childConversationId) throw new Error(`Named agent binding did not resolve to ${childConversationId}`);
135
- if (await getNonTerminalAgentInvocationForConversation(childConversationId)) throw new AgentInvocationBusyError(input.agentName);
136
- }
137
- await getSqlExecutor().db().insert(juniorAgentInvocations).values({
138
- invocationId,
139
- parentConversationId: input.parentConversationId,
140
- childConversationId,
141
- agentName: input.agentName ?? null,
142
- input: input.input,
143
- actor: input.actor,
144
- credentialContext: input.credentialContext ?? null,
145
- source: { kind: "agent_invocation" },
146
- destination: input.destination,
147
- reasoningLevel: input.reasoningLevel ?? null,
148
- status: "pending",
149
- mailboxStatus: "pending",
150
- createdAt: new Date(nowMs),
151
- updatedAt: new Date(nowMs),
152
- terminalAt: null
153
- }).onConflictDoNothing();
154
- const invocation = await getAgentInvocation(invocationId);
155
- if (!invocation || !sameCreateInput(invocation, input)) throw new Error(`Agent invocation creation raced for ${invocationId}`);
156
- return invocation;
157
- });
158
- }
159
- /** List bounded invocation records whose mailbox append still needs repair. */
160
- async function listPendingAgentInvocationMailboxAppends(limit = 100) {
161
- return (await getSqlExecutor().db().select().from(juniorAgentInvocations).where(eq(juniorAgentInvocations.mailboxStatus, "pending")).orderBy(asc(juniorAgentInvocations.createdAt)).limit(limit)).map(invocationFromRow);
162
- }
163
- /** Record that the invocation's idempotent mailbox append completed. */
164
- async function markAgentInvocationMailboxAppended(invocationId, nowMs = Date.now()) {
165
- await getSqlExecutor().db().update(juniorAgentInvocations).set({
166
- mailboxStatus: "appended",
167
- updatedAt: new Date(nowMs)
168
- }).where(eq(juniorAgentInvocations.invocationId, invocationId));
169
- }
170
- /** Mark one non-terminal invocation as actively executing. */
171
- async function markAgentInvocationRunning(invocationId, nowMs = Date.now()) {
172
- await getSqlExecutor().db().update(juniorAgentInvocations).set({
173
- status: "running",
174
- updatedAt: new Date(nowMs)
175
- }).where(and(eq(juniorAgentInvocations.invocationId, invocationId), inArray(juniorAgentInvocations.status, NON_TERMINAL_AGENT_INVOCATION_STATUSES)));
176
- return await getAgentInvocation(invocationId);
177
- }
178
- /** Mark one invocation as waiting for another shared execution slice. */
179
- async function markAgentInvocationAwaitingResume(invocationId, nowMs = Date.now()) {
180
- await getSqlExecutor().db().update(juniorAgentInvocations).set({
181
- status: "awaiting_resume",
182
- updatedAt: new Date(nowMs)
183
- }).where(and(eq(juniorAgentInvocations.invocationId, invocationId), inArray(juniorAgentInvocations.status, NON_TERMINAL_AGENT_INVOCATION_STATUSES)));
184
- }
185
- /** Persist one terminal invocation result without overwriting an earlier result. */
186
- async function completeAgentInvocation(args) {
187
- const nowMs = args.nowMs ?? Date.now();
188
- await getSqlExecutor().db().update(juniorAgentInvocations).set({
189
- status: args.status,
190
- result: args.status === "completed" ? args.result : null,
191
- errorMessage: args.status === "completed" ? null : args.errorMessage,
192
- terminalAt: new Date(nowMs),
193
- updatedAt: new Date(nowMs)
194
- }).where(and(eq(juniorAgentInvocations.invocationId, args.invocationId), inArray(juniorAgentInvocations.status, NON_TERMINAL_AGENT_INVOCATION_STATUSES)));
195
- return await getAgentInvocation(args.invocationId);
196
- }
197
- /** Return whether an agent invocation has finished. */
198
- function isTerminalAgentInvocation(invocation) {
199
- return TERMINAL_AGENT_INVOCATION_STATUSES.includes(invocation.status);
200
- }
201
- //#endregion
202
9
  //#region src/chat/runtime/turn-execution.ts
203
10
  /** An error thrown while the agent advances a Run. */
204
11
  var AgentRunError = class extends Error {
@@ -275,7 +82,8 @@ function buildAgentInvocationInboundMessage(invocation, nowMs = Date.now()) {
275
82
  }
276
83
  /** Append one invocation to its child mailbox and send the normal queue wake. */
277
84
  async function enqueueAgentInvocation(invocation, options) {
278
- if (invocation.mailboxStatus === "appended") return;
85
+ if (invocation.mailboxStatus === "appended" || isTerminalAgentInvocation(invocation)) return;
86
+ await recordSubagentStarted(invocation);
279
87
  const nowMs = options.nowMs ?? Date.now();
280
88
  await appendAndEnqueueInboundMessage({
281
89
  message: buildAgentInvocationInboundMessage(invocation, nowMs),
@@ -310,7 +118,10 @@ async function resolveAgentInvocationId(context) {
310
118
  const mailboxInvocationId = invocationIdFromMessages(context.attempt.messages);
311
119
  if (mailboxInvocationId) return mailboxInvocationId;
312
120
  if (context.attempt.messages.length > 0) return;
313
- return (await getActiveAgentInvocationForConversation(context.conversationId))?.invocationId;
121
+ const active = await getActiveAgentInvocationForConversation(context.conversationId);
122
+ if (active) return active.invocationId;
123
+ const latest = await getLatestAgentInvocationForConversation(context.conversationId);
124
+ return latest?.status === "stopped" ? latest.invocationId : void 0;
314
125
  }
315
126
  /** Project the terminal invocation into the idempotent turn lifecycle. */
316
127
  async function persistTerminalLifecycle(invocation) {
@@ -324,7 +135,17 @@ async function persistTerminalLifecycle(invocation) {
324
135
  ...common,
325
136
  outcome: invocation.result?.trim() ? "success" : "no_reply"
326
137
  });
327
- else if (invocation.status === "blocked" || invocation.status === "failed") await lifecycle.fail({
138
+ else if (invocation.status === "stopped") {
139
+ await abandonTurnRecord({
140
+ conversationId: common.conversationId,
141
+ errorMessage: "Turn cancelled",
142
+ turnId: common.turnId
143
+ });
144
+ await lifecycle.complete({
145
+ ...common,
146
+ outcome: "cancelled"
147
+ });
148
+ } else if (invocation.status === "blocked" || invocation.status === "failed") await lifecycle.fail({
328
149
  ...common,
329
150
  failureCode: "model_execution_failed"
330
151
  });
@@ -393,20 +214,58 @@ async function saveAgentInvocationResult(args) {
393
214
  }
394
215
  });
395
216
  if (!terminal || !isTerminalAgentInvocation(terminal)) throw new Error(`Agent invocation did not finish for ${args.invocation.invocationId}`);
396
- return terminal.status === "completed" ? {
217
+ if (terminal.status === "completed") return {
397
218
  finishedAtMs: terminal.terminalAtMs,
398
219
  outcome: terminal.result.trim() ? "success" : "no_reply"
399
- } : {
220
+ };
221
+ if (terminal.status === "stopped") return {
222
+ finishedAtMs: terminal.terminalAtMs,
223
+ outcome: "cancelled"
224
+ };
225
+ return {
400
226
  finishedAtMs: terminal.terminalAtMs,
401
227
  failureCode: "model_execution_failed",
402
228
  outcome: "failed"
403
229
  };
404
230
  }
405
- /** Build the invocation consumer that advances work through the shared runner. */
406
- function createAgentInvocationWorker(agentRunner) {
231
+ /**
232
+ * Read how many parents a child Conversation has and the root Conversation's
233
+ * Location. A child of the root has depth 1.
234
+ */
235
+ async function readChildDepthAndRootLocation(conversationId) {
236
+ const conversationStore = getConversationStore();
237
+ let depth = 0;
238
+ let conversation = await conversationStore.get({ conversationId });
239
+ while (conversation?.parentConversationId) {
240
+ depth += 1;
241
+ conversation = await conversationStore.get({ conversationId: conversation.parentConversationId });
242
+ }
243
+ return {
244
+ depth,
245
+ location: conversation?.location
246
+ };
247
+ }
248
+ /**
249
+ * Build the invocation consumer that advances work through the shared runner.
250
+ * `enqueueResult` sends each terminal result to the parent mailbox. Omit it
251
+ * only where the parent has no mailbox, such as the local CLI.
252
+ * `maxAgentDepth` is the number of child agent levels that can start their
253
+ * own child agents. The default, 0, lets only the root start child agents.
254
+ */
255
+ function createAgentInvocationWorker(agentRunner, options = {}) {
256
+ const maxAgentDepth = options.maxAgentDepth ?? 0;
407
257
  return async (context, invocationId) => {
408
258
  let invocation = await getAgentInvocation(invocationId);
409
259
  if (!invocation) throw new Error(`Agent invocation is missing for ${invocationId}`);
260
+ const report = async (terminal) => {
261
+ await recordSubagentEnded(terminal);
262
+ await options.enqueueResult?.(terminal);
263
+ };
264
+ const finish = async (terminal) => {
265
+ if (!terminal || !isTerminalAgentInvocation(terminal)) return;
266
+ await persistTerminalLifecycle(terminal);
267
+ await report(terminal);
268
+ };
410
269
  let acknowledged = context.attempt.messages.length === 0;
411
270
  const acknowledge = async () => {
412
271
  if (acknowledged) return;
@@ -417,26 +276,43 @@ function createAgentInvocationWorker(agentRunner) {
417
276
  }
418
277
  acknowledged = true;
419
278
  };
279
+ const stopSignal = context.stopSignal?.();
280
+ const finishStopped = async () => {
281
+ await finish(await completeAgentInvocation({
282
+ invocationId,
283
+ errorMessage: "Agent invocation was stopped",
284
+ status: "stopped"
285
+ }));
286
+ await stopChildAgentsOf(context.conversationId);
287
+ await acknowledge();
288
+ return { status: "completed" };
289
+ };
420
290
  const turnId = getAgentInvocationTurnId(invocation.invocationId);
421
291
  const lifecycle = new ConversationTurnLifecycleService(getConversationEventStore());
422
292
  let sandboxRef;
423
293
  let history;
424
294
  let location;
295
+ let canSpawn = false;
425
296
  try {
426
297
  if (invocation.childConversationId !== context.conversationId) throw new Error(`Agent invocation ${invocationId} belongs to ${invocation.childConversationId}, not ${context.conversationId}`);
427
298
  if (context.destination) throw new Error(`Agent invocation conversation ${context.conversationId} must not own a provider destination`);
428
299
  if (isTerminalAgentInvocation(invocation)) {
429
- await persistTerminalLifecycle(invocation);
300
+ await finish(invocation);
430
301
  await acknowledge();
431
302
  return { status: "completed" };
432
303
  }
433
304
  const projected = await projectTerminalTurn(invocation);
434
305
  if (projected && isTerminalAgentInvocation(projected)) {
435
- await persistTerminalLifecycle(projected);
306
+ await finish(projected);
436
307
  await acknowledge();
437
308
  return { status: "completed" };
438
309
  }
439
310
  invocation = await markAgentInvocationRunning(invocation.invocationId) ?? invocation;
311
+ if (isTerminalAgentInvocation(invocation)) {
312
+ await finish(invocation);
313
+ await acknowledge();
314
+ return { status: "completed" };
315
+ }
440
316
  await lifecycle.start({
441
317
  conversationId: invocation.childConversationId,
442
318
  createdAtMs: invocation.createdAtMs,
@@ -444,19 +320,23 @@ function createAgentInvocationWorker(agentRunner) {
444
320
  surface: "internal",
445
321
  turnId
446
322
  });
447
- const [persisted, projection] = await Promise.all([getPersistedThreadState(invocation.childConversationId), openConversationProjection({ conversationId: invocation.childConversationId })]);
323
+ const [persisted, projection, tree] = await Promise.all([
324
+ getPersistedThreadState(invocation.childConversationId),
325
+ openConversationProjection({ conversationId: invocation.childConversationId }),
326
+ readChildDepthAndRootLocation(invocation.childConversationId)
327
+ ]);
448
328
  sandboxRef = getPersistedSandboxState(persisted);
449
329
  history = projection.messages;
450
- location = (await getConversationStore().get({ conversationId: invocation.parentConversationId }))?.location;
330
+ location = tree.location;
331
+ canSpawn = tree.depth <= maxAgentDepth;
451
332
  } catch (error) {
452
333
  if (isInvocationInputCommitLost(error)) return { status: "lost_lease" };
453
334
  if (!context.attempt.isFinalAttempt) throw error;
454
- const terminal = await completeAgentInvocation({
335
+ await finish(await completeAgentInvocation({
455
336
  invocationId: invocation.invocationId,
456
337
  errorMessage: error instanceof Error ? error.message : "Agent invocation failed",
457
338
  status: "failed"
458
- });
459
- if (terminal) await persistTerminalLifecycle(terminal);
339
+ }));
460
340
  await acknowledge();
461
341
  return { status: "completed" };
462
342
  }
@@ -474,7 +354,8 @@ function createAgentInvocationWorker(agentRunner) {
474
354
  source: invocation.source,
475
355
  ...location ? { location } : void 0,
476
356
  surface: "internal",
477
- disabledFeatures: [
357
+ ...stopSignal ? { signal: stopSignal } : void 0,
358
+ disabledFeatures: canSpawn ? ["handoff", "interactive-auth"] : [
478
359
  "handoff",
479
360
  "interactive-auth",
480
361
  "subagents"
@@ -499,41 +380,46 @@ function createAgentInvocationWorker(agentRunner) {
499
380
  if (!(error instanceof AgentRunError)) throw error;
500
381
  const runError = error.cause;
501
382
  if (isInvocationInputCommitLost(runError)) return { status: "lost_lease" };
383
+ if (stopSignal?.aborted) return await finishStopped();
502
384
  const blocking = blockingInvocationError(runError);
503
385
  if (blocking) {
504
- const terminal = await completeAgentInvocation({
386
+ await finish(await completeAgentInvocation({
505
387
  invocationId: invocation.invocationId,
506
388
  errorMessage: blocking.message,
507
389
  status: "blocked"
508
- });
509
- if (terminal) await persistTerminalLifecycle(terminal);
390
+ }));
510
391
  await acknowledge();
511
392
  return { status: "completed" };
512
393
  }
513
394
  if (!context.attempt.isFinalAttempt) throw runError;
514
- const terminal = await completeAgentInvocation({
395
+ await finish(await completeAgentInvocation({
515
396
  invocationId: invocation.invocationId,
516
397
  errorMessage: runError instanceof Error ? runError.message : "Agent invocation failed",
517
398
  status: "failed"
518
- });
519
- if (terminal) await persistTerminalLifecycle(terminal);
399
+ }));
520
400
  await acknowledge();
521
401
  return { status: "completed" };
522
402
  }
403
+ if (outcome.status !== "completed" && stopSignal?.aborted) return await finishStopped();
523
404
  if (outcome.status === "suspended") {
524
- await markAgentInvocationAwaitingResume(invocation.invocationId);
405
+ await markAgentInvocationPaused(invocation.invocationId);
525
406
  return { status: "yielded" };
526
407
  }
527
408
  if (outcome.status === "awaiting_auth") {
528
- const terminal = await completeAgentInvocation({
409
+ await finish(await completeAgentInvocation({
529
410
  invocationId: invocation.invocationId,
530
411
  errorMessage: `Agent invocation requires ${outcome.providerDisplayName} authorization`,
531
412
  status: "blocked"
532
- });
533
- if (terminal) await persistTerminalLifecycle(terminal);
413
+ }));
534
414
  await acknowledge();
535
415
  return { status: "completed" };
536
416
  }
417
+ let terminal = await getAgentInvocation(invocation.invocationId);
418
+ if (stopSignal?.aborted) {
419
+ if (terminal && isTerminalAgentInvocation(terminal) && terminal.parentMailboxStatus === "replaced") terminal = await reportReplacedAgentInvocation(terminal.invocationId);
420
+ await stopChildAgentsOf(context.conversationId);
421
+ }
422
+ if (terminal && isTerminalAgentInvocation(terminal)) await report(terminal);
537
423
  await acknowledge();
538
424
  return { status: "completed" };
539
425
  };
@@ -546,4 +432,4 @@ function routeAgentInvocationWork(options) {
546
432
  };
547
433
  }
548
434
  //#endregion
549
- export { work_exports as a, listPendingAgentInvocationMailboxAppends as c, resolveAgentInvocationId as i, createAndEnqueueAgentInvocation as n, AgentRunError as o, enqueueAgentInvocation as r, executeTurn as s, createAgentInvocationWorker as t };
435
+ export { work_exports as a, resolveAgentInvocationId as i, createAndEnqueueAgentInvocation as n, AgentRunError as o, enqueueAgentInvocation as r, executeTurn as s, createAgentInvocationWorker as t };
@@ -1,9 +1,9 @@
1
1
  import { t as __exportAll } from "./rolldown-runtime-B4FMCO8f.js";
2
- import { At as hasConversationStop, Et as startConversationWork, Mt as isFinalAttempt, Nt as isInvalidConversationRecordError, St as releaseConversationWork, _t as getConversationWorkState, at as beginConversationResume, bt as recordAttemptFailure, ct as clearConsumedConversationWake, dt as completeConversationWork, ft as countPendingConversationMessages, ht as ensureConversationWake, mt as drainConversationMailbox, nt as ackMessages, pt as deadLetterAttempt, st as checkInConversationWork, ut as completeConversationStop, wt as requestAnotherSlice, xt as recordConversationRetry } from "./agent-runner-BTgnxJ7g.js";
3
- import { v as isProviderRetryError } from "./client-D51G-Jdg.js";
4
- import { C as withLogContext, d as logException, f as logInfo, p as logWarn } from "./logging-BdZ_mK3q.js";
5
- import { i as getChatConfig } from "./config-C6Uu3Re9.js";
6
- import { f as getTurnRequestDeadline, m as ConversationQueueMessageRejectedError } from "./job-queue-hLylM9cy.js";
2
+ import { At as drainConversationMailbox, Bt as requestAnotherSlice, Ct as checkInConversationWork, Dt as completeConversationWork, Et as completeConversationStop, Ht as startConversationWork, It as recordAttemptFailure, Jt as isFinalAttempt, Kt as hasConversationStop, Lt as recordConversationRetry, Nt as getConversationWorkState, Ot as countPendingConversationMessages, Rt as releaseConversationWork, Yt as isInvalidConversationRecordError, jt as ensureConversationWake, kt as deadLetterAttempt, vt as ackMessages, wt as clearConsumedConversationWake, xt as beginConversationResume } from "./agent-runner-DtEtbaOb.js";
3
+ import { v as isProviderRetryError } from "./client-BVaGEb1-.js";
4
+ import { C as withLogContext, d as logException, f as logInfo, p as logWarn } from "./logging-z7_w-KN_.js";
5
+ import { i as getChatConfig } from "./config-q9xXVN_5.js";
6
+ import { f as getTurnRequestDeadline, m as ConversationQueueMessageRejectedError } from "./job-queue-mVyF9jgv.js";
7
7
  //#region src/chat/task-execution/worker.ts
8
8
  var worker_exports = /* @__PURE__ */ __exportAll({
9
9
  CONVERSATION_WORK_DEFER_DELAY_MS: () => CONVERSATION_WORK_DEFER_DELAY_MS,
@@ -0,0 +1,4 @@
1
+ ALTER TABLE "junior_agent_invocations" ADD COLUMN "parent_mailbox_status" text;--> statement-breakpoint
2
+ CREATE INDEX "junior_agent_invocations_parent_mailbox_idx" ON "junior_agent_invocations" USING btree ("parent_mailbox_status","terminal_at");--> statement-breakpoint
3
+ UPDATE "junior_agent_invocations" SET "status" = 'paused' WHERE "status" = 'awaiting_resume';--> statement-breakpoint
4
+ UPDATE "junior_agent_invocations" SET "parent_mailbox_status" = 'appended' WHERE "status" IN ('blocked', 'completed', 'failed') AND "parent_mailbox_status" IS NULL;
@@ -0,0 +1 @@
1
+ ALTER TABLE "junior_agent_invocations" ADD COLUMN "parent_tool_call_id" text;
@@ -0,0 +1 @@
1
+ ALTER TABLE "junior_agent_invocations" ADD COLUMN "title" text;