@sentry/junior 0.137.0 → 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.
- package/dist/{agent-hooks-MZOKEP3J.js → agent-hooks-SCR74Q3E.js} +11 -9
- package/dist/api.js +21 -18
- package/dist/app.d.ts +7 -0
- package/dist/app.js +248 -689
- package/dist/{catalog-runtime-MD66YLFV.js → catalog-runtime-IU3QF4GG.js} +5 -5
- package/dist/chat/agent/request.d.ts +32 -2
- package/dist/chat/agent-dispatch/heartbeat.d.ts +5 -0
- package/dist/chat/agent-dispatch/types.d.ts +1 -1
- package/dist/chat/agent-invocations/errors.d.ts +8 -0
- package/dist/chat/agent-invocations/spawn.d.ts +12 -0
- package/dist/chat/agent-invocations/store.d.ts +44 -0
- package/dist/chat/agent-invocations/types.d.ts +449 -0
- package/dist/chat/agent-invocations/work.d.ts +31 -0
- package/dist/chat/config.d.ts +1 -0
- package/dist/chat/conversations/sql/store.d.ts +8 -0
- package/dist/chat/conversations/store.d.ts +7 -0
- package/dist/chat/experimental.d.ts +16 -0
- package/dist/chat/runtime/agent-runner.d.ts +2 -1
- package/dist/chat/services/auth-pause.d.ts +0 -1
- package/dist/chat/services/mcp-auth-orchestration.d.ts +3 -2
- package/dist/chat/services/plugin-auth-orchestration.d.ts +3 -2
- package/dist/chat/state/turn-session.d.ts +0 -2
- package/dist/chat/task-execution/state.d.ts +23 -11
- package/dist/chat/task-execution/store.d.ts +20 -11
- package/dist/chat/task-execution/worker.d.ts +2 -2
- package/dist/chat/tools/runtime/spawn-agent.d.ts +31 -0
- package/dist/chat/tools/types.d.ts +2 -0
- package/dist/chunk-3Y47VMHV.js +6 -0
- package/dist/chunk-5IDFPCON.js +46 -0
- package/dist/chunk-5LTJ4ITU.js +1523 -0
- package/dist/{chunk-WPPXIAFB.js → chunk-5MXWHTCK.js} +17 -434
- package/dist/{chunk-UOXATPSI.js → chunk-A7WCHXZV.js} +7 -5
- package/dist/{chunk-5G34IEK2.js → chunk-BOLJOV5T.js} +2 -56
- package/dist/{chunk-UG6ZYPBI.js → chunk-BYW4B4PR.js} +73 -60
- package/dist/{chunk-XZ7NPXAB.js → chunk-C7ZVPO4Z.js} +1 -1
- package/dist/{chunk-RB4NJKEX.js → chunk-DCLLQGS6.js} +119 -50
- package/dist/{chunk-DQXAAVTR.js → chunk-E5UYAIKJ.js} +44 -1467
- package/dist/{chunk-6QW2RRQT.js → chunk-ELKW5FIB.js} +5 -5
- package/dist/chunk-EM225ITJ.js +56 -0
- package/dist/{chunk-SYCX4DOK.js → chunk-FEUNPTGV.js} +7 -60
- package/dist/{chunk-YRPC6MJY.js → chunk-FNLBTB4K.js} +1 -1
- package/dist/chunk-HIWJWWMT.js +668 -0
- package/dist/chunk-IUFM5JWV.js +24 -0
- package/dist/{chunk-T6Z5S6PQ.js → chunk-JNNJZURV.js} +1 -1
- package/dist/{chunk-6ZK6FZIV.js → chunk-KCBZSJRU.js} +6 -27
- package/dist/{chunk-5PNJMOY5.js → chunk-KDCHVT25.js} +1 -1
- package/dist/{chunk-BZAMASJP.js → chunk-KFRHRSCJ.js} +1 -1
- package/dist/chunk-LPVUNO2W.js +320 -0
- package/dist/{chunk-EWDSB24B.js → chunk-N7WARMC6.js} +16 -776
- package/dist/chunk-PB3CXNVL.js +41 -0
- package/dist/chunk-Q6QZTTLS.js +55 -0
- package/dist/{chunk-36EG34YI.js → chunk-QS527AV5.js} +1 -1
- package/dist/chunk-R776HKPK.js +81 -0
- package/dist/{chunk-KJ4HGRHN.js → chunk-RZIBSCYC.js} +507 -596
- package/dist/chunk-SD3OFRWY.js +714 -0
- package/dist/{chunk-ZFS67WIR.js → chunk-SIPKWSXK.js} +57 -6
- package/dist/chunk-TCS76X5D.js +441 -0
- package/dist/{chunk-K2NCQJKH.js → chunk-TDJCLNDH.js} +3 -3
- package/dist/{chunk-POBSY47Q.js → chunk-TUUDXEXU.js} +336 -302
- package/dist/{chunk-YNFERGKP.js → chunk-VPZFVJND.js} +1 -1
- package/dist/{chunk-UZQ4VY3M.js → chunk-YU5QUF67.js} +1 -1
- package/dist/chunk-ZBSMCXKV.js +710 -0
- package/dist/{chunk-3Q342EJJ.js → chunk-ZXNTJJIE.js} +2 -2
- package/dist/cli/chat.js +65 -26
- package/dist/cli/check.js +6 -6
- package/dist/cli/plugins.js +14 -12
- package/dist/cli/snapshot-create.js +10 -9
- package/dist/cli/upgrade.js +7 -7
- package/dist/conversation-work-KATFHLG3.js +56 -0
- package/dist/db/schema/agent-invocations.d.ts +544 -0
- package/dist/db/schema.d.ts +543 -1
- package/dist/{db-66E7VDNC.js → db-UO2IWXGA.js} +7 -7
- package/dist/experimental-HH6OM6UH.js +13 -0
- package/dist/instrumentation.js +4 -4
- package/dist/nitro.js +4 -3
- package/dist/{oauth-callback-server-7QPD4Q7P.js → oauth-callback-server-MAQI43LX.js} +26 -18
- package/dist/{runner-L2BKJKOD.js → runner-PIUQEXKE.js} +26 -14
- package/dist/{skills-HOQ2NSPW.js → skills-4ARJADHQ.js} +6 -6
- package/dist/spawn-G2AWPMCT.js +38 -0
- package/dist/{validation-ENHI7S2C.js → validation-S3EE3HWL.js} +6 -6
- package/dist/version.js +1 -1
- package/dist/work-UARRUAW3.js +46 -0
- package/dist/worker-DOCIRVD3.js +24 -0
- package/migrations/0021_first_warhawk.sql +35 -0
- package/migrations/meta/0021_snapshot.json +2176 -0
- package/migrations/meta/_journal.json +8 -1
- package/package.json +5 -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
|
+
};
|