@sentry/junior 0.136.1 → 0.138.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 (103) hide show
  1. package/dist/{agent-hooks-2KDSPPO4.js → agent-hooks-SCR74Q3E.js} +11 -9
  2. package/dist/api/conversations/projection.d.ts +1 -0
  3. package/dist/api/conversations/stats.d.ts +1 -0
  4. package/dist/api/schema/conversation.d.ts +2 -0
  5. package/dist/api/schema.js +1 -1
  6. package/dist/api.js +187 -46
  7. package/dist/app.d.ts +8 -1
  8. package/dist/app.js +519 -860
  9. package/dist/{catalog-runtime-WSZQGC4D.js → catalog-runtime-IU3QF4GG.js} +5 -5
  10. package/dist/chat/agent/request.d.ts +32 -2
  11. package/dist/chat/agent-dispatch/heartbeat.d.ts +5 -0
  12. package/dist/chat/agent-dispatch/types.d.ts +1 -1
  13. package/dist/chat/agent-invocations/errors.d.ts +8 -0
  14. package/dist/chat/agent-invocations/spawn.d.ts +12 -0
  15. package/dist/chat/agent-invocations/store.d.ts +44 -0
  16. package/dist/chat/agent-invocations/types.d.ts +449 -0
  17. package/dist/chat/agent-invocations/work.d.ts +31 -0
  18. package/dist/chat/config.d.ts +1 -0
  19. package/dist/chat/configuration/defaults.d.ts +1 -1
  20. package/dist/chat/conversations/sql/store.d.ts +8 -0
  21. package/dist/chat/conversations/store.d.ts +9 -1
  22. package/dist/chat/experimental.d.ts +16 -0
  23. package/dist/chat/pi/client.d.ts +1 -8
  24. package/dist/chat/pi/gateway-auth.d.ts +16 -0
  25. package/dist/chat/runtime/agent-runner.d.ts +2 -1
  26. package/dist/chat/sandbox/docker.d.ts +17 -0
  27. package/dist/chat/services/agent-continue.d.ts +2 -0
  28. package/dist/chat/services/auth-pause.d.ts +0 -1
  29. package/dist/chat/services/mcp-auth-orchestration.d.ts +3 -2
  30. package/dist/chat/services/plugin-auth-orchestration.d.ts +3 -2
  31. package/dist/chat/services/subscribed-reply-router-policy.d.ts +12 -0
  32. package/dist/chat/services/turn-session-routing.d.ts +17 -0
  33. package/dist/chat/slack/message.d.ts +6 -1
  34. package/dist/chat/slack/source-link.d.ts +8 -2
  35. package/dist/chat/slack/team-domain.d.ts +4 -0
  36. package/dist/chat/source.d.ts +5 -5
  37. package/dist/chat/state/turn-session.d.ts +0 -2
  38. package/dist/chat/task-execution/state.d.ts +23 -11
  39. package/dist/chat/task-execution/store.d.ts +20 -11
  40. package/dist/chat/task-execution/worker.d.ts +2 -2
  41. package/dist/chat/tools/runtime/spawn-agent.d.ts +31 -0
  42. package/dist/chat/tools/types.d.ts +2 -0
  43. package/dist/chunk-3Y47VMHV.js +6 -0
  44. package/dist/chunk-5IDFPCON.js +46 -0
  45. package/dist/chunk-5LTJ4ITU.js +1523 -0
  46. package/dist/{chunk-KLBYCSLJ.js → chunk-5MXWHTCK.js} +17 -434
  47. package/dist/{chunk-CZZDCH3Q.js → chunk-A7WCHXZV.js} +206 -78
  48. package/dist/{chunk-4YQ6I6VF.js → chunk-BOLJOV5T.js} +2 -56
  49. package/dist/{chunk-XCZ6COFO.js → chunk-BYW4B4PR.js} +90 -54
  50. package/dist/{chunk-JQRTXJHD.js → chunk-C7ZVPO4Z.js} +2 -1
  51. package/dist/{chunk-MW2QRPGW.js → chunk-DCLLQGS6.js} +119 -50
  52. package/dist/{chunk-CDVCCOMF.js → chunk-E5UYAIKJ.js} +75 -1475
  53. package/dist/{chunk-3SUK3UXX.js → chunk-ELKW5FIB.js} +5 -5
  54. package/dist/chunk-EM225ITJ.js +56 -0
  55. package/dist/{chunk-Y6JNHTRD.js → chunk-FEUNPTGV.js} +34 -70
  56. package/dist/{chunk-TYNMSQKA.js → chunk-FNLBTB4K.js} +1 -1
  57. package/dist/{chunk-TRZSM67C.js → chunk-HCIVH3JS.js} +1 -0
  58. package/dist/chunk-HIWJWWMT.js +668 -0
  59. package/dist/chunk-IUFM5JWV.js +24 -0
  60. package/dist/{chunk-XWPA5KZF.js → chunk-JNNJZURV.js} +1 -1
  61. package/dist/{chunk-6ZK6FZIV.js → chunk-KCBZSJRU.js} +6 -27
  62. package/dist/{chunk-BG3KWWDJ.js → chunk-KDCHVT25.js} +1 -1
  63. package/dist/{chunk-VH6MI4L7.js → chunk-KFRHRSCJ.js} +1 -1
  64. package/dist/chunk-LPVUNO2W.js +320 -0
  65. package/dist/{chunk-G66RYKRS.js → chunk-N7WARMC6.js} +16 -776
  66. package/dist/chunk-PB3CXNVL.js +41 -0
  67. package/dist/chunk-Q6QZTTLS.js +55 -0
  68. package/dist/{chunk-3QNJOHKG.js → chunk-QS527AV5.js} +1 -1
  69. package/dist/chunk-R776HKPK.js +81 -0
  70. package/dist/{chunk-OTJMQYVP.js → chunk-RZIBSCYC.js} +517 -608
  71. package/dist/chunk-SD3OFRWY.js +714 -0
  72. package/dist/{chunk-HIRIWTWD.js → chunk-SIPKWSXK.js} +58 -10
  73. package/dist/chunk-TCS76X5D.js +441 -0
  74. package/dist/{chunk-UX2TNH5D.js → chunk-TDJCLNDH.js} +3 -3
  75. package/dist/{chunk-NVVBEJUB.js → chunk-TUUDXEXU.js} +336 -300
  76. package/dist/{chunk-22Z6MGMK.js → chunk-VPZFVJND.js} +1 -1
  77. package/dist/{chunk-RFDD3Z2I.js → chunk-YU5QUF67.js} +1 -1
  78. package/dist/chunk-ZBSMCXKV.js +710 -0
  79. package/dist/{chunk-FSYXYBWU.js → chunk-ZXNTJJIE.js} +2 -2
  80. package/dist/cli/chat.js +65 -26
  81. package/dist/cli/check.js +7 -7
  82. package/dist/cli/plugins.js +14 -12
  83. package/dist/cli/snapshot-create.js +10 -9
  84. package/dist/cli/upgrade.js +7 -7
  85. package/dist/conversation-work-KATFHLG3.js +56 -0
  86. package/dist/db/schema/agent-invocations.d.ts +544 -0
  87. package/dist/db/schema.d.ts +543 -1
  88. package/dist/{db-Z2NNL2JC.js → db-UO2IWXGA.js} +7 -7
  89. package/dist/experimental-HH6OM6UH.js +13 -0
  90. package/dist/instrumentation.js +4 -4
  91. package/dist/nitro.js +4 -3
  92. package/dist/{oauth-callback-server-PO3M5JMG.js → oauth-callback-server-MAQI43LX.js} +26 -18
  93. package/dist/{runner-QHQYZYJA.js → runner-PIUQEXKE.js} +26 -14
  94. package/dist/{skills-KXMT3DRA.js → skills-4ARJADHQ.js} +6 -6
  95. package/dist/spawn-G2AWPMCT.js +38 -0
  96. package/dist/{validation-Y42ULTT6.js → validation-S3EE3HWL.js} +6 -6
  97. package/dist/version.js +1 -1
  98. package/dist/work-UARRUAW3.js +46 -0
  99. package/dist/worker-DOCIRVD3.js +24 -0
  100. package/migrations/0021_first_warhawk.sql +35 -0
  101. package/migrations/meta/0021_snapshot.json +2176 -0
  102. package/migrations/meta/_journal.json +8 -1
  103. package/package.json +6 -5
