@sentry/junior 0.261.0 → 0.263.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/{adapter-Bx_1B2De.js → adapter-BIacI0Bt.js} +2 -2
- package/dist/{admin-BAbbVgVc.js → admin-C7vUk7s9.js} +1 -1
- package/dist/{agent-hooks-ghyZZQqB.js → agent-hooks-CqnZfuCt.js} +126 -36
- package/dist/{agent-invocations-Qfz7dWgU.js → agent-invocations-CxKldAeB.js} +1 -0
- package/dist/{agent-runner-g110-xgg.js → agent-runner-BUBas7Yy.js} +150 -683
- package/dist/api/schema.d.ts +4 -0
- package/dist/api/schema.js +3 -3
- package/dist/api.js +34 -30
- package/dist/{app-VCiPQ65Q.d.ts → app-Bo0kB987.d.ts} +1 -1
- package/dist/app.d.ts +1 -1
- package/dist/app.js +55 -61
- package/dist/{assistant-reply-CQcMaWNL.js → assistant-reply-BFmjgJjD.js} +1 -1
- package/dist/{automation-DrpcA-SX.js → automation-CWX0qL8U.js} +1 -1
- package/dist/{briefs-CKcfrIQL.js → briefs-h55v5lX9.js} +4 -4
- package/dist/{catalog-runtime-cd60vzAB.js → catalog-runtime-Suf-aLlh.js} +1 -1
- package/dist/cli/chat.js +15 -17
- package/dist/cli/check.js +3 -3
- package/dist/cli/main.js +3 -3
- package/dist/cli/plugins.js +4 -4
- package/dist/cli/snapshot-create.js +4 -4
- package/dist/cli/upgrade.js +3 -3
- package/dist/{client-2i0JJpoC.js → client-DZBCBj1M.js} +2 -2
- package/dist/{config-C9qM41O0.js → config-Dnv8KmNs.js} +2 -2
- package/dist/{connections-DrtTrwkp.js → connections-Br0_xv27.js} +10 -10
- package/dist/{conversation-CvuKJ3tA.js → conversation-B0baEwoA.js} +2 -0
- package/dist/{conversation-privacy-1iTiFX1O.js → conversation-privacy-FY8yc2q3.js} +1 -1
- package/dist/{credential-sync-wKNQ1dYc.js → credential-sync-BvkiyWF5.js} +14 -14
- package/dist/{executor-Rimyj8HZ.js → executor-J9k2sp9m.js} +4 -3
- package/dist/{images-D1JggcVY.js → images-DV2FlEGL.js} +3 -3
- package/dist/instrumentation.js +2 -2
- package/dist/{job-queue-BjQdSqA3.js → job-queue-B4yTgklN.js} +1 -1
- package/dist/{logging-BLTa7R7f.js → logging-Dk_FpF36.js} +1 -1
- package/dist/{message-DAeP5WWW.js → message-BzseAFH1.js} +4 -3
- package/dist/nitro.d.ts +1 -1
- package/dist/nitro.js +3 -3
- package/dist/{oauth-callback-server-BAy1hhF1.js → oauth-callback-server-bTspTMCC.js} +2 -2
- package/dist/{profile-CthM2qFM.js → profile-Dq5I7TV1.js} +13 -6
- package/dist/{prompt-DeVax2qO.js → prompt-CxatQVaj.js} +1 -1
- package/dist/{registry-BnSaArNb.js → registry-BFzQFV_y.js} +1 -1
- package/dist/{resolve-B5eLRl0v.js → resolve-CnRHib0U.js} +4 -4
- package/dist/{result-Cvm0GZCv.js → result-DczfmNBa.js} +18 -12
- package/dist/{runner-D-pUzHbU.js → runner-CJd9Wec-.js} +11 -11
- package/dist/{sandbox-egress-signals-mvwGV7rl.js → sandbox-egress-signals-DUmVkOx4.js} +1 -1
- package/dist/{schema-OzL8Zw4H.js → schema-QidbykYl.js} +3 -3
- package/dist/{service-account-DWxvpSfd.js → service-account-BI2kQDrb.js} +3 -3
- package/dist/{service-account-Ch1uIpnR.js → service-account-x2QZXm4X.js} +3 -3
- package/dist/{session-stop-Boj_zpTa.js → session-stop-CtyJsnEj.js} +12 -12
- package/dist/{skills-BpdYCfFt.js → skills-Dbzx9-rS.js} +2 -2
- package/dist/{spawn-h1DJMQTf.js → spawn-D8WU99UV.js} +3 -2
- package/dist/store-TKg1HVKx.js +697 -0
- package/dist/{store-B6KqcdCQ.js → store-UlOrW6JC.js} +6 -6
- package/dist/{task-queue-BaDwKSYv.js → task-queue-BRB49H-J.js} +3 -3
- package/dist/{task-runner-BAX5li9a.js → task-runner-C575f2Dd.js} +19 -11
- package/dist/{tasks-uCfmlWga.js → tasks-LOItiYgL.js} +46 -66
- package/dist/{thread-state-BJHVIz8G.js → thread-state-IDHdZA7R.js} +5 -5
- package/dist/{turn-failure-response-CCC76P6-.js → turn-failure-response-h_LSXJ-M.js} +46 -20
- package/dist/{turn-lifecycle-pHSESVR9.js → turn-lifecycle-Cvi6Sb8Y.js} +1 -1
- package/dist/{turn-session-routing-BIKJZaiw.js → turn-session-routing-5Nyv4fso.js} +1 -1
- package/dist/{validation-B9KL_5Wc.js → validation-BeTriWC2.js} +1 -1
- package/dist/{version-CN9BXMW_.js → version-BQX_hLDC.js} +1 -1
- package/dist/version.js +1 -1
- package/dist/{viewer-C5xFaYVi.js → viewer-DmNRM88V.js} +4 -4
- package/dist/{web-input-D6eqfXYd.js → web-input-BHcGXSox.js} +97 -36
- package/dist/{work-C-ecZAme.js → work-DLJMV02b.js} +41 -10
- package/dist/{worker-BpYDqiOf.js → worker-vfG2ESg1.js} +5 -5
- package/migrations/0062_plugin_report_index.sql +20 -0
- package/migrations/0063_agent_invocation_type.sql +1 -0
- package/migrations/meta/0062_snapshot.json +3499 -0
- package/migrations/meta/0063_snapshot.json +3505 -0
- package/migrations/meta/_journal.json +14 -0
- package/package.json +4 -4
|
@@ -0,0 +1,697 @@
|
|
|
1
|
+
import { l as TURN_REASONING_LEVELS, s as modelProfileSchema } from "./model-profile-Bg8EoCKD.js";
|
|
2
|
+
import { f as getSqlExecutor, u as getConversationStore } from "./viewer-DmNRM88V.js";
|
|
3
|
+
import { a as juniorAgentInvocations, i as juniorAgentBindings, n as AGENT_INVOCATION_PARENT_MAILBOX_STATUSES, t as AGENT_INVOCATION_MAILBOX_STATUSES } from "./agent-invocations-CxKldAeB.js";
|
|
4
|
+
import "./executor-J9k2sp9m.js";
|
|
5
|
+
import { p as credentialContextSchema } from "./registry-BFzQFV_y.js";
|
|
6
|
+
import { z } from "zod";
|
|
7
|
+
import { createHash } from "node:crypto";
|
|
8
|
+
import { and, asc, count, countDistinct, desc, eq, inArray, lt, ne, or, sql } from "drizzle-orm";
|
|
9
|
+
import { actorSchema, destinationSchema, sourceSchema } from "@sentry/junior-plugin-api";
|
|
10
|
+
// src/chat/runtime/turn.ts
|
|
11
|
+
/** Classified Slack turn failure carrying the owning Sentry capture outward. */
|
|
12
|
+
var ConversationTurnBoundaryError = class extends Error {
|
|
13
|
+
eventId;
|
|
14
|
+
failureCode;
|
|
15
|
+
constructor(args) {
|
|
16
|
+
super(args.cause instanceof Error ? args.cause.message : String(args.cause), { cause: args.cause });
|
|
17
|
+
this.name = "ConversationTurnBoundaryError";
|
|
18
|
+
this.eventId = args.eventId;
|
|
19
|
+
this.failureCode = args.failureCode;
|
|
20
|
+
}
|
|
21
|
+
};
|
|
22
|
+
/** Return a classified Slack boundary failure without erasing its cause. */
|
|
23
|
+
function getConversationTurnBoundaryError(error) {
|
|
24
|
+
return error instanceof ConversationTurnBoundaryError ? error : void 0;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Queue-worker yield routing: respond.ts returns a suspended AgentRunOutcome
|
|
28
|
+
* and the Slack executor raises this at the worker boundary so the lease owner
|
|
29
|
+
* requeues the conversation.
|
|
30
|
+
*/
|
|
31
|
+
var CooperativeTurnYieldError = class extends Error {
|
|
32
|
+
code = "cooperative_turn_yield";
|
|
33
|
+
constructor(message = "Agent turn yielded at a safe boundary") {
|
|
34
|
+
super(message);
|
|
35
|
+
this.name = "CooperativeTurnYieldError";
|
|
36
|
+
}
|
|
37
|
+
};
|
|
38
|
+
function isCooperativeTurnYieldError(error) {
|
|
39
|
+
return error instanceof CooperativeTurnYieldError;
|
|
40
|
+
}
|
|
41
|
+
/** Error indicating durable turn input could not be committed by the worker owner. */
|
|
42
|
+
var TurnInputCommitLostError = class extends Error {
|
|
43
|
+
code = "turn_input_commit_lost";
|
|
44
|
+
constructor(message = "Turn input commit lost its durable owner") {
|
|
45
|
+
super(message);
|
|
46
|
+
this.name = "TurnInputCommitLostError";
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
/** Return whether an error means the durable worker lost input ownership. */
|
|
50
|
+
function isTurnInputCommitLostError(error) {
|
|
51
|
+
return error instanceof TurnInputCommitLostError;
|
|
52
|
+
}
|
|
53
|
+
/** Error indicating durable turn input should stay pending for a later worker. */
|
|
54
|
+
var TurnInputDeferredError = class extends Error {
|
|
55
|
+
code = "turn_input_deferred";
|
|
56
|
+
constructor(message = "Turn input is deferred until the active resume ends") {
|
|
57
|
+
super(message);
|
|
58
|
+
this.name = "TurnInputDeferredError";
|
|
59
|
+
}
|
|
60
|
+
};
|
|
61
|
+
/** Return whether an error means the durable worker should redeliver input later. */
|
|
62
|
+
function isTurnInputDeferredError(error) {
|
|
63
|
+
return error instanceof TurnInputDeferredError;
|
|
64
|
+
}
|
|
65
|
+
/** Mark a turn as the active turn in conversation state. */
|
|
66
|
+
function startActiveTurn(args) {
|
|
67
|
+
args.conversation.processing.activeTurnId = args.nextTurnId;
|
|
68
|
+
}
|
|
69
|
+
function clearActiveTurn(conversation, sessionId) {
|
|
70
|
+
if (!sessionId || conversation.processing.activeTurnId === sessionId) conversation.processing.activeTurnId = void 0;
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Close the active turn without marking a Pi session reusable for future
|
|
74
|
+
* history. Use this for auth handoffs and recovery replies that end the live
|
|
75
|
+
* turn but do not produce a completed Pi session.
|
|
76
|
+
*/
|
|
77
|
+
function markTurnClosed(args) {
|
|
78
|
+
clearActiveTurn(args.conversation, args.sessionId);
|
|
79
|
+
args.conversation.processing.lastCompletedAtMs = args.nowMs;
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Mark a turn as completed after its assistant output has been handled.
|
|
83
|
+
*/
|
|
84
|
+
function markTurnCompleted(args) {
|
|
85
|
+
clearActiveTurn(args.conversation, args.sessionId);
|
|
86
|
+
args.conversation.processing.lastCompletedAtMs = args.nowMs;
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Mark a turn as failed when execution or final user-visible reply delivery
|
|
90
|
+
* cannot be completed. If `sessionId` is provided, `activeTurnId` is only
|
|
91
|
+
* cleared when it still matches the failing turn.
|
|
92
|
+
*/
|
|
93
|
+
function markTurnFailed(args) {
|
|
94
|
+
clearActiveTurn(args.conversation, args.sessionId);
|
|
95
|
+
args.conversation.processing.lastCompletedAtMs = args.nowMs;
|
|
96
|
+
args.markConversationMessage(args.conversation, args.userMessageId, {
|
|
97
|
+
replied: false,
|
|
98
|
+
skippedReason: "reply failed"
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
// src/chat/agent-invocations/agent-types.ts
|
|
102
|
+
/**
|
|
103
|
+
* Child agent types. A type is plain data: when to pick it, whether the child
|
|
104
|
+
* starts with its parent's history, and what it reads with its task. Runtime
|
|
105
|
+
* code reads only these fields, never a type name, so types can later come
|
|
106
|
+
* from configuration. Types do not limit tools: Guardian reviews each child
|
|
107
|
+
* action against the root user's messages, as for any child. A child keeps its
|
|
108
|
+
* type for later agent invocations, the same as its profile. A child without
|
|
109
|
+
* a type starts fresh.
|
|
110
|
+
*/
|
|
111
|
+
const AGENT_TYPES = [
|
|
112
|
+
"fork",
|
|
113
|
+
"explore",
|
|
114
|
+
"reviewer"
|
|
115
|
+
];
|
|
116
|
+
/**
|
|
117
|
+
* Stored name of a child agent type. Like a model profile, a stored name stays
|
|
118
|
+
* readable after its type is removed; the runtime then treats the child as a
|
|
119
|
+
* child without a type.
|
|
120
|
+
*/
|
|
121
|
+
const agentTypeSchema = z.string().regex(/^[a-z][a-z0-9_-]*$/);
|
|
122
|
+
const AGENT_TYPE_DEFINITIONS = {
|
|
123
|
+
fork: {
|
|
124
|
+
whenToUse: "A copy of you. Use it when the brief would need most of what you already know, or when the intermediate tool output is not worth keeping in your context. The task is a directive: say what to do and what is in or out of scope; do not re-explain the background.",
|
|
125
|
+
startsWithParentHistory: true,
|
|
126
|
+
instructions: "You are a fork of your parent agent. The messages before your first parent task are copied from the parent's conversation so you have its context. They are not your job: your job is the parent task below. Sandbox files, approvals, and credentials of the parent were not copied, and your sandbox is separate. Verify files in your own sandbox before you use them."
|
|
127
|
+
},
|
|
128
|
+
explore: {
|
|
129
|
+
whenToUse: "Investigation across code, docs, logs, telemetry, or the web. Use it when an answer needs reading across many files or sources and you need the conclusion with evidence, not the raw output.",
|
|
130
|
+
startsWithParentHistory: false,
|
|
131
|
+
instructions: "You are an explore agent. Your job is to investigate and report, so do not change anything outside your sandbox unless the task asks for it. Report what you found with exact evidence, such as paths, lines, links, and ids, and say what you could not verify."
|
|
132
|
+
},
|
|
133
|
+
reviewer: {
|
|
134
|
+
whenToUse: "An independent second opinion on a change, plan, or claim. Give it the evidence to judge, not your conclusion.",
|
|
135
|
+
startsWithParentHistory: false,
|
|
136
|
+
instructions: "You are a reviewer agent. Give an independent assessment. Do not assume that the parent's framing is correct: verify each claim against the evidence. Do not change anything outside your own sandbox. Report concrete problems ranked by severity, each with its evidence, and say clearly when you found none."
|
|
137
|
+
}
|
|
138
|
+
};
|
|
139
|
+
function agentTypeDefinition(type) {
|
|
140
|
+
return type && Object.hasOwn(AGENT_TYPE_DEFINITIONS, type) ? AGENT_TYPE_DEFINITIONS[type] : void 0;
|
|
141
|
+
}
|
|
142
|
+
/** Model-facing list of child agent types, with how each child starts. */
|
|
143
|
+
function formatAgentTypes() {
|
|
144
|
+
return AGENT_TYPES.map((type) => {
|
|
145
|
+
const definition = AGENT_TYPE_DEFINITIONS[type];
|
|
146
|
+
return `- "${type}"${definition.startsWithParentHistory ? " (starts with this conversation's history)" : ""}: ${definition.whenToUse}`;
|
|
147
|
+
}).join("\n");
|
|
148
|
+
}
|
|
149
|
+
/** Return whether a child of this type starts with its parent's history. */
|
|
150
|
+
function agentTypeStartsWithParentHistory(type) {
|
|
151
|
+
return agentTypeDefinition(type)?.startsWithParentHistory ?? false;
|
|
152
|
+
}
|
|
153
|
+
/** Return the instructions that a child of this type reads with its task. */
|
|
154
|
+
function agentTypeInstructions(type) {
|
|
155
|
+
return agentTypeDefinition(type)?.instructions;
|
|
156
|
+
}
|
|
157
|
+
// src/chat/agent-invocations/types.ts
|
|
158
|
+
const exactStringSchema = z.string().min(1).refine((value) => value === value.trim());
|
|
159
|
+
const agentNameSchema = exactStringSchema.max(64);
|
|
160
|
+
/** `messageAgent` target for the parent of a child agent. */
|
|
161
|
+
const PARENT_AGENT_TARGET = "parent";
|
|
162
|
+
const agentInvocationMailboxStatusSchema = z.enum(AGENT_INVOCATION_MAILBOX_STATUSES);
|
|
163
|
+
const agentInvocationParentMailboxStatusSchema = z.enum(AGENT_INVOCATION_PARENT_MAILBOX_STATUSES);
|
|
164
|
+
const agentBindingSchema = z.object({
|
|
165
|
+
childConversationId: exactStringSchema,
|
|
166
|
+
name: agentNameSchema,
|
|
167
|
+
parentConversationId: exactStringSchema
|
|
168
|
+
}).strict();
|
|
169
|
+
const agentInvocationBaseSchema = z.object({
|
|
170
|
+
actor: actorSchema,
|
|
171
|
+
agentName: agentNameSchema.optional(),
|
|
172
|
+
childConversationId: exactStringSchema,
|
|
173
|
+
createdAtMs: z.number().finite(),
|
|
174
|
+
credentialContext: credentialContextSchema.optional(),
|
|
175
|
+
destination: destinationSchema,
|
|
176
|
+
input: exactStringSchema,
|
|
177
|
+
invocationId: exactStringSchema,
|
|
178
|
+
mailboxStatus: agentInvocationMailboxStatusSchema,
|
|
179
|
+
parentConversationId: exactStringSchema,
|
|
180
|
+
parentToolCallId: exactStringSchema.optional(),
|
|
181
|
+
agentType: agentTypeSchema.optional(),
|
|
182
|
+
modelProfile: modelProfileSchema.optional(),
|
|
183
|
+
reasoningLevel: z.enum(TURN_REASONING_LEVELS).optional(),
|
|
184
|
+
source: sourceSchema,
|
|
185
|
+
title: exactStringSchema.optional(),
|
|
186
|
+
updatedAtMs: z.number().finite()
|
|
187
|
+
}).strict();
|
|
188
|
+
const agentInvocationSchema = z.discriminatedUnion("status", [
|
|
189
|
+
agentInvocationBaseSchema.extend({ status: z.enum([
|
|
190
|
+
"pending",
|
|
191
|
+
"running",
|
|
192
|
+
"paused"
|
|
193
|
+
]) }),
|
|
194
|
+
agentInvocationBaseSchema.extend({
|
|
195
|
+
parentMailboxStatus: agentInvocationParentMailboxStatusSchema,
|
|
196
|
+
result: z.string(),
|
|
197
|
+
status: z.literal("completed"),
|
|
198
|
+
terminalAtMs: z.number().finite()
|
|
199
|
+
}),
|
|
200
|
+
agentInvocationBaseSchema.extend({
|
|
201
|
+
errorMessage: z.string(),
|
|
202
|
+
parentMailboxStatus: agentInvocationParentMailboxStatusSchema,
|
|
203
|
+
status: z.enum([
|
|
204
|
+
"blocked",
|
|
205
|
+
"failed",
|
|
206
|
+
"stopped"
|
|
207
|
+
]),
|
|
208
|
+
terminalAtMs: z.number().finite()
|
|
209
|
+
})
|
|
210
|
+
]);
|
|
211
|
+
const createAgentInvocationSchema = z.object({
|
|
212
|
+
actor: actorSchema,
|
|
213
|
+
agentName: agentNameSchema.optional(),
|
|
214
|
+
credentialContext: credentialContextSchema.optional(),
|
|
215
|
+
destination: destinationSchema,
|
|
216
|
+
idempotencyKey: exactStringSchema,
|
|
217
|
+
input: exactStringSchema,
|
|
218
|
+
parentConversationId: exactStringSchema,
|
|
219
|
+
parentToolCallId: exactStringSchema.optional(),
|
|
220
|
+
agentType: agentTypeSchema.optional(),
|
|
221
|
+
modelProfile: modelProfileSchema.optional(),
|
|
222
|
+
reasoningLevel: z.enum(TURN_REASONING_LEVELS).optional(),
|
|
223
|
+
title: exactStringSchema.optional()
|
|
224
|
+
}).strict();
|
|
225
|
+
/** How a finished task of a child agent ended. A stop is not a result. */
|
|
226
|
+
const CHILD_AGENT_RESULTS = [
|
|
227
|
+
"completed",
|
|
228
|
+
"blocked",
|
|
229
|
+
"failed"
|
|
230
|
+
];
|
|
231
|
+
// src/chat/agent-invocations/errors.ts
|
|
232
|
+
/** A child agent already uses this name in the parent Conversation. */
|
|
233
|
+
var AgentNameInUseError = class extends Error {
|
|
234
|
+
constructor(name) {
|
|
235
|
+
super(`A child agent named "${name}" already exists`);
|
|
236
|
+
this.name = "AgentNameInUseError";
|
|
237
|
+
}
|
|
238
|
+
};
|
|
239
|
+
/** Parent already has the maximum number of non-terminal child invocations. */
|
|
240
|
+
var AgentInvocationLimitError = class extends Error {
|
|
241
|
+
constructor(limit) {
|
|
242
|
+
super(`Parent already has ${limit} active child agent invocations (limit ${limit})`);
|
|
243
|
+
this.name = "AgentInvocationLimitError";
|
|
244
|
+
}
|
|
245
|
+
};
|
|
246
|
+
/** A child agent already has the maximum number of open tasks. */
|
|
247
|
+
var AgentMessageQueueFullError = class extends Error {
|
|
248
|
+
constructor(limit) {
|
|
249
|
+
super(`The child agent already has ${limit} open tasks`);
|
|
250
|
+
this.name = "AgentMessageQueueFullError";
|
|
251
|
+
}
|
|
252
|
+
};
|
|
253
|
+
/** A messageAgent target is not the parent or a child of the sender. */
|
|
254
|
+
var AgentMessageTargetError = class extends Error {
|
|
255
|
+
constructor(message) {
|
|
256
|
+
super(message);
|
|
257
|
+
this.name = "AgentMessageTargetError";
|
|
258
|
+
}
|
|
259
|
+
};
|
|
260
|
+
// src/chat/agent-invocations/store.ts
|
|
261
|
+
const CREATE_LOCK_PREFIX = "junior:agent_invocation:create";
|
|
262
|
+
/**
|
|
263
|
+
* Return the SQL lock that one parent Conversation takes to start, message,
|
|
264
|
+
* or stop its child agents.
|
|
265
|
+
*/
|
|
266
|
+
function getChildAgentLockName(parentConversationId) {
|
|
267
|
+
return `${CREATE_LOCK_PREFIX}:${parentConversationId}`;
|
|
268
|
+
}
|
|
269
|
+
const TERMINAL_AGENT_INVOCATION_STATUSES = [
|
|
270
|
+
"blocked",
|
|
271
|
+
"completed",
|
|
272
|
+
"failed",
|
|
273
|
+
"stopped"
|
|
274
|
+
];
|
|
275
|
+
/** Statuses of an agent invocation that has not finished. */
|
|
276
|
+
const NON_TERMINAL_AGENT_INVOCATION_STATUSES = [
|
|
277
|
+
"pending",
|
|
278
|
+
"running",
|
|
279
|
+
"paused"
|
|
280
|
+
];
|
|
281
|
+
function stableId(prefix, ...parts) {
|
|
282
|
+
return `${prefix}:${createHash("sha256").update(parts.join("\0")).digest("hex").slice(0, 32)}`;
|
|
283
|
+
}
|
|
284
|
+
/** Return the stable identity for one retry-safe delegated task. */
|
|
285
|
+
function getAgentInvocationId(parentConversationId, idempotencyKey) {
|
|
286
|
+
return stableId("agent-invocation", parentConversationId, idempotencyKey);
|
|
287
|
+
}
|
|
288
|
+
/** Return the stable child identity for one invocation without a named binding. */
|
|
289
|
+
function getUnnamedAgentConversationId(invocationId) {
|
|
290
|
+
return stableId("agent", invocationId);
|
|
291
|
+
}
|
|
292
|
+
/** Return the stable child identity for one named binding. */
|
|
293
|
+
function getNamedAgentConversationId(parentConversationId, name) {
|
|
294
|
+
return stableId("agent", parentConversationId, name);
|
|
295
|
+
}
|
|
296
|
+
/**
|
|
297
|
+
* Return the stable identity of the agent invocation that gives one child
|
|
298
|
+
* agent result to the child Conversation that started that child agent.
|
|
299
|
+
*/
|
|
300
|
+
function getAgentInvocationIdForResult(resultInvocationId) {
|
|
301
|
+
return stableId("agent-invocation", "result", resultInvocationId);
|
|
302
|
+
}
|
|
303
|
+
/**
|
|
304
|
+
* Return the stable identity of the agent invocation that gives one message
|
|
305
|
+
* from a child agent to the child Conversation that started that child agent.
|
|
306
|
+
*/
|
|
307
|
+
function getAgentInvocationIdForMessage(messageId) {
|
|
308
|
+
return stableId("agent-invocation", "message", messageId);
|
|
309
|
+
}
|
|
310
|
+
/**
|
|
311
|
+
* Return the stable identity of the agent invocation that gives one batch of
|
|
312
|
+
* watch events to a child Conversation. The first message of the batch names
|
|
313
|
+
* it.
|
|
314
|
+
*/
|
|
315
|
+
function getAgentInvocationIdForEvents(messageId) {
|
|
316
|
+
return stableId("agent-invocation", "events", messageId);
|
|
317
|
+
}
|
|
318
|
+
/** Return the stable turn identity advanced by one agent invocation. */
|
|
319
|
+
function getAgentInvocationTurnId(invocationId) {
|
|
320
|
+
return `agent-invocation:${invocationId}`;
|
|
321
|
+
}
|
|
322
|
+
/** Return the stable mailbox identity for one agent invocation. */
|
|
323
|
+
function getAgentInvocationMessageId(invocationId) {
|
|
324
|
+
return `agent-invocation:${invocationId}:input`;
|
|
325
|
+
}
|
|
326
|
+
const RESULT_MESSAGE_PREFIX = "agent-invocation:";
|
|
327
|
+
const RESULT_MESSAGE_SUFFIX = ":result";
|
|
328
|
+
/** Return the stable parent mailbox identity for one terminal result. */
|
|
329
|
+
function getAgentInvocationResultMessageId(invocationId) {
|
|
330
|
+
return `${RESULT_MESSAGE_PREFIX}${invocationId}${RESULT_MESSAGE_SUFFIX}`;
|
|
331
|
+
}
|
|
332
|
+
/** Read the invocation identity from one parent result mailbox identity. */
|
|
333
|
+
function parseAgentInvocationResultMessageId(messageId) {
|
|
334
|
+
if (!messageId.startsWith(RESULT_MESSAGE_PREFIX) || !messageId.endsWith(RESULT_MESSAGE_SUFFIX)) return;
|
|
335
|
+
return messageId.slice(17, -7) || void 0;
|
|
336
|
+
}
|
|
337
|
+
function bindingFromRow(row) {
|
|
338
|
+
return agentBindingSchema.parse({
|
|
339
|
+
childConversationId: row.childConversationId,
|
|
340
|
+
name: row.name,
|
|
341
|
+
parentConversationId: row.parentConversationId
|
|
342
|
+
});
|
|
343
|
+
}
|
|
344
|
+
/** Parse one agent invocation row. */
|
|
345
|
+
function invocationFromRow(row) {
|
|
346
|
+
return agentInvocationSchema.parse({
|
|
347
|
+
actor: row.actor,
|
|
348
|
+
...row.agentName ? { agentName: row.agentName } : void 0,
|
|
349
|
+
childConversationId: row.childConversationId,
|
|
350
|
+
createdAtMs: row.createdAt.getTime(),
|
|
351
|
+
...row.credentialContext ? { credentialContext: row.credentialContext } : void 0,
|
|
352
|
+
destination: row.destination,
|
|
353
|
+
...row.errorMessage !== null ? { errorMessage: row.errorMessage } : void 0,
|
|
354
|
+
input: row.input,
|
|
355
|
+
invocationId: row.invocationId,
|
|
356
|
+
mailboxStatus: row.mailboxStatus,
|
|
357
|
+
parentConversationId: row.parentConversationId,
|
|
358
|
+
...row.parentMailboxStatus ? { parentMailboxStatus: row.parentMailboxStatus } : void 0,
|
|
359
|
+
...row.parentToolCallId ? { parentToolCallId: row.parentToolCallId } : void 0,
|
|
360
|
+
...row.agentType ? { agentType: row.agentType } : void 0,
|
|
361
|
+
...row.modelProfile ? { modelProfile: row.modelProfile } : void 0,
|
|
362
|
+
...row.reasoningLevel ? { reasoningLevel: row.reasoningLevel } : void 0,
|
|
363
|
+
...row.result !== null ? { result: row.result } : void 0,
|
|
364
|
+
source: row.source,
|
|
365
|
+
status: row.status,
|
|
366
|
+
...row.terminalAt ? { terminalAtMs: row.terminalAt.getTime() } : void 0,
|
|
367
|
+
...row.title ? { title: row.title } : void 0,
|
|
368
|
+
updatedAtMs: row.updatedAt.getTime()
|
|
369
|
+
});
|
|
370
|
+
}
|
|
371
|
+
/**
|
|
372
|
+
* Require every durable creation input to match before replaying one key.
|
|
373
|
+
* The title is not compared: a retry can generate a different one, and the
|
|
374
|
+
* stored title wins.
|
|
375
|
+
*/
|
|
376
|
+
function sameCreateInput(invocation, input) {
|
|
377
|
+
return invocation.parentConversationId === input.parentConversationId && invocation.agentName === input.agentName && invocation.input === input.input && invocation.agentType === input.agentType && invocation.modelProfile === input.modelProfile && 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);
|
|
378
|
+
}
|
|
379
|
+
/** Read one named child binding in its parent-agent scope. */
|
|
380
|
+
async function getAgentBinding(args) {
|
|
381
|
+
const rows = await getSqlExecutor().db().select().from(juniorAgentBindings).where(and(eq(juniorAgentBindings.parentConversationId, args.parentConversationId), eq(juniorAgentBindings.name, args.name)));
|
|
382
|
+
return rows[0] ? bindingFromRow(rows[0]) : void 0;
|
|
383
|
+
}
|
|
384
|
+
/** Read one durable agent invocation. */
|
|
385
|
+
async function getAgentInvocation(invocationId) {
|
|
386
|
+
const rows = await getSqlExecutor().db().select().from(juniorAgentInvocations).where(eq(juniorAgentInvocations.invocationId, invocationId));
|
|
387
|
+
return rows[0] ? invocationFromRow(rows[0]) : void 0;
|
|
388
|
+
}
|
|
389
|
+
/** Resolve the single invocation that may resume for one child conversation. */
|
|
390
|
+
async function getActiveAgentInvocationForConversation(childConversationId) {
|
|
391
|
+
const rows = await getSqlExecutor().db().select().from(juniorAgentInvocations).where(and(eq(juniorAgentInvocations.childConversationId, childConversationId), inArray(juniorAgentInvocations.status, ["running", "paused"]))).orderBy(asc(juniorAgentInvocations.createdAt)).limit(2);
|
|
392
|
+
if (rows.length > 1) throw new Error(`Child conversation ${childConversationId} has multiple active agent invocations`);
|
|
393
|
+
return rows[0] ? invocationFromRow(rows[0]) : void 0;
|
|
394
|
+
}
|
|
395
|
+
/** Read the newest agent invocation of one child Conversation. */
|
|
396
|
+
async function getLatestAgentInvocationForConversation(childConversationId) {
|
|
397
|
+
const rows = await getSqlExecutor().db().select().from(juniorAgentInvocations).where(eq(juniorAgentInvocations.childConversationId, childConversationId)).orderBy(desc(juniorAgentInvocations.createdAt)).limit(1);
|
|
398
|
+
return rows[0] ? invocationFromRow(rows[0]) : void 0;
|
|
399
|
+
}
|
|
400
|
+
/**
|
|
401
|
+
* Read the name and the newest task title of one child Conversation, for
|
|
402
|
+
* labels. Agent invocations that give a nested result to the child have no
|
|
403
|
+
* title, so an agent invocation with a title comes first.
|
|
404
|
+
*/
|
|
405
|
+
async function getChildAgentLabel(childConversationId) {
|
|
406
|
+
const [row] = await getSqlExecutor().db().select({
|
|
407
|
+
agentName: juniorAgentInvocations.agentName,
|
|
408
|
+
title: juniorAgentInvocations.title
|
|
409
|
+
}).from(juniorAgentInvocations).where(eq(juniorAgentInvocations.childConversationId, childConversationId)).orderBy(sql`${juniorAgentInvocations.title} is null`, desc(juniorAgentInvocations.createdAt)).limit(1);
|
|
410
|
+
return row;
|
|
411
|
+
}
|
|
412
|
+
async function getNonTerminalAgentInvocationForConversation(childConversationId) {
|
|
413
|
+
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);
|
|
414
|
+
return rows[0] ? invocationFromRow(rows[0]) : void 0;
|
|
415
|
+
}
|
|
416
|
+
async function countNonTerminalAgentInvocationsForChild(childConversationId) {
|
|
417
|
+
return (await getSqlExecutor().db().select({ value: count() }).from(juniorAgentInvocations).where(and(eq(juniorAgentInvocations.childConversationId, childConversationId), inArray(juniorAgentInvocations.status, NON_TERMINAL_AGENT_INVOCATION_STATUSES))))[0]?.value ?? 0;
|
|
418
|
+
}
|
|
419
|
+
/**
|
|
420
|
+
* A child agent can only start or give work to child agents while it has
|
|
421
|
+
* open work. A stopped child has none. A root Conversation has no agent
|
|
422
|
+
* invocation, so it always passes.
|
|
423
|
+
*/
|
|
424
|
+
async function assertCanGiveChildWork(parentConversationId) {
|
|
425
|
+
if (await getLatestAgentInvocationForConversation(parentConversationId) && !await getNonTerminalAgentInvocationForConversation(parentConversationId)) throw new Error(`Agent conversation ${parentConversationId} has no open work and cannot give work to child agents`);
|
|
426
|
+
}
|
|
427
|
+
/**
|
|
428
|
+
* Count the child agents of one parent that have open work. Messages queued
|
|
429
|
+
* for a busy child do not count again.
|
|
430
|
+
*/
|
|
431
|
+
async function countActiveChildAgentsForParent(parentConversationId) {
|
|
432
|
+
return (await getSqlExecutor().db().select({ value: countDistinct(juniorAgentInvocations.childConversationId) }).from(juniorAgentInvocations).where(and(eq(juniorAgentInvocations.parentConversationId, parentConversationId), inArray(juniorAgentInvocations.status, NON_TERMINAL_AGENT_INVOCATION_STATUSES))))[0]?.value ?? 0;
|
|
433
|
+
}
|
|
434
|
+
/**
|
|
435
|
+
* Create or replay the first invocation of one new child agent. A name gives
|
|
436
|
+
* the child a stable Conversation id that later messages can address. A name
|
|
437
|
+
* that is already used throws: later work for that child goes through
|
|
438
|
+
* `createAgentInvocationForMessage`.
|
|
439
|
+
*/
|
|
440
|
+
async function createAgentInvocation(rawInput, nowMs = Date.now()) {
|
|
441
|
+
const input = createAgentInvocationSchema.parse(rawInput);
|
|
442
|
+
const invocationId = getAgentInvocationId(input.parentConversationId, input.idempotencyKey);
|
|
443
|
+
const childConversationId = input.agentName ? getNamedAgentConversationId(input.parentConversationId, input.agentName) : getUnnamedAgentConversationId(invocationId);
|
|
444
|
+
const lockName = getChildAgentLockName(input.parentConversationId);
|
|
445
|
+
return await getSqlExecutor().withLock(lockName, async () => {
|
|
446
|
+
const existing = await getAgentInvocation(invocationId);
|
|
447
|
+
if (existing) {
|
|
448
|
+
if (!sameCreateInput(existing, input)) throw new Error(`Agent invocation idempotency key was reused with different input for ${invocationId}`);
|
|
449
|
+
return existing;
|
|
450
|
+
}
|
|
451
|
+
await assertCanGiveChildWork(input.parentConversationId);
|
|
452
|
+
if (input.agentName && await getAgentBinding({
|
|
453
|
+
name: input.agentName,
|
|
454
|
+
parentConversationId: input.parentConversationId
|
|
455
|
+
})) throw new AgentNameInUseError(input.agentName);
|
|
456
|
+
if (await countActiveChildAgentsForParent(input.parentConversationId) >= 8) throw new AgentInvocationLimitError(8);
|
|
457
|
+
await getConversationStore().createChild({
|
|
458
|
+
childConversationId,
|
|
459
|
+
parentConversationId: input.parentConversationId,
|
|
460
|
+
nowMs,
|
|
461
|
+
source: "internal"
|
|
462
|
+
});
|
|
463
|
+
if (input.agentName) await getSqlExecutor().db().insert(juniorAgentBindings).values({
|
|
464
|
+
childConversationId,
|
|
465
|
+
name: input.agentName,
|
|
466
|
+
parentConversationId: input.parentConversationId
|
|
467
|
+
});
|
|
468
|
+
await getSqlExecutor().db().insert(juniorAgentInvocations).values({
|
|
469
|
+
invocationId,
|
|
470
|
+
parentConversationId: input.parentConversationId,
|
|
471
|
+
childConversationId,
|
|
472
|
+
agentName: input.agentName ?? null,
|
|
473
|
+
input: input.input,
|
|
474
|
+
title: input.title ?? null,
|
|
475
|
+
actor: input.actor,
|
|
476
|
+
credentialContext: input.credentialContext ?? null,
|
|
477
|
+
source: { kind: "agent_invocation" },
|
|
478
|
+
destination: input.destination,
|
|
479
|
+
agentType: input.agentType ?? null,
|
|
480
|
+
modelProfile: input.modelProfile ?? null,
|
|
481
|
+
reasoningLevel: input.reasoningLevel ?? null,
|
|
482
|
+
parentToolCallId: input.parentToolCallId ?? null,
|
|
483
|
+
status: "pending",
|
|
484
|
+
mailboxStatus: "pending",
|
|
485
|
+
createdAt: new Date(nowMs),
|
|
486
|
+
updatedAt: new Date(nowMs),
|
|
487
|
+
terminalAt: null
|
|
488
|
+
}).onConflictDoNothing();
|
|
489
|
+
const invocation = await getAgentInvocation(invocationId);
|
|
490
|
+
if (!invocation || !sameCreateInput(invocation, input)) throw new Error(`Agent invocation creation raced for ${invocationId}`);
|
|
491
|
+
return invocation;
|
|
492
|
+
});
|
|
493
|
+
}
|
|
494
|
+
/**
|
|
495
|
+
* Create or replay the agent invocation that gives one input to a child
|
|
496
|
+
* Conversation: a result or message from its own child agent, or its own
|
|
497
|
+
* watch events. It continues that Conversation for the same parent as
|
|
498
|
+
* `latest`, and for the same Actor and credentials unless `runAs` sets them.
|
|
499
|
+
* It skips the queue and active-count checks because it continues work that
|
|
500
|
+
* already passed them.
|
|
501
|
+
*/
|
|
502
|
+
async function createAgentInvocationFromLatest(args, nowMs = Date.now()) {
|
|
503
|
+
const { invocationId } = args;
|
|
504
|
+
await getSqlExecutor().db().insert(juniorAgentInvocations).values({
|
|
505
|
+
invocationId,
|
|
506
|
+
parentConversationId: args.latest.parentConversationId,
|
|
507
|
+
childConversationId: args.latest.childConversationId,
|
|
508
|
+
agentName: args.latest.agentName ?? null,
|
|
509
|
+
input: args.input,
|
|
510
|
+
actor: (args.runAs ?? args.latest).actor,
|
|
511
|
+
credentialContext: (args.runAs ?? args.latest).credentialContext ?? null,
|
|
512
|
+
source: args.runAs?.source ?? { kind: "agent_invocation" },
|
|
513
|
+
destination: args.latest.destination,
|
|
514
|
+
agentType: args.latest.agentType ?? null,
|
|
515
|
+
modelProfile: args.latest.modelProfile ?? null,
|
|
516
|
+
reasoningLevel: args.latest.reasoningLevel ?? null,
|
|
517
|
+
status: "pending",
|
|
518
|
+
mailboxStatus: "pending",
|
|
519
|
+
createdAt: new Date(nowMs),
|
|
520
|
+
updatedAt: new Date(nowMs),
|
|
521
|
+
terminalAt: null
|
|
522
|
+
}).onConflictDoNothing();
|
|
523
|
+
const invocation = await getAgentInvocation(invocationId);
|
|
524
|
+
if (!invocation || invocation.childConversationId !== args.latest.childConversationId) throw new Error(`Agent invocation creation raced for ${invocationId}`);
|
|
525
|
+
return invocation;
|
|
526
|
+
}
|
|
527
|
+
/** Find a direct child agent of one parent by name or child Conversation id. */
|
|
528
|
+
async function findChildAgentConversationId(args) {
|
|
529
|
+
const binding = await getAgentBinding({
|
|
530
|
+
name: args.target,
|
|
531
|
+
parentConversationId: args.parentConversationId
|
|
532
|
+
});
|
|
533
|
+
if (binding) return binding.childConversationId;
|
|
534
|
+
return (await getSqlExecutor().db().select({ childConversationId: juniorAgentInvocations.childConversationId }).from(juniorAgentInvocations).where(and(eq(juniorAgentInvocations.parentConversationId, args.parentConversationId), eq(juniorAgentInvocations.childConversationId, args.target))).limit(1))[0]?.childConversationId;
|
|
535
|
+
}
|
|
536
|
+
/**
|
|
537
|
+
* Return whether a pending agent invocation still waits for older open work
|
|
538
|
+
* on its child.
|
|
539
|
+
*/
|
|
540
|
+
async function isQueuedBehindOpenWork(invocation) {
|
|
541
|
+
if (invocation.status !== "pending") return false;
|
|
542
|
+
return (await getSqlExecutor().db().select({ invocationId: juniorAgentInvocations.invocationId }).from(juniorAgentInvocations).where(and(eq(juniorAgentInvocations.childConversationId, invocation.childConversationId), lt(juniorAgentInvocations.createdAt, new Date(invocation.createdAtMs)), inArray(juniorAgentInvocations.status, NON_TERMINAL_AGENT_INVOCATION_STATUSES))).limit(1)).length > 0;
|
|
543
|
+
}
|
|
544
|
+
/**
|
|
545
|
+
* Create or replay the agent invocation for one message from a parent to one
|
|
546
|
+
* of its child agents. The child continues its history. If the child already
|
|
547
|
+
* has open work, the new agent invocation waits for that work and the parent
|
|
548
|
+
* gets one report for both. A stopped child starts again.
|
|
549
|
+
*/
|
|
550
|
+
async function createAgentInvocationForMessage(input, nowMs = Date.now()) {
|
|
551
|
+
const invocationId = getAgentInvocationId(input.parentConversationId, input.idempotencyKey);
|
|
552
|
+
return await getSqlExecutor().withLock(getChildAgentLockName(input.parentConversationId), async () => {
|
|
553
|
+
const existing = await getAgentInvocation(invocationId);
|
|
554
|
+
if (existing) {
|
|
555
|
+
if (existing.childConversationId !== input.childConversationId || existing.input !== input.input) throw new Error(`Agent invocation idempotency key was reused with different input for ${invocationId}`);
|
|
556
|
+
return {
|
|
557
|
+
invocation: existing,
|
|
558
|
+
queued: await isQueuedBehindOpenWork(existing)
|
|
559
|
+
};
|
|
560
|
+
}
|
|
561
|
+
const latest = await getLatestAgentInvocationForConversation(input.childConversationId);
|
|
562
|
+
if (!latest || latest.parentConversationId !== input.parentConversationId) throw new Error(`${input.childConversationId} is not a child agent of ${input.parentConversationId}`);
|
|
563
|
+
await assertCanGiveChildWork(input.parentConversationId);
|
|
564
|
+
const openCount = await countNonTerminalAgentInvocationsForChild(input.childConversationId);
|
|
565
|
+
if (openCount >= 4) throw new AgentMessageQueueFullError(4);
|
|
566
|
+
if (openCount === 0 && await countActiveChildAgentsForParent(input.parentConversationId) >= 8) throw new AgentInvocationLimitError(8);
|
|
567
|
+
await getSqlExecutor().db().insert(juniorAgentInvocations).values({
|
|
568
|
+
invocationId,
|
|
569
|
+
parentConversationId: input.parentConversationId,
|
|
570
|
+
childConversationId: input.childConversationId,
|
|
571
|
+
agentName: latest.agentName ?? null,
|
|
572
|
+
input: input.input,
|
|
573
|
+
title: null,
|
|
574
|
+
actor: input.actor,
|
|
575
|
+
credentialContext: input.credentialContext ?? null,
|
|
576
|
+
source: { kind: "agent_invocation" },
|
|
577
|
+
destination: input.destination,
|
|
578
|
+
agentType: latest.agentType ?? null,
|
|
579
|
+
modelProfile: latest.modelProfile ?? null,
|
|
580
|
+
reasoningLevel: latest.reasoningLevel ?? null,
|
|
581
|
+
parentToolCallId: input.parentToolCallId,
|
|
582
|
+
status: "pending",
|
|
583
|
+
mailboxStatus: "pending",
|
|
584
|
+
createdAt: new Date(nowMs),
|
|
585
|
+
updatedAt: new Date(nowMs),
|
|
586
|
+
terminalAt: null
|
|
587
|
+
}).onConflictDoNothing();
|
|
588
|
+
const invocation = await getAgentInvocation(invocationId);
|
|
589
|
+
if (!invocation) throw new Error(`Agent invocation creation raced for ${invocationId}`);
|
|
590
|
+
return {
|
|
591
|
+
invocation,
|
|
592
|
+
queued: openCount > 0
|
|
593
|
+
};
|
|
594
|
+
});
|
|
595
|
+
}
|
|
596
|
+
/**
|
|
597
|
+
* Return whether a child Conversation has work that will start another agent
|
|
598
|
+
* invocation on it: another open agent invocation, an open child agent, or a
|
|
599
|
+
* sent child agent result that no agent invocation has received yet.
|
|
600
|
+
*/
|
|
601
|
+
async function hasOpenChildWork(invocation) {
|
|
602
|
+
const db = getSqlExecutor().db();
|
|
603
|
+
const conversationId = invocation.childConversationId;
|
|
604
|
+
if ((await db.select({ invocationId: juniorAgentInvocations.invocationId }).from(juniorAgentInvocations).where(and(or(eq(juniorAgentInvocations.childConversationId, conversationId), eq(juniorAgentInvocations.parentConversationId, conversationId)), ne(juniorAgentInvocations.invocationId, invocation.invocationId), inArray(juniorAgentInvocations.status, NON_TERMINAL_AGENT_INVOCATION_STATUSES))).limit(1)).length > 0) return true;
|
|
605
|
+
const sent = await db.select({ invocationId: juniorAgentInvocations.invocationId }).from(juniorAgentInvocations).where(and(eq(juniorAgentInvocations.parentConversationId, conversationId), inArray(juniorAgentInvocations.parentMailboxStatus, ["pending", "appended"])));
|
|
606
|
+
if (sent.length === 0) return false;
|
|
607
|
+
return (await db.select({ invocationId: juniorAgentInvocations.invocationId }).from(juniorAgentInvocations).where(inArray(juniorAgentInvocations.invocationId, sent.map((row) => getAgentInvocationIdForResult(row.invocationId))))).length < sent.length;
|
|
608
|
+
}
|
|
609
|
+
/** List bounded invocation records whose mailbox append still needs repair. */
|
|
610
|
+
async function listPendingAgentInvocationMailboxAppends(limit = 100) {
|
|
611
|
+
return (await getSqlExecutor().db().select().from(juniorAgentInvocations).where(and(eq(juniorAgentInvocations.mailboxStatus, "pending"), inArray(juniorAgentInvocations.status, NON_TERMINAL_AGENT_INVOCATION_STATUSES))).orderBy(asc(juniorAgentInvocations.createdAt)).limit(limit)).map(invocationFromRow);
|
|
612
|
+
}
|
|
613
|
+
/** List terminal agent invocation results that still need a parent mailbox append. */
|
|
614
|
+
async function listPendingAgentInvocationResults(limit = 100) {
|
|
615
|
+
return (await getSqlExecutor().db().select().from(juniorAgentInvocations).where(eq(juniorAgentInvocations.parentMailboxStatus, "pending")).orderBy(asc(juniorAgentInvocations.terminalAt)).limit(limit)).map(invocationFromRow).filter(isTerminalAgentInvocation);
|
|
616
|
+
}
|
|
617
|
+
/**
|
|
618
|
+
* Send a `replaced` result to the parent now. A stop ended the open work of
|
|
619
|
+
* the child, so no later agent invocation sends the report.
|
|
620
|
+
*/
|
|
621
|
+
async function reportReplacedAgentInvocation(invocationId, nowMs = Date.now()) {
|
|
622
|
+
await getSqlExecutor().db().update(juniorAgentInvocations).set({
|
|
623
|
+
parentMailboxStatus: "pending",
|
|
624
|
+
updatedAt: new Date(nowMs)
|
|
625
|
+
}).where(and(eq(juniorAgentInvocations.invocationId, invocationId), eq(juniorAgentInvocations.parentMailboxStatus, "replaced")));
|
|
626
|
+
return await getAgentInvocation(invocationId);
|
|
627
|
+
}
|
|
628
|
+
/** Record that one terminal result is in its parent mailbox. */
|
|
629
|
+
async function markAgentInvocationParentMailboxAppended(invocationId, nowMs = Date.now()) {
|
|
630
|
+
await getSqlExecutor().db().update(juniorAgentInvocations).set({
|
|
631
|
+
parentMailboxStatus: "appended",
|
|
632
|
+
updatedAt: new Date(nowMs)
|
|
633
|
+
}).where(and(eq(juniorAgentInvocations.invocationId, invocationId), eq(juniorAgentInvocations.parentMailboxStatus, "pending")));
|
|
634
|
+
}
|
|
635
|
+
/** Record that the invocation's idempotent mailbox append completed. */
|
|
636
|
+
async function markAgentInvocationMailboxAppended(invocationId, nowMs = Date.now()) {
|
|
637
|
+
await getSqlExecutor().db().update(juniorAgentInvocations).set({
|
|
638
|
+
mailboxStatus: "appended",
|
|
639
|
+
updatedAt: new Date(nowMs)
|
|
640
|
+
}).where(eq(juniorAgentInvocations.invocationId, invocationId));
|
|
641
|
+
}
|
|
642
|
+
/** Mark one non-terminal invocation as actively executing. */
|
|
643
|
+
async function markAgentInvocationRunning(invocationId, nowMs = Date.now()) {
|
|
644
|
+
await getSqlExecutor().db().update(juniorAgentInvocations).set({
|
|
645
|
+
status: "running",
|
|
646
|
+
updatedAt: new Date(nowMs)
|
|
647
|
+
}).where(and(eq(juniorAgentInvocations.invocationId, invocationId), inArray(juniorAgentInvocations.status, NON_TERMINAL_AGENT_INVOCATION_STATUSES)));
|
|
648
|
+
return await getAgentInvocation(invocationId);
|
|
649
|
+
}
|
|
650
|
+
/** Mark one invocation as waiting for another shared execution slice. */
|
|
651
|
+
async function markAgentInvocationPaused(invocationId, nowMs = Date.now()) {
|
|
652
|
+
await getSqlExecutor().db().update(juniorAgentInvocations).set({
|
|
653
|
+
status: "paused",
|
|
654
|
+
updatedAt: new Date(nowMs)
|
|
655
|
+
}).where(and(eq(juniorAgentInvocations.invocationId, invocationId), inArray(juniorAgentInvocations.status, NON_TERMINAL_AGENT_INVOCATION_STATUSES)));
|
|
656
|
+
}
|
|
657
|
+
/**
|
|
658
|
+
* Persist one terminal invocation result without overwriting an earlier
|
|
659
|
+
* result. The same write marks the result for the parent mailbox. If the
|
|
660
|
+
* child still has open work, the result is `replaced`: the parent gets one
|
|
661
|
+
* report, from the agent invocation that finishes the child's work.
|
|
662
|
+
*/
|
|
663
|
+
async function completeAgentInvocation(args) {
|
|
664
|
+
const nowMs = args.nowMs ?? Date.now();
|
|
665
|
+
const current = await getAgentInvocation(args.invocationId);
|
|
666
|
+
if (!current || isTerminalAgentInvocation(current)) return current;
|
|
667
|
+
const parentMailboxStatus = args.status !== "stopped" && await hasOpenChildWork(current) ? "replaced" : "pending";
|
|
668
|
+
await getSqlExecutor().db().update(juniorAgentInvocations).set({
|
|
669
|
+
status: args.status,
|
|
670
|
+
result: args.status === "completed" ? args.result : null,
|
|
671
|
+
errorMessage: args.status === "completed" ? null : args.errorMessage,
|
|
672
|
+
parentMailboxStatus,
|
|
673
|
+
terminalAt: new Date(nowMs),
|
|
674
|
+
updatedAt: new Date(nowMs)
|
|
675
|
+
}).where(and(eq(juniorAgentInvocations.invocationId, args.invocationId), inArray(juniorAgentInvocations.status, NON_TERMINAL_AGENT_INVOCATION_STATUSES)));
|
|
676
|
+
return await getAgentInvocation(args.invocationId);
|
|
677
|
+
}
|
|
678
|
+
/**
|
|
679
|
+
* End a pending agent invocation whose input the running agent invocation of
|
|
680
|
+
* the same child read as a steering message. The running agent invocation
|
|
681
|
+
* reports for both, so this one is never sent to the parent.
|
|
682
|
+
*/
|
|
683
|
+
async function markAgentInvocationSteered(invocationId, nowMs = Date.now()) {
|
|
684
|
+
await getSqlExecutor().db().update(juniorAgentInvocations).set({
|
|
685
|
+
status: "completed",
|
|
686
|
+
result: "",
|
|
687
|
+
parentMailboxStatus: "steered",
|
|
688
|
+
terminalAt: new Date(nowMs),
|
|
689
|
+
updatedAt: new Date(nowMs)
|
|
690
|
+
}).where(and(eq(juniorAgentInvocations.invocationId, invocationId), eq(juniorAgentInvocations.status, "pending")));
|
|
691
|
+
return await getAgentInvocation(invocationId);
|
|
692
|
+
}
|
|
693
|
+
/** Return whether an agent invocation has finished. */
|
|
694
|
+
function isTerminalAgentInvocation(invocation) {
|
|
695
|
+
return TERMINAL_AGENT_INVOCATION_STATUSES.includes(invocation.status);
|
|
696
|
+
}
|
|
697
|
+
export { AgentMessageQueueFullError as A, ConversationTurnBoundaryError as B, markAgentInvocationParentMailboxAppended as C, parseAgentInvocationResultMessageId as D, markAgentInvocationSteered as E, agentNameSchema as F, isCooperativeTurnYieldError as G, TurnInputCommitLostError as H, AGENT_TYPES as I, markTurnClosed as J, isTurnInputCommitLostError as K, agentTypeInstructions as L, AgentNameInUseError as M, CHILD_AGENT_RESULTS as N, reportReplacedAgentInvocation as O, PARENT_AGENT_TARGET as P, agentTypeStartsWithParentHistory as R, markAgentInvocationMailboxAppended as S, markAgentInvocationRunning as T, TurnInputDeferredError as U, CooperativeTurnYieldError as V, getConversationTurnBoundaryError as W, markTurnFailed as X, markTurnCompleted as Y, startActiveTurn as Z, getLatestAgentInvocationForConversation as _, createAgentInvocationFromLatest as a, listPendingAgentInvocationMailboxAppends as b, getAgentInvocation as c, getAgentInvocationIdForResult as d, getAgentInvocationMessageId as f, getChildAgentLockName as g, getChildAgentLabel as h, createAgentInvocationForMessage as i, AgentMessageTargetError as j, AgentInvocationLimitError as k, getAgentInvocationIdForEvents as l, getAgentInvocationTurnId as m, completeAgentInvocation as n, findChildAgentConversationId as o, getAgentInvocationResultMessageId as p, isTurnInputDeferredError as q, createAgentInvocation as r, getActiveAgentInvocationForConversation as s, NON_TERMINAL_AGENT_INVOCATION_STATUSES as t, getAgentInvocationIdForMessage as u, invocationFromRow as v, markAgentInvocationPaused as w, listPendingAgentInvocationResults as x, isTerminalAgentInvocation as y, formatAgentTypes as z };
|