@@ -0,0 +1,710 @@
1
+ import {
2
+ appendAndEnqueueInboundMessage
3
+ } from "./chunk-5LTJ4ITU.js";
4
+ import {
5
+ ConversationTurnLifecycleService
6
+ } from "./chunk-Q6QZTTLS.js";
7
+ import {
8
+ AgentInvocationBusyError,
9
+ AgentInvocationLimitError,
10
+ AuthorizationFlowDisabledError,
11
+ PluginCredentialFailureError,
12
+ agentBindingSchema,
13
+ agentInvocationSchema,
14
+ createAgentInvocationSchema
15
+ } from "./chunk-LPVUNO2W.js";
16
+ import {
17
+ TurnInputCommitLostError,
18
+ coerceThreadArtifactsState,
19
+ failAgentTurnSessionRecord,
20
+ getAgentTurnSessionRecord,
21
+ getAssistantReplyText,
22
+ getConversationTurnBoundaryError,
23
+ getPersistedSandboxState,
24
+ getPersistedThreadState,
25
+ getTerminalAssistantMessages,
26
+ isTurnInputCommitLostError,
27
+ openConversationProjection,
28
+ persistCompletedSessionRecord,
29
+ persistThreadStateById,
30
+ persistYieldSessionRecord
31
+ } from "./chunk-TUUDXEXU.js";
32
+ import {
33
+ getConversationEventStore,
34
+ getConversationStore,
35
+ getSqlExecutor
36
+ } from "./chunk-SIPKWSXK.js";
37
+ import {
38
+ juniorAgentBindings,
39
+ juniorAgentInvocations
40
+ } from "./chunk-DCLLQGS6.js";
41
+
42
+ // src/chat/agent-invocations/work.ts
43
+ import { z } from "zod";
44
+
45
+ // src/chat/agent-invocations/store.ts
46
+ import { createHash } from "crypto";
47
+ import { and, asc, count, eq, inArray } from "drizzle-orm";
48
+ var CREATE_LOCK_PREFIX = "junior:agent_invocation:create";
49
+ var MAX_ACTIVE_AGENT_INVOCATIONS_PER_PARENT = 8;
50
+ var TERMINAL_AGENT_INVOCATION_STATUSES = [
51
+ "blocked",
52
+ "completed",
53
+ "failed"
54
+ ];
55
+ var NON_TERMINAL_AGENT_INVOCATION_STATUSES = [
56
+ "pending",
57
+ "running",
58
+ "awaiting_resume"
59
+ ];
60
+ function stableId(prefix, ...parts) {
61
+ const digest = createHash("sha256").update(parts.join("\0")).digest("hex").slice(0, 32);
62
+ return `${prefix}:${digest}`;
63
+ }
64
+ function getAgentInvocationId(parentConversationId, idempotencyKey) {
65
+ return stableId("agent-invocation", parentConversationId, idempotencyKey);
66
+ }
67
+ function getUnnamedAgentConversationId(invocationId) {
68
+ return stableId("agent", invocationId);
69
+ }
70
+ function getNamedAgentConversationId(parentConversationId, name) {
71
+ return stableId("agent", parentConversationId, name);
72
+ }
73
+ function getAgentInvocationTurnId(invocationId) {
74
+ return `agent-invocation:${invocationId}`;
75
+ }
76
+ function getAgentInvocationMessageId(invocationId) {
77
+ return `agent-invocation:${invocationId}:input`;
78
+ }
79
+ function bindingFromRow(row) {
80
+ return agentBindingSchema.parse({
81
+ childConversationId: row.childConversationId,
82
+ name: row.name,
83
+ parentConversationId: row.parentConversationId
84
+ });
85
+ }
86
+ function invocationFromRow(row) {
87
+ return agentInvocationSchema.parse({
88
+ actor: row.actor,
89
+ ...row.agentName ? { agentName: row.agentName } : {},
90
+ childConversationId: row.childConversationId,
91
+ createdAtMs: row.createdAt.getTime(),
92
+ ...row.credentialContext ? { credentialContext: row.credentialContext } : {},
93
+ destination: row.destination,
94
+ ...row.destinationVisibility ? { destinationVisibility: row.destinationVisibility } : {},
95
+ ...row.errorMessage !== null ? { errorMessage: row.errorMessage } : {},
96
+ input: row.input,
97
+ invocationId: row.invocationId,
98
+ mailboxStatus: row.mailboxStatus,
99
+ parentConversationId: row.parentConversationId,
100
+ ...row.reasoningLevel ? { reasoningLevel: row.reasoningLevel } : {},
101
+ ...row.result !== null ? { result: row.result } : {},
102
+ source: row.source,
103
+ status: row.status,
104
+ ...row.terminalAt ? { terminalAtMs: row.terminalAt.getTime() } : {},
105
+ updatedAtMs: row.updatedAt.getTime()
106
+ });
107
+ }
108
+ function sameCreateInput(invocation, input) {
109
+ 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) && invocation.destinationVisibility === input.destinationVisibility && JSON.stringify(invocation.source) === JSON.stringify(input.source);
110
+ }
111
+ async function getAgentBinding(args) {
112
+ const rows = await getSqlExecutor().db().select().from(juniorAgentBindings).where(
113
+ and(
114
+ eq(juniorAgentBindings.parentConversationId, args.parentConversationId),
115
+ eq(juniorAgentBindings.name, args.name)
116
+ )
117
+ );
118
+ return rows[0] ? bindingFromRow(rows[0]) : void 0;
119
+ }
120
+ async function getAgentInvocation(invocationId) {
121
+ const rows = await getSqlExecutor().db().select().from(juniorAgentInvocations).where(eq(juniorAgentInvocations.invocationId, invocationId));
122
+ return rows[0] ? invocationFromRow(rows[0]) : void 0;
123
+ }
124
+ async function getActiveAgentInvocationForConversation(childConversationId) {
125
+ const rows = await getSqlExecutor().db().select().from(juniorAgentInvocations).where(
126
+ and(
127
+ eq(juniorAgentInvocations.childConversationId, childConversationId),
128
+ inArray(juniorAgentInvocations.status, ["running", "awaiting_resume"])
129
+ )
130
+ ).orderBy(asc(juniorAgentInvocations.createdAt)).limit(2);
131
+ if (rows.length > 1) {
132
+ throw new Error(
133
+ `Child conversation ${childConversationId} has multiple active agent invocations`
134
+ );
135
+ }
136
+ return rows[0] ? invocationFromRow(rows[0]) : void 0;
137
+ }
138
+ async function getNonTerminalAgentInvocationForConversation(childConversationId) {
139
+ const rows = await getSqlExecutor().db().select().from(juniorAgentInvocations).where(
140
+ and(
141
+ eq(juniorAgentInvocations.childConversationId, childConversationId),
142
+ inArray(
143
+ juniorAgentInvocations.status,
144
+ NON_TERMINAL_AGENT_INVOCATION_STATUSES
145
+ )
146
+ )
147
+ ).orderBy(asc(juniorAgentInvocations.createdAt)).limit(1);
148
+ return rows[0] ? invocationFromRow(rows[0]) : void 0;
149
+ }
150
+ async function countNonTerminalAgentInvocationsForParent(parentConversationId) {
151
+ const rows = await getSqlExecutor().db().select({ value: count() }).from(juniorAgentInvocations).where(
152
+ and(
153
+ eq(juniorAgentInvocations.parentConversationId, parentConversationId),
154
+ inArray(
155
+ juniorAgentInvocations.status,
156
+ NON_TERMINAL_AGENT_INVOCATION_STATUSES
157
+ )
158
+ )
159
+ );
160
+ return rows[0]?.value ?? 0;
161
+ }
162
+ async function createAgentInvocation(rawInput, nowMs = Date.now()) {
163
+ const input = createAgentInvocationSchema.parse(rawInput);
164
+ const invocationId = getAgentInvocationId(
165
+ input.parentConversationId,
166
+ input.idempotencyKey
167
+ );
168
+ const childConversationId = input.agentName ? getNamedAgentConversationId(input.parentConversationId, input.agentName) : getUnnamedAgentConversationId(invocationId);
169
+ const lockName = `${CREATE_LOCK_PREFIX}:${input.parentConversationId}`;
170
+ return await getSqlExecutor().withLock(lockName, async () => {
171
+ const existing = await getAgentInvocation(invocationId);
172
+ if (existing) {
173
+ if (!sameCreateInput(existing, input)) {
174
+ throw new Error(
175
+ `Agent invocation idempotency key was reused with different input for ${invocationId}`
176
+ );
177
+ }
178
+ return existing;
179
+ }
180
+ const activeCount = await countNonTerminalAgentInvocationsForParent(
181
+ input.parentConversationId
182
+ );
183
+ if (activeCount >= MAX_ACTIVE_AGENT_INVOCATIONS_PER_PARENT) {
184
+ throw new AgentInvocationLimitError(
185
+ MAX_ACTIVE_AGENT_INVOCATIONS_PER_PARENT
186
+ );
187
+ }
188
+ await getConversationStore().createChild({
189
+ childConversationId,
190
+ parentConversationId: input.parentConversationId,
191
+ nowMs,
192
+ source: "internal"
193
+ });
194
+ if (input.agentName) {
195
+ await getSqlExecutor().db().insert(juniorAgentBindings).values({
196
+ childConversationId,
197
+ name: input.agentName,
198
+ parentConversationId: input.parentConversationId
199
+ }).onConflictDoNothing();
200
+ const binding = await getAgentBinding({
201
+ name: input.agentName,
202
+ parentConversationId: input.parentConversationId
203
+ });
204
+ if (!binding || binding.childConversationId !== childConversationId) {
205
+ throw new Error(
206
+ `Named agent binding did not resolve to ${childConversationId}`
207
+ );
208
+ }
209
+ if (await getNonTerminalAgentInvocationForConversation(childConversationId)) {
210
+ throw new AgentInvocationBusyError(input.agentName);
211
+ }
212
+ }
213
+ await getSqlExecutor().db().insert(juniorAgentInvocations).values({
214
+ invocationId,
215
+ parentConversationId: input.parentConversationId,
216
+ childConversationId,
217
+ agentName: input.agentName ?? null,
218
+ input: input.input,
219
+ actor: input.actor,
220
+ credentialContext: input.credentialContext ?? null,
221
+ source: input.source,
222
+ destination: input.destination,
223
+ destinationVisibility: input.destinationVisibility ?? null,
224
+ reasoningLevel: input.reasoningLevel ?? null,
225
+ status: "pending",
226
+ mailboxStatus: "pending",
227
+ createdAt: new Date(nowMs),
228
+ updatedAt: new Date(nowMs),
229
+ terminalAt: null
230
+ }).onConflictDoNothing();
231
+ const invocation = await getAgentInvocation(invocationId);
232
+ if (!invocation || !sameCreateInput(invocation, input)) {
233
+ throw new Error(`Agent invocation creation raced for ${invocationId}`);
234
+ }
235
+ return invocation;
236
+ });
237
+ }
238
+ async function listPendingAgentInvocationMailboxAppends(limit = 100) {
239
+ const rows = await getSqlExecutor().db().select().from(juniorAgentInvocations).where(eq(juniorAgentInvocations.mailboxStatus, "pending")).orderBy(asc(juniorAgentInvocations.createdAt)).limit(limit);
240
+ return rows.map(invocationFromRow);
241
+ }
242
+ async function markAgentInvocationMailboxAppended(invocationId, nowMs = Date.now()) {
243
+ await getSqlExecutor().db().update(juniorAgentInvocations).set({ mailboxStatus: "appended", updatedAt: new Date(nowMs) }).where(eq(juniorAgentInvocations.invocationId, invocationId));
244
+ }
245
+ async function markAgentInvocationRunning(invocationId, nowMs = Date.now()) {
246
+ await getSqlExecutor().db().update(juniorAgentInvocations).set({ status: "running", updatedAt: new Date(nowMs) }).where(
247
+ and(
248
+ eq(juniorAgentInvocations.invocationId, invocationId),
249
+ inArray(
250
+ juniorAgentInvocations.status,
251
+ NON_TERMINAL_AGENT_INVOCATION_STATUSES
252
+ )
253
+ )
254
+ );
255
+ return await getAgentInvocation(invocationId);
256
+ }
257
+ async function markAgentInvocationAwaitingResume(invocationId, nowMs = Date.now()) {
258
+ await getSqlExecutor().db().update(juniorAgentInvocations).set({ status: "awaiting_resume", updatedAt: new Date(nowMs) }).where(
259
+ and(
260
+ eq(juniorAgentInvocations.invocationId, invocationId),
261
+ inArray(
262
+ juniorAgentInvocations.status,
263
+ NON_TERMINAL_AGENT_INVOCATION_STATUSES
264
+ )
265
+ )
266
+ );
267
+ }
268
+ async function completeAgentInvocation(args) {
269
+ const nowMs = args.nowMs ?? Date.now();
270
+ await getSqlExecutor().db().update(juniorAgentInvocations).set({
271
+ status: args.status,
272
+ result: args.status === "completed" ? args.result : null,
273
+ errorMessage: args.status === "completed" ? null : args.errorMessage,
274
+ terminalAt: new Date(nowMs),
275
+ updatedAt: new Date(nowMs)
276
+ }).where(
277
+ and(
278
+ eq(juniorAgentInvocations.invocationId, args.invocationId),
279
+ inArray(
280
+ juniorAgentInvocations.status,
281
+ NON_TERMINAL_AGENT_INVOCATION_STATUSES
282
+ )
283
+ )
284
+ );
285
+ return await getAgentInvocation(args.invocationId);
286
+ }
287
+ function isTerminalAgentInvocation(invocation) {
288
+ return TERMINAL_AGENT_INVOCATION_STATUSES.includes(
289
+ invocation.status
290
+ );
291
+ }
292
+
293
+ // src/chat/agent-invocations/work.ts
294
+ var agentInvocationMailboxMetadataSchema = z.object({
295
+ agentInvocationId: z.string().min(1),
296
+ kind: z.literal("agent_invocation")
297
+ }).strict();
298
+ function buildAgentInvocationInboundMessage(invocation, nowMs = Date.now()) {
299
+ return {
300
+ conversationId: invocation.childConversationId,
301
+ createdAtMs: invocation.createdAtMs,
302
+ delivery: "defer",
303
+ inboundMessageId: getAgentInvocationMessageId(invocation.invocationId),
304
+ input: {
305
+ authorId: invocation.invocationId,
306
+ text: "[agent invocation]",
307
+ metadata: {
308
+ agentInvocationId: invocation.invocationId,
309
+ kind: "agent_invocation"
310
+ }
311
+ },
312
+ receivedAtMs: nowMs,
313
+ source: "internal"
314
+ };
315
+ }
316
+ async function enqueueAgentInvocation(invocation, options) {
317
+ if (invocation.mailboxStatus === "appended") {
318
+ return;
319
+ }
320
+ const nowMs = options.nowMs ?? Date.now();
321
+ await appendAndEnqueueInboundMessage({
322
+ message: buildAgentInvocationInboundMessage(invocation, nowMs),
323
+ conversationStore: options.conversationStore,
324
+ nowMs,
325
+ queue: options.queue,
326
+ state: options.state
327
+ });
328
+ await markAgentInvocationMailboxAppended(invocation.invocationId, nowMs);
329
+ }
330
+ async function createAndEnqueueAgentInvocation(input, options) {
331
+ const invocation = await createAgentInvocation(input, options.nowMs);
332
+ await enqueueAgentInvocation(invocation, options);
333
+ return await getAgentInvocation(invocation.invocationId) ?? invocation;
334
+ }
335
+ function invocationIdFromMessages(messages) {
336
+ if (messages.length === 0) {
337
+ return void 0;
338
+ }
339
+ const parsed = messages.map(
340
+ (message) => agentInvocationMailboxMetadataSchema.safeParse(message.input.metadata)
341
+ );
342
+ if (parsed.every((result) => !result.success)) {
343
+ return void 0;
344
+ }
345
+ if (parsed.some((result) => !result.success)) {
346
+ throw new Error(
347
+ "Conversation mailbox mixes agent invocation and other input"
348
+ );
349
+ }
350
+ const invocationIds = new Set(
351
+ parsed.map((result) => {
352
+ if (!result.success) {
353
+ throw new Error("Agent invocation mailbox metadata failed validation");
354
+ }
355
+ return result.data.agentInvocationId;
356
+ })
357
+ );
358
+ if (invocationIds.size !== 1) {
359
+ throw new Error(
360
+ "Conversation mailbox contains multiple agent invocations in one attempt"
361
+ );
362
+ }
363
+ return invocationIds.values().next().value;
364
+ }
365
+ async function resolveAgentInvocationId(context) {
366
+ const mailboxInvocationId = invocationIdFromMessages(
367
+ context.attempt.messages
368
+ );
369
+ if (mailboxInvocationId) {
370
+ return mailboxInvocationId;
371
+ }
372
+ if (context.attempt.messages.length > 0) {
373
+ return void 0;
374
+ }
375
+ return (await getActiveAgentInvocationForConversation(context.conversationId))?.invocationId;
376
+ }
377
+ async function persistTerminalLifecycle(invocation) {
378
+ const lifecycle = new ConversationTurnLifecycleService(
379
+ getConversationEventStore()
380
+ );
381
+ const common = {
382
+ conversationId: invocation.childConversationId,
383
+ createdAtMs: "terminalAtMs" in invocation ? invocation.terminalAtMs : invocation.updatedAtMs,
384
+ turnId: getAgentInvocationTurnId(invocation.invocationId)
385
+ };
386
+ if (invocation.status === "completed") {
387
+ await lifecycle.complete({
388
+ ...common,
389
+ outcome: invocation.result?.trim() ? "success" : "no_reply"
390
+ });
391
+ } else if (invocation.status === "blocked" || invocation.status === "failed") {
392
+ await lifecycle.fail({
393
+ ...common,
394
+ failureCode: "model_execution_failed"
395
+ });
396
+ }
397
+ }
398
+ async function projectTerminalSession(invocation) {
399
+ const session = await getAgentTurnSessionRecord(
400
+ invocation.childConversationId,
401
+ getAgentInvocationTurnId(invocation.invocationId)
402
+ );
403
+ if (!session) {
404
+ return void 0;
405
+ }
406
+ if (session.state === "awaiting_resume" || session.state === "running") {
407
+ return void 0;
408
+ }
409
+ const result = getTerminalAssistantMessages(session.piMessages).map(getAssistantReplyText).filter((text) => text !== void 0).join("\n\n");
410
+ const failed = session.state !== "completed" || Boolean(session.errorMessage);
411
+ return await completeAgentInvocation({
412
+ invocationId: invocation.invocationId,
413
+ ...failed ? {
414
+ errorMessage: session.errorMessage ?? "Agent invocation failed",
415
+ status: "failed"
416
+ } : {
417
+ result,
418
+ status: "completed"
419
+ }
420
+ });
421
+ }
422
+ async function recoverRunningSession(invocation) {
423
+ const session = await getAgentTurnSessionRecord(
424
+ invocation.childConversationId,
425
+ getAgentInvocationTurnId(invocation.invocationId)
426
+ );
427
+ if (!session || session.state !== "running") {
428
+ return;
429
+ }
430
+ if (!session.modelId) {
431
+ await failAgentTurnSessionRecord({
432
+ conversationId: invocation.childConversationId,
433
+ expectedVersion: session.version,
434
+ sessionId: session.sessionId,
435
+ errorMessage: `Running agent invocation session is missing its model for ${invocation.invocationId}`
436
+ });
437
+ return;
438
+ }
439
+ const parked = await persistYieldSessionRecord({
440
+ conversationId: invocation.childConversationId,
441
+ currentSliceId: session.sliceId,
442
+ errorMessage: "Recovered running agent invocation after worker loss",
443
+ messages: session.piMessages,
444
+ modelId: session.modelId,
445
+ actor: session.actor,
446
+ reasoningLevel: session.reasoningLevel,
447
+ sessionId: session.sessionId,
448
+ surface: session.surface
449
+ });
450
+ if (!parked) {
451
+ await failAgentTurnSessionRecord({
452
+ conversationId: invocation.childConversationId,
453
+ expectedVersion: session.version,
454
+ sessionId: session.sessionId,
455
+ errorMessage: `Running agent invocation had no resumable boundary for ${invocation.invocationId}`
456
+ });
457
+ return;
458
+ }
459
+ await markAgentInvocationAwaitingResume(invocation.invocationId);
460
+ }
461
+ function blockingInvocationError(error) {
462
+ const cause = getConversationTurnBoundaryError(error)?.cause ?? error;
463
+ return cause instanceof AuthorizationFlowDisabledError || cause instanceof PluginCredentialFailureError ? cause : void 0;
464
+ }
465
+ function isInvocationInputCommitLost(error) {
466
+ const cause = getConversationTurnBoundaryError(error)?.cause ?? error;
467
+ return isTurnInputCommitLostError(cause);
468
+ }
469
+ function createAgentInvocationWorker(options) {
470
+ return async (context, invocationId) => {
471
+ let invocation = await getAgentInvocation(invocationId);
472
+ if (!invocation) {
473
+ throw new Error(`Agent invocation is missing for ${invocationId}`);
474
+ }
475
+ let acknowledged = context.attempt.messages.length === 0;
476
+ const acknowledge = async () => {
477
+ if (acknowledged) {
478
+ return;
479
+ }
480
+ try {
481
+ await context.attempt.ack();
482
+ } catch {
483
+ throw new TurnInputCommitLostError(
484
+ `Conversation work lease lost before invocation inbox ack for ${context.conversationId}`
485
+ );
486
+ }
487
+ acknowledged = true;
488
+ };
489
+ const turnId = getAgentInvocationTurnId(invocation.invocationId);
490
+ const lifecycle = new ConversationTurnLifecycleService(
491
+ getConversationEventStore()
492
+ );
493
+ let artifacts;
494
+ let sandboxRef;
495
+ let history;
496
+ try {
497
+ if (invocation.childConversationId !== context.conversationId) {
498
+ throw new Error(
499
+ `Agent invocation ${invocationId} belongs to ${invocation.childConversationId}, not ${context.conversationId}`
500
+ );
501
+ }
502
+ if (context.destination) {
503
+ throw new Error(
504
+ `Agent invocation conversation ${context.conversationId} must not own a provider destination`
505
+ );
506
+ }
507
+ if (isTerminalAgentInvocation(invocation)) {
508
+ await persistTerminalLifecycle(invocation);
509
+ await acknowledge();
510
+ return { status: "completed" };
511
+ }
512
+ await recoverRunningSession(invocation);
513
+ const projected = await projectTerminalSession(invocation);
514
+ if (projected && isTerminalAgentInvocation(projected)) {
515
+ await persistTerminalLifecycle(projected);
516
+ await acknowledge();
517
+ return { status: "completed" };
518
+ }
519
+ invocation = await markAgentInvocationRunning(invocation.invocationId) ?? invocation;
520
+ await lifecycle.start({
521
+ conversationId: invocation.childConversationId,
522
+ createdAtMs: invocation.createdAtMs,
523
+ inputMessageIds: [getAgentInvocationMessageId(invocation.invocationId)],
524
+ surface: "internal",
525
+ turnId
526
+ });
527
+ const [persisted, projection] = await Promise.all([
528
+ getPersistedThreadState(invocation.childConversationId),
529
+ openConversationProjection({
530
+ conversationId: invocation.childConversationId
531
+ })
532
+ ]);
533
+ artifacts = coerceThreadArtifactsState(persisted);
534
+ sandboxRef = getPersistedSandboxState(persisted);
535
+ history = projection.messages;
536
+ } catch (error) {
537
+ if (isInvocationInputCommitLost(error)) {
538
+ return { status: "lost_lease" };
539
+ }
540
+ if (!context.attempt.isFinalAttempt) {
541
+ throw error;
542
+ }
543
+ const terminal2 = await completeAgentInvocation({
544
+ invocationId: invocation.invocationId,
545
+ errorMessage: error instanceof Error ? error.message : "Agent invocation failed",
546
+ status: "failed"
547
+ });
548
+ if (terminal2) {
549
+ await persistTerminalLifecycle(terminal2);
550
+ }
551
+ await acknowledge();
552
+ return { status: "completed" };
553
+ }
554
+ let outcome;
555
+ try {
556
+ outcome = await options.agentRunner.run({
557
+ conversationId: invocation.childConversationId,
558
+ turnId,
559
+ runId: invocation.invocationId,
560
+ input: {
561
+ messageText: invocation.input,
562
+ piMessages: history
563
+ },
564
+ routing: {
565
+ actor: invocation.actor,
566
+ credentialContext: invocation.credentialContext,
567
+ destination: invocation.destination,
568
+ destinationVisibility: invocation.destinationVisibility,
569
+ source: invocation.source,
570
+ surface: "internal"
571
+ },
572
+ policy: {
573
+ // TODO(#881, #883): Child runs may still need a path to force
574
+ // interactive auth when a delegated tool requires credentials the
575
+ // parent already has authority to request. Today background children
576
+ // hard-fail instead of pausing for an OAuth link.
577
+ disabledFeatures: ["handoff", "interactive-auth", "subagents"],
578
+ reasoningLevel: invocation.reasoningLevel
579
+ },
580
+ state: {
581
+ artifactState: artifacts,
582
+ sandboxRef
583
+ },
584
+ durability: {
585
+ onInputCommitted: acknowledge,
586
+ shouldYield: context.shouldYield,
587
+ onArtifactStateUpdated: async (nextArtifacts) => {
588
+ artifacts = nextArtifacts;
589
+ await persistThreadStateById(invocation.childConversationId, {
590
+ artifacts,
591
+ sandboxRef
592
+ });
593
+ },
594
+ onSandboxRefChanged: async (nextSandboxRef) => {
595
+ sandboxRef = nextSandboxRef;
596
+ await persistThreadStateById(invocation.childConversationId, {
597
+ artifacts,
598
+ sandboxRef
599
+ });
600
+ }
601
+ }
602
+ });
603
+ } catch (error) {
604
+ if (isInvocationInputCommitLost(error)) {
605
+ return { status: "lost_lease" };
606
+ }
607
+ const blocking = blockingInvocationError(error);
608
+ if (blocking) {
609
+ const terminal3 = await completeAgentInvocation({
610
+ invocationId: invocation.invocationId,
611
+ errorMessage: blocking.message,
612
+ status: "blocked"
613
+ });
614
+ if (terminal3) {
615
+ await persistTerminalLifecycle(terminal3);
616
+ }
617
+ await acknowledge();
618
+ return { status: "completed" };
619
+ }
620
+ if (!context.attempt.isFinalAttempt) {
621
+ throw error;
622
+ }
623
+ const terminal2 = await completeAgentInvocation({
624
+ invocationId: invocation.invocationId,
625
+ errorMessage: error instanceof Error ? error.message : "Agent invocation failed",
626
+ status: "failed"
627
+ });
628
+ if (terminal2) {
629
+ await persistTerminalLifecycle(terminal2);
630
+ }
631
+ await acknowledge();
632
+ return { status: "completed" };
633
+ }
634
+ if (outcome.status === "suspended") {
635
+ await markAgentInvocationAwaitingResume(invocation.invocationId);
636
+ return { status: "yielded" };
637
+ }
638
+ if (outcome.status === "awaiting_auth") {
639
+ const terminal2 = await completeAgentInvocation({
640
+ invocationId: invocation.invocationId,
641
+ errorMessage: `Agent invocation requires ${outcome.providerDisplayName} authorization`,
642
+ status: "blocked"
643
+ });
644
+ if (terminal2) {
645
+ await persistTerminalLifecycle(terminal2);
646
+ }
647
+ await acknowledge();
648
+ return { status: "completed" };
649
+ }
650
+ const result = outcome.result;
651
+ const failed = result.diagnostics.outcome !== "success";
652
+ await persistThreadStateById(invocation.childConversationId, {
653
+ artifacts: result.artifactStatePatch ? { ...artifacts, ...result.artifactStatePatch } : artifacts,
654
+ sandboxRef: result.sandboxRef ?? sandboxRef
655
+ });
656
+ if (result.piMessages?.length) {
657
+ await persistCompletedSessionRecord({
658
+ conversationId: invocation.childConversationId,
659
+ currentDurationMs: result.diagnostics.durationMs,
660
+ currentUsage: result.diagnostics.usage,
661
+ destination: invocation.destination,
662
+ destinationVisibility: invocation.destinationVisibility,
663
+ ...failed ? {
664
+ errorMessage: result.diagnostics.errorMessage ?? "Agent invocation failed"
665
+ } : {},
666
+ sessionId: turnId,
667
+ allMessages: result.piMessages,
668
+ modelId: result.diagnostics.modelId,
669
+ actor: invocation.actor,
670
+ reasoningLevel: result.diagnostics.reasoningLevel,
671
+ source: invocation.source,
672
+ surface: "internal"
673
+ });
674
+ }
675
+ const terminal = await completeAgentInvocation({
676
+ invocationId: invocation.invocationId,
677
+ ...failed ? {
678
+ errorMessage: result.diagnostics.errorMessage ?? "Agent invocation failed",
679
+ status: "failed"
680
+ } : {
681
+ result: result.text,
682
+ status: "completed"
683
+ }
684
+ });
685
+ if (!terminal) {
686
+ throw new Error(
687
+ `Agent invocation disappeared during completion for ${invocation.invocationId}`
688
+ );
689
+ }
690
+ await persistTerminalLifecycle(terminal);
691
+ await acknowledge();
692
+ return { status: "completed" };
693
+ };
694
+ }
695
+ function routeAgentInvocationWork(options) {
696
+ return async (context) => {
697
+ const invocationId = await resolveAgentInvocationId(context);
698
+ return invocationId ? await options.invocationWorker(context, invocationId) : await options.fallbackWorker(context);
699
+ };
700
+ }
701
+
702
+ export {
703
+ listPendingAgentInvocationMailboxAppends,
704
+ buildAgentInvocationInboundMessage,
705
+ enqueueAgentInvocation,
706
+ createAndEnqueueAgentInvocation,
707
+ resolveAgentInvocationId,
708
+ createAgentInvocationWorker,
709
+ routeAgentInvocationWork
710
+ };