@sentry/junior 0.137.0 → 0.139.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-MJXMD7JP.js} +11 -9
- package/dist/api/schema/task.d.ts +100 -1
- package/dist/api/schema.d.ts +2 -2
- package/dist/api/schema.js +9 -1
- package/dist/api.js +202 -55
- package/dist/app.d.ts +7 -0
- package/dist/app.js +601 -1356
- package/dist/{catalog-runtime-MD66YLFV.js → catalog-runtime-GCCFYPIK.js} +6 -6
- package/dist/chat/agent/request.d.ts +44 -4
- 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/credentials/context.d.ts +14 -0
- package/dist/chat/experimental.d.ts +16 -0
- package/dist/chat/resource-events/actor.d.ts +23 -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 +1 -5
- 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/tasks/execution-stats.d.ts +34 -3
- package/dist/chat/tasks/read.d.ts +3 -1
- package/dist/chat/tools/runtime/spawn-agent.d.ts +31 -0
- package/dist/chat/tools/types.d.ts +2 -0
- package/dist/{chunk-T6Z5S6PQ.js → chunk-2ECYPYKP.js} +1 -1
- package/dist/chunk-3T2JAGRH.js +115 -0
- package/dist/chunk-3Y47VMHV.js +6 -0
- package/dist/chunk-4V4QIYJH.js +41 -0
- package/dist/{chunk-SYCX4DOK.js → chunk-5GSZWRE2.js} +434 -65
- package/dist/chunk-5IDFPCON.js +46 -0
- package/dist/{chunk-36EG34YI.js → chunk-6OHTDR4F.js} +1 -1
- package/dist/chunk-6SU2DT27.js +441 -0
- package/dist/{chunk-UG6ZYPBI.js → chunk-75I3UBIL.js} +73 -60
- package/dist/chunk-AEQISMMU.js +711 -0
- package/dist/{chunk-5PNJMOY5.js → chunk-C2N6MNO4.js} +9 -2
- package/dist/{chunk-BZAMASJP.js → chunk-CXH25EL4.js} +2 -2
- package/dist/{chunk-K2NCQJKH.js → chunk-DW4PMHNW.js} +3 -3
- package/dist/chunk-EM225ITJ.js +56 -0
- package/dist/{chunk-UOXATPSI.js → chunk-G52UXKGO.js} +7 -5
- package/dist/chunk-GI52BW74.js +207 -0
- package/dist/{chunk-3Q342EJJ.js → chunk-GWQSZT4C.js} +2 -2
- package/dist/{chunk-EPBBYMNE.js → chunk-HUO3UTAQ.js} +1 -1
- package/dist/{chunk-WPPXIAFB.js → chunk-IIESAYDL.js} +17 -434
- package/dist/chunk-IUFM5JWV.js +24 -0
- package/dist/{chunk-UZQ4VY3M.js → chunk-J3S57AXY.js} +1 -1
- package/dist/{chunk-DQXAAVTR.js → chunk-J6EJQCHY.js} +46 -1471
- package/dist/{chunk-6ZK6FZIV.js → chunk-KCBZSJRU.js} +6 -27
- package/dist/chunk-MQ5RYATN.js +1523 -0
- package/dist/{chunk-5G34IEK2.js → chunk-MXTVUV3I.js} +2 -56
- package/dist/{chunk-YRPC6MJY.js → chunk-MXVD2BDS.js} +1 -1
- package/dist/{chunk-POBSY47Q.js → chunk-PJW6U46K.js} +337 -306
- package/dist/chunk-Q6QZTTLS.js +55 -0
- package/dist/{chunk-ZFS67WIR.js → chunk-QTBKPZDA.js} +57 -6
- package/dist/chunk-R776HKPK.js +81 -0
- package/dist/{chunk-77VDLF3Z.js → chunk-RUIXF3YQ.js} +10 -0
- package/dist/chunk-U27DSXKB.js +714 -0
- package/dist/{chunk-XZ7NPXAB.js → chunk-U7CMWQ7Z.js} +1 -1
- package/dist/{chunk-RB4NJKEX.js → chunk-UDBUS533.js} +125 -51
- package/dist/chunk-WQRX57EA.js +668 -0
- package/dist/chunk-WZH4JV7R.js +320 -0
- package/dist/{chunk-HCIVH3JS.js → chunk-YCTDA7BD.js} +28 -0
- package/dist/{chunk-YNFERGKP.js → chunk-YGZ2OLXL.js} +1 -1
- package/dist/{chunk-EWDSB24B.js → chunk-ZQCGQMF2.js} +16 -776
- package/dist/{chunk-KJ4HGRHN.js → chunk-ZYQSPQYR.js} +515 -601
- package/dist/cli/chat.js +68 -29
- package/dist/cli/check.js +7 -7
- package/dist/cli/plugins.js +15 -13
- package/dist/cli/snapshot-create.js +11 -10
- package/dist/cli/upgrade.js +8 -8
- package/dist/conversation-work-KATFHLG3.js +56 -0
- package/dist/db/schema/agent-invocations.d.ts +544 -0
- package/dist/db/schema/task-executions.d.ts +23 -2
- package/dist/db/schema.d.ts +563 -2
- package/dist/{db-66E7VDNC.js → db-UXPPGHDF.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-4ZYMD5UA.js} +28 -19
- package/dist/{runner-L2BKJKOD.js → runner-MZSEONRZ.js} +32 -18
- package/dist/{sandbox-egress-signals-A5HWIQH4.js → sandbox-egress-signals-EKPAVWQL.js} +2 -2
- package/dist/{skills-HOQ2NSPW.js → skills-IMVPXSQ7.js} +7 -7
- package/dist/spawn-S5LSVOCE.js +38 -0
- package/dist/{validation-ENHI7S2C.js → validation-ZKCY2BVR.js} +7 -7
- package/dist/version.js +1 -1
- package/dist/work-GG3XDYRX.js +46 -0
- package/dist/worker-23AUELCJ.js +24 -0
- package/migrations/0021_first_warhawk.sql +35 -0
- package/migrations/0022_task_execution_status.sql +3 -0
- package/migrations/meta/0021_snapshot.json +2176 -0
- package/migrations/meta/0022_snapshot.json +2186 -0
- package/migrations/meta/_journal.json +15 -1
- package/package.json +4 -4
- package/dist/chunk-6QW2RRQT.js +0 -233
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
import {
|
|
2
|
+
recordTaskExecution
|
|
3
|
+
} from "./chunk-GI52BW74.js";
|
|
1
4
|
import {
|
|
2
5
|
pluginTaskId,
|
|
3
6
|
pluginTaskParamsSchema,
|
|
@@ -22,7 +25,10 @@ import {
|
|
|
22
25
|
persistThreadStateById,
|
|
23
26
|
stripRuntimeTurnContext,
|
|
24
27
|
updateConversationStats
|
|
25
|
-
} from "./chunk-
|
|
28
|
+
} from "./chunk-PJW6U46K.js";
|
|
29
|
+
import {
|
|
30
|
+
JUNIOR_THREAD_STATE_TTL_MS
|
|
31
|
+
} from "./chunk-3Y47VMHV.js";
|
|
26
32
|
import {
|
|
27
33
|
createPluginConversationEvents,
|
|
28
34
|
createPluginEmbedder,
|
|
@@ -30,26 +36,30 @@ import {
|
|
|
30
36
|
createPluginModel,
|
|
31
37
|
createPluginState,
|
|
32
38
|
getPlugins
|
|
33
|
-
} from "./chunk-
|
|
39
|
+
} from "./chunk-IIESAYDL.js";
|
|
34
40
|
import {
|
|
41
|
+
getConversationStore,
|
|
35
42
|
getDb,
|
|
36
43
|
sameActorIdentity
|
|
37
|
-
} from "./chunk-
|
|
44
|
+
} from "./chunk-QTBKPZDA.js";
|
|
38
45
|
import {
|
|
39
46
|
getStateAdapter
|
|
40
|
-
} from "./chunk-
|
|
47
|
+
} from "./chunk-MXTVUV3I.js";
|
|
41
48
|
import {
|
|
42
49
|
ProviderError,
|
|
43
50
|
findProviderError,
|
|
44
51
|
getProviderErrorAttributes,
|
|
45
52
|
getProviderErrorUserMessage
|
|
46
|
-
} from "./chunk-
|
|
53
|
+
} from "./chunk-75I3UBIL.js";
|
|
54
|
+
import {
|
|
55
|
+
credentialSubjectSchema
|
|
56
|
+
} from "./chunk-RUIXF3YQ.js";
|
|
47
57
|
import {
|
|
48
58
|
parseSlackMessageTs
|
|
49
59
|
} from "./chunk-F3B2YJ76.js";
|
|
50
60
|
import {
|
|
51
61
|
buildTurnFailureResponse
|
|
52
|
-
} from "./chunk-
|
|
62
|
+
} from "./chunk-2ECYPYKP.js";
|
|
53
63
|
|
|
54
64
|
// src/chat/runtime/delivered-turn-state.ts
|
|
55
65
|
function buildDeliveredTurnStatePatch(args) {
|
|
@@ -85,7 +95,7 @@ import {
|
|
|
85
95
|
|
|
86
96
|
// src/chat/services/turn-session-routing.ts
|
|
87
97
|
async function resolveTurnSessionRouting(args) {
|
|
88
|
-
const conversationStore = args.conversationStore ?? (await import("./db-
|
|
98
|
+
const conversationStore = args.conversationStore ?? (await import("./db-UXPPGHDF.js")).getConversationStore();
|
|
89
99
|
const conversation = await conversationStore.get({
|
|
90
100
|
conversationId: args.conversationId
|
|
91
101
|
});
|
|
@@ -100,6 +110,397 @@ async function resolveTurnSessionRouting(args) {
|
|
|
100
110
|
};
|
|
101
111
|
}
|
|
102
112
|
|
|
113
|
+
// src/chat/agent-dispatch/store.ts
|
|
114
|
+
import { createHash } from "crypto";
|
|
115
|
+
import {
|
|
116
|
+
destinationSchema,
|
|
117
|
+
destinationVisibilitySchema,
|
|
118
|
+
isSlackDestination,
|
|
119
|
+
replyAttributionSchema,
|
|
120
|
+
sourceSchema
|
|
121
|
+
} from "@sentry/junior-plugin-api";
|
|
122
|
+
import { z } from "zod";
|
|
123
|
+
var DISPATCH_PREFIX = "junior:agent_dispatch";
|
|
124
|
+
var DISPATCH_LOCK_TTL_MS = 10 * 60 * 1e3;
|
|
125
|
+
var DISPATCH_MAILBOX_APPEND_INDEX_LOCK_TTL_MS = 1e4;
|
|
126
|
+
var DISPATCH_MAILBOX_APPEND_INDEX_MAX_LENGTH = 1e4;
|
|
127
|
+
var nonEmptyExactStringSchema = z.string().min(1).refine(
|
|
128
|
+
(value) => value === value.trim() && value.toLowerCase() !== "unknown"
|
|
129
|
+
);
|
|
130
|
+
var pendingDispatchMailboxAppendIndexSchema = z.array(
|
|
131
|
+
nonEmptyExactStringSchema
|
|
132
|
+
);
|
|
133
|
+
var dispatchStatusSchema = z.enum([
|
|
134
|
+
"pending",
|
|
135
|
+
"running",
|
|
136
|
+
"awaiting_resume",
|
|
137
|
+
"completed",
|
|
138
|
+
"failed",
|
|
139
|
+
"blocked"
|
|
140
|
+
]);
|
|
141
|
+
var dispatchActorSchema = z.object({
|
|
142
|
+
platform: z.literal("system"),
|
|
143
|
+
name: nonEmptyExactStringSchema
|
|
144
|
+
}).strict();
|
|
145
|
+
var dispatchRecordSchema = z.object({
|
|
146
|
+
actor: dispatchActorSchema,
|
|
147
|
+
createdAtMs: z.number().finite(),
|
|
148
|
+
credentialSubject: credentialSubjectSchema.optional(),
|
|
149
|
+
destination: destinationSchema,
|
|
150
|
+
destinationVisibility: destinationVisibilitySchema,
|
|
151
|
+
errorMessage: z.string().optional(),
|
|
152
|
+
id: nonEmptyExactStringSchema,
|
|
153
|
+
idempotencyKey: z.string().min(1),
|
|
154
|
+
input: z.string().min(1),
|
|
155
|
+
metadata: z.record(z.string(), z.string()).optional(),
|
|
156
|
+
plugin: nonEmptyExactStringSchema,
|
|
157
|
+
replyAttribution: replyAttributionSchema.optional(),
|
|
158
|
+
resultMessageTs: z.string().optional(),
|
|
159
|
+
source: sourceSchema,
|
|
160
|
+
status: dispatchStatusSchema,
|
|
161
|
+
updatedAtMs: z.number().finite()
|
|
162
|
+
}).strict().superRefine((record, ctx) => {
|
|
163
|
+
if (!isSlackDestination(record.destination)) {
|
|
164
|
+
ctx.addIssue({
|
|
165
|
+
code: z.ZodIssueCode.custom,
|
|
166
|
+
message: "Dispatch destination platform must be slack",
|
|
167
|
+
path: ["destination"]
|
|
168
|
+
});
|
|
169
|
+
return;
|
|
170
|
+
}
|
|
171
|
+
const subject = record.credentialSubject;
|
|
172
|
+
if (!subject) {
|
|
173
|
+
return;
|
|
174
|
+
}
|
|
175
|
+
if (subject.allowedWhen === "private-direct-conversation") {
|
|
176
|
+
if (!record.destination.channelId.startsWith("D")) {
|
|
177
|
+
ctx.addIssue({
|
|
178
|
+
code: z.ZodIssueCode.custom,
|
|
179
|
+
message: "Dispatch credentialSubject requires a private direct Slack destination",
|
|
180
|
+
path: ["credentialSubject"]
|
|
181
|
+
});
|
|
182
|
+
return;
|
|
183
|
+
}
|
|
184
|
+
if (subject.binding.type !== "slack-direct-conversation" || subject.binding.teamId !== record.destination.teamId || subject.binding.channelId !== record.destination.channelId) {
|
|
185
|
+
ctx.addIssue({
|
|
186
|
+
code: z.ZodIssueCode.custom,
|
|
187
|
+
message: "Dispatch credentialSubject binding must match destination",
|
|
188
|
+
path: ["credentialSubject", "binding"]
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
return;
|
|
192
|
+
}
|
|
193
|
+
if (subject.binding.type !== subject.allowedWhen || subject.binding.plugin !== record.plugin || subject.binding.taskId !== subject.taskId) {
|
|
194
|
+
ctx.addIssue({
|
|
195
|
+
code: z.ZodIssueCode.custom,
|
|
196
|
+
message: "Dispatch credentialSubject binding must match task",
|
|
197
|
+
path: ["credentialSubject", "binding"]
|
|
198
|
+
});
|
|
199
|
+
}
|
|
200
|
+
});
|
|
201
|
+
var storedDispatchRecordSchema = dispatchRecordSchema.extend({
|
|
202
|
+
attempt: z.number().int().nonnegative().optional(),
|
|
203
|
+
lastCallbackAtMs: z.number().finite().optional(),
|
|
204
|
+
leaseExpiresAtMs: z.number().finite().optional(),
|
|
205
|
+
maxAttempts: z.number().int().positive().optional(),
|
|
206
|
+
version: z.number().int().positive().optional()
|
|
207
|
+
});
|
|
208
|
+
function getDispatchStorageKey(id) {
|
|
209
|
+
return `${DISPATCH_PREFIX}:record:${id}`;
|
|
210
|
+
}
|
|
211
|
+
function dispatchLockKey(id) {
|
|
212
|
+
return `${DISPATCH_PREFIX}:lock:${id}`;
|
|
213
|
+
}
|
|
214
|
+
function pendingDispatchMailboxAppendIndexKey() {
|
|
215
|
+
return `${DISPATCH_PREFIX}:incomplete`;
|
|
216
|
+
}
|
|
217
|
+
function pendingDispatchMailboxAppendIndexLockKey() {
|
|
218
|
+
return `${DISPATCH_PREFIX}:incomplete:lock`;
|
|
219
|
+
}
|
|
220
|
+
function normalizeMetadata(metadata) {
|
|
221
|
+
if (!metadata) {
|
|
222
|
+
return void 0;
|
|
223
|
+
}
|
|
224
|
+
const entries = Object.entries(metadata).filter(
|
|
225
|
+
(entry) => typeof entry[0] === "string" && typeof entry[1] === "string"
|
|
226
|
+
);
|
|
227
|
+
return entries.length > 0 ? Object.fromEntries(entries) : void 0;
|
|
228
|
+
}
|
|
229
|
+
function buildDispatchId(plugin, idempotencyKey) {
|
|
230
|
+
const digest = createHash("sha256").update(plugin).update("\0").update(idempotencyKey).digest("hex").slice(0, 32);
|
|
231
|
+
return `dispatch_${digest}`;
|
|
232
|
+
}
|
|
233
|
+
function parseDispatchRecord(value) {
|
|
234
|
+
const parsed = parseStoredDispatchRecord(value);
|
|
235
|
+
return parsed?.record;
|
|
236
|
+
}
|
|
237
|
+
function parseStoredDispatchRecord(value) {
|
|
238
|
+
const parsed = storedDispatchRecordSchema.safeParse(value);
|
|
239
|
+
if (!parsed.success) {
|
|
240
|
+
return void 0;
|
|
241
|
+
}
|
|
242
|
+
const {
|
|
243
|
+
attempt: _attempt,
|
|
244
|
+
lastCallbackAtMs: _lastCallbackAtMs,
|
|
245
|
+
leaseExpiresAtMs: _leaseExpiresAtMs,
|
|
246
|
+
maxAttempts: _maxAttempts,
|
|
247
|
+
version: _version,
|
|
248
|
+
...record
|
|
249
|
+
} = parsed.data;
|
|
250
|
+
return {
|
|
251
|
+
...typeof parsed.data.leaseExpiresAtMs === "number" ? { legacyLeaseExpiresAtMs: parsed.data.leaseExpiresAtMs } : {},
|
|
252
|
+
record
|
|
253
|
+
};
|
|
254
|
+
}
|
|
255
|
+
function getDispatchConversationId(dispatch) {
|
|
256
|
+
return `agent-dispatch:${dispatch.id}`;
|
|
257
|
+
}
|
|
258
|
+
function getDispatchInputMessageId(dispatchId) {
|
|
259
|
+
return `agent-dispatch:${dispatchId}`;
|
|
260
|
+
}
|
|
261
|
+
function getLegacyDispatchInputMessageId(dispatchId) {
|
|
262
|
+
return `dispatch:${dispatchId}:user`;
|
|
263
|
+
}
|
|
264
|
+
function getDispatchInputMessageIds(dispatchId) {
|
|
265
|
+
return [
|
|
266
|
+
getDispatchInputMessageId(dispatchId),
|
|
267
|
+
getLegacyDispatchInputMessageId(dispatchId)
|
|
268
|
+
];
|
|
269
|
+
}
|
|
270
|
+
function getDispatchTurnId(dispatchId) {
|
|
271
|
+
return `dispatch:${dispatchId}`;
|
|
272
|
+
}
|
|
273
|
+
function toDispatchProjection(record) {
|
|
274
|
+
return {
|
|
275
|
+
id: record.id,
|
|
276
|
+
status: record.status,
|
|
277
|
+
...record.resultMessageTs ? { resultMessageTs: record.resultMessageTs } : {},
|
|
278
|
+
...record.errorMessage ? { errorMessage: record.errorMessage } : {}
|
|
279
|
+
};
|
|
280
|
+
}
|
|
281
|
+
function isTerminalDispatchStatus(status) {
|
|
282
|
+
return status === "completed" || status === "failed" || status === "blocked";
|
|
283
|
+
}
|
|
284
|
+
async function withDispatchLock(dispatchId, task) {
|
|
285
|
+
const state = getStateAdapter();
|
|
286
|
+
await state.connect();
|
|
287
|
+
const lock = await state.acquireLock(
|
|
288
|
+
dispatchLockKey(dispatchId),
|
|
289
|
+
DISPATCH_LOCK_TTL_MS
|
|
290
|
+
);
|
|
291
|
+
if (!lock) {
|
|
292
|
+
throw new Error(`Could not acquire dispatch lock for ${dispatchId}`);
|
|
293
|
+
}
|
|
294
|
+
try {
|
|
295
|
+
return await task(state);
|
|
296
|
+
} finally {
|
|
297
|
+
await state.releaseLock(lock);
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
async function putRecord(state, record) {
|
|
301
|
+
const parsed = dispatchRecordSchema.safeParse(record);
|
|
302
|
+
if (!parsed.success) {
|
|
303
|
+
throw new Error(
|
|
304
|
+
`Dispatch record is invalid: ${parsed.error.issues.map((issue) => `${issue.path.join(".") || "<root>"} ${issue.message}`).join("; ")}`
|
|
305
|
+
);
|
|
306
|
+
}
|
|
307
|
+
const next = parsed.data;
|
|
308
|
+
await state.set(
|
|
309
|
+
getDispatchStorageKey(next.id),
|
|
310
|
+
next,
|
|
311
|
+
JUNIOR_THREAD_STATE_TTL_MS
|
|
312
|
+
);
|
|
313
|
+
}
|
|
314
|
+
async function updatePendingDispatchMailboxAppendIndex(state, update) {
|
|
315
|
+
const lock = await state.acquireLock(
|
|
316
|
+
pendingDispatchMailboxAppendIndexLockKey(),
|
|
317
|
+
DISPATCH_MAILBOX_APPEND_INDEX_LOCK_TTL_MS
|
|
318
|
+
);
|
|
319
|
+
if (!lock) {
|
|
320
|
+
throw new Error("Could not acquire dispatch mailbox append index lock");
|
|
321
|
+
}
|
|
322
|
+
try {
|
|
323
|
+
const stored = await state.get(pendingDispatchMailboxAppendIndexKey());
|
|
324
|
+
const ids = stored === void 0 || stored === null ? [] : pendingDispatchMailboxAppendIndexSchema.parse(stored);
|
|
325
|
+
const next = [...new Set(update([...new Set(ids)]))].slice(
|
|
326
|
+
-DISPATCH_MAILBOX_APPEND_INDEX_MAX_LENGTH
|
|
327
|
+
);
|
|
328
|
+
if (next.length === ids.length && next.every((id, index) => id === ids[index])) {
|
|
329
|
+
return;
|
|
330
|
+
}
|
|
331
|
+
await state.set(
|
|
332
|
+
pendingDispatchMailboxAppendIndexKey(),
|
|
333
|
+
next,
|
|
334
|
+
JUNIOR_THREAD_STATE_TTL_MS
|
|
335
|
+
);
|
|
336
|
+
} finally {
|
|
337
|
+
await state.releaseLock(lock);
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
async function getDispatchRecord(id) {
|
|
341
|
+
const state = getStateAdapter();
|
|
342
|
+
await state.connect();
|
|
343
|
+
return parseDispatchRecord(await state.get(getDispatchStorageKey(id)));
|
|
344
|
+
}
|
|
345
|
+
async function createOrGetDispatch(args) {
|
|
346
|
+
const id = buildDispatchId(args.plugin, args.options.idempotencyKey);
|
|
347
|
+
return await withDispatchLock(id, async (state) => {
|
|
348
|
+
const existing = parseDispatchRecord(
|
|
349
|
+
await state.get(getDispatchStorageKey(id))
|
|
350
|
+
);
|
|
351
|
+
if (existing) {
|
|
352
|
+
return { record: existing, status: "already_exists" };
|
|
353
|
+
}
|
|
354
|
+
const metadata = normalizeMetadata(args.options.metadata);
|
|
355
|
+
const record = {
|
|
356
|
+
actor: { platform: "system", name: args.plugin },
|
|
357
|
+
createdAtMs: args.nowMs,
|
|
358
|
+
...args.options.credentialSubject ? { credentialSubject: args.options.credentialSubject } : {},
|
|
359
|
+
destination: args.options.destination,
|
|
360
|
+
destinationVisibility: args.options.destinationVisibility,
|
|
361
|
+
id,
|
|
362
|
+
idempotencyKey: args.options.idempotencyKey,
|
|
363
|
+
input: args.options.input,
|
|
364
|
+
...metadata ? { metadata } : {},
|
|
365
|
+
plugin: args.plugin,
|
|
366
|
+
...args.options.replyAttribution ? { replyAttribution: args.options.replyAttribution } : {},
|
|
367
|
+
status: "pending",
|
|
368
|
+
source: args.options.source,
|
|
369
|
+
updatedAtMs: args.nowMs
|
|
370
|
+
};
|
|
371
|
+
await updatePendingDispatchMailboxAppendIndex(
|
|
372
|
+
state,
|
|
373
|
+
(ids) => ids.includes(id) ? ids : [...ids, id]
|
|
374
|
+
);
|
|
375
|
+
await putRecord(state, record);
|
|
376
|
+
return { record, status: "created" };
|
|
377
|
+
});
|
|
378
|
+
}
|
|
379
|
+
async function transitionDispatch(id, transition) {
|
|
380
|
+
return await withDispatchLock(id, async (state) => {
|
|
381
|
+
const current = parseDispatchRecord(
|
|
382
|
+
await state.get(getDispatchStorageKey(id))
|
|
383
|
+
);
|
|
384
|
+
if (!current) {
|
|
385
|
+
return void 0;
|
|
386
|
+
}
|
|
387
|
+
const transitioned = transition(current);
|
|
388
|
+
if (transitioned === current) {
|
|
389
|
+
return current;
|
|
390
|
+
}
|
|
391
|
+
const next = {
|
|
392
|
+
...transitioned,
|
|
393
|
+
updatedAtMs: Date.now()
|
|
394
|
+
};
|
|
395
|
+
await putRecord(state, next);
|
|
396
|
+
return next;
|
|
397
|
+
});
|
|
398
|
+
}
|
|
399
|
+
async function markDispatchRunning(id) {
|
|
400
|
+
return await transitionDispatch(
|
|
401
|
+
id,
|
|
402
|
+
(record) => isTerminalDispatchStatus(record.status) ? record : { ...record, status: "running", errorMessage: void 0 }
|
|
403
|
+
);
|
|
404
|
+
}
|
|
405
|
+
async function markDispatchAwaitingResume(id) {
|
|
406
|
+
return await transitionDispatch(
|
|
407
|
+
id,
|
|
408
|
+
(record) => isTerminalDispatchStatus(record.status) ? record : {
|
|
409
|
+
...record,
|
|
410
|
+
status: "awaiting_resume"
|
|
411
|
+
}
|
|
412
|
+
);
|
|
413
|
+
}
|
|
414
|
+
async function recordEventTaskExecution(previous, next, status) {
|
|
415
|
+
if (!next || next.status !== status || previous?.status === status) return;
|
|
416
|
+
if (next.plugin !== "junior") return;
|
|
417
|
+
const eventTaskId = next.metadata?.eventTaskId;
|
|
418
|
+
if (!eventTaskId) return;
|
|
419
|
+
const conversationId = getDispatchConversationId(next);
|
|
420
|
+
const conversation = await getConversationStore().get({ conversationId });
|
|
421
|
+
await recordTaskExecution("event", eventTaskId, {
|
|
422
|
+
...conversation ? { conversationId } : {},
|
|
423
|
+
executionId: next.id,
|
|
424
|
+
nowMs: next.updatedAtMs,
|
|
425
|
+
status
|
|
426
|
+
});
|
|
427
|
+
}
|
|
428
|
+
async function markDispatchBlocked(id, errorMessage, resultMessageTs) {
|
|
429
|
+
const previous = await getDispatchRecord(id);
|
|
430
|
+
const next = await transitionDispatch(
|
|
431
|
+
id,
|
|
432
|
+
(record) => isTerminalDispatchStatus(record.status) ? record : {
|
|
433
|
+
...record,
|
|
434
|
+
errorMessage,
|
|
435
|
+
...resultMessageTs ? { resultMessageTs } : {},
|
|
436
|
+
status: "blocked"
|
|
437
|
+
}
|
|
438
|
+
);
|
|
439
|
+
await recordEventTaskExecution(previous, next, "blocked");
|
|
440
|
+
return next;
|
|
441
|
+
}
|
|
442
|
+
async function markDispatchCompleted(id, resultMessageTs) {
|
|
443
|
+
const previous = await getDispatchRecord(id);
|
|
444
|
+
const next = await transitionDispatch(
|
|
445
|
+
id,
|
|
446
|
+
(record) => isTerminalDispatchStatus(record.status) ? record : {
|
|
447
|
+
...record,
|
|
448
|
+
errorMessage: void 0,
|
|
449
|
+
...resultMessageTs ? { resultMessageTs } : {},
|
|
450
|
+
status: "completed"
|
|
451
|
+
}
|
|
452
|
+
);
|
|
453
|
+
await recordEventTaskExecution(previous, next, "completed");
|
|
454
|
+
return next;
|
|
455
|
+
}
|
|
456
|
+
async function markDispatchFailed(id, errorMessage, resultMessageTs) {
|
|
457
|
+
const previous = await getDispatchRecord(id);
|
|
458
|
+
const next = await transitionDispatch(
|
|
459
|
+
id,
|
|
460
|
+
(record) => isTerminalDispatchStatus(record.status) ? record : {
|
|
461
|
+
...record,
|
|
462
|
+
errorMessage,
|
|
463
|
+
...resultMessageTs ? { resultMessageTs } : {},
|
|
464
|
+
status: "failed"
|
|
465
|
+
}
|
|
466
|
+
);
|
|
467
|
+
await recordEventTaskExecution(previous, next, "failed");
|
|
468
|
+
return next;
|
|
469
|
+
}
|
|
470
|
+
async function confirmDispatchMailboxAppend(id) {
|
|
471
|
+
const state = getStateAdapter();
|
|
472
|
+
await state.connect();
|
|
473
|
+
await updatePendingDispatchMailboxAppendIndex(
|
|
474
|
+
state,
|
|
475
|
+
(ids) => ids.filter((candidate) => candidate !== id)
|
|
476
|
+
);
|
|
477
|
+
}
|
|
478
|
+
async function listPendingDispatchMailboxAppends() {
|
|
479
|
+
const state = getStateAdapter();
|
|
480
|
+
await state.connect();
|
|
481
|
+
const stored = await state.get(pendingDispatchMailboxAppendIndexKey());
|
|
482
|
+
return stored === void 0 || stored === null ? [] : [...new Set(pendingDispatchMailboxAppendIndexSchema.parse(stored))];
|
|
483
|
+
}
|
|
484
|
+
async function claimDispatchMailboxAppend(id, nowMs) {
|
|
485
|
+
return await withDispatchLock(id, async (state) => {
|
|
486
|
+
const stored = parseStoredDispatchRecord(
|
|
487
|
+
await state.get(getDispatchStorageKey(id))
|
|
488
|
+
);
|
|
489
|
+
if (!stored || stored.legacyLeaseExpiresAtMs && stored.legacyLeaseExpiresAtMs > nowMs) {
|
|
490
|
+
return void 0;
|
|
491
|
+
}
|
|
492
|
+
await putRecord(state, stored.record);
|
|
493
|
+
return stored.record;
|
|
494
|
+
});
|
|
495
|
+
}
|
|
496
|
+
async function getPluginDispatchProjection(args) {
|
|
497
|
+
const record = await getDispatchRecord(args.id);
|
|
498
|
+
if (!record || record.plugin !== args.plugin) {
|
|
499
|
+
return void 0;
|
|
500
|
+
}
|
|
501
|
+
return toDispatchProjection(record);
|
|
502
|
+
}
|
|
503
|
+
|
|
103
504
|
// src/chat/plugins/task-runner.ts
|
|
104
505
|
var PLUGIN_TASK_LOCK_TTL_MS = 5 * 60 * 1e3;
|
|
105
506
|
function isRecord(value) {
|
|
@@ -229,7 +630,7 @@ function messageExistedAtRunCompletion(message, completedAtMs) {
|
|
|
229
630
|
const messageTimestampMs = conversationMessageTimestampMs(message);
|
|
230
631
|
return messageTimestampMs !== void 0 && messageTimestampMs <= completedAtMs;
|
|
231
632
|
}
|
|
232
|
-
async function loadConversationContextTranscriptEntries(record, source) {
|
|
633
|
+
async function loadConversationContextTranscriptEntries(record, source, runActor) {
|
|
233
634
|
if (source.platform !== "slack" || isPrivateSource(source)) {
|
|
234
635
|
return [];
|
|
235
636
|
}
|
|
@@ -260,7 +661,7 @@ async function loadConversationContextTranscriptEntries(record, source) {
|
|
|
260
661
|
authority: "context",
|
|
261
662
|
...author ? { actor: author } : {}
|
|
262
663
|
},
|
|
263
|
-
isRunActor: sameActorIdentity(author,
|
|
664
|
+
isRunActor: sameActorIdentity(author, runActor)
|
|
264
665
|
});
|
|
265
666
|
}
|
|
266
667
|
return entries;
|
|
@@ -295,13 +696,18 @@ async function loadPluginRun(params) {
|
|
|
295
696
|
const routing = await resolveTurnSessionRouting({
|
|
296
697
|
conversationId: params.conversationId
|
|
297
698
|
});
|
|
699
|
+
const runActor = record.actors[0] ?? (record.dispatchId ? (await getDispatchRecord(record.dispatchId))?.actor : void 0);
|
|
298
700
|
const runEntries = turnMessagesWithProvenance(record).map(
|
|
299
|
-
({ message, provenance }) => runTranscriptEntry(message, provenance,
|
|
701
|
+
({ message, provenance }) => runTranscriptEntry(message, provenance, runActor)
|
|
300
702
|
).filter((entry) => Boolean(entry));
|
|
301
703
|
const runMessageTexts = new Set(
|
|
302
704
|
runEntries.filter((entry) => entry.type === "message").map((entry) => entry.text)
|
|
303
705
|
);
|
|
304
|
-
const contextEntries = (await loadConversationContextTranscriptEntries(
|
|
706
|
+
const contextEntries = (await loadConversationContextTranscriptEntries(
|
|
707
|
+
record,
|
|
708
|
+
routing.source,
|
|
709
|
+
runActor
|
|
710
|
+
)).filter(
|
|
305
711
|
(entry) => entry.type !== "message" || !runMessageTexts.has(entry.text)
|
|
306
712
|
);
|
|
307
713
|
return pluginRunContextSchema.parse({
|
|
@@ -311,7 +717,7 @@ async function loadPluginRun(params) {
|
|
|
311
717
|
// Derived from the full run provenance on the record, not the sliced or
|
|
312
718
|
// stripped transcript, so it reflects every committed instruction actor.
|
|
313
719
|
actors: record.actors,
|
|
314
|
-
...
|
|
720
|
+
...runActor ? { actor: runActor } : {},
|
|
315
721
|
runId: record.sessionId,
|
|
316
722
|
source: routing.source,
|
|
317
723
|
transcript: [...contextEntries, ...runEntries]
|
|
@@ -487,58 +893,6 @@ function finalizeFailedTurnReplyWithEvent(args) {
|
|
|
487
893
|
};
|
|
488
894
|
}
|
|
489
895
|
|
|
490
|
-
// src/chat/conversations/turn-lifecycle.ts
|
|
491
|
-
var ConversationTurnLifecycleService = class {
|
|
492
|
-
constructor(events) {
|
|
493
|
-
this.events = events;
|
|
494
|
-
}
|
|
495
|
-
events;
|
|
496
|
-
/** Record the turn start once after every input message is durable. */
|
|
497
|
-
async start(input) {
|
|
498
|
-
await this.events.append(input.conversationId, [
|
|
499
|
-
{
|
|
500
|
-
idempotencyKey: `turn:${input.turnId}:started`,
|
|
501
|
-
createdAtMs: input.createdAtMs,
|
|
502
|
-
data: {
|
|
503
|
-
type: "turn_started",
|
|
504
|
-
turnId: input.turnId,
|
|
505
|
-
inputMessageIds: input.inputMessageIds,
|
|
506
|
-
surface: input.surface
|
|
507
|
-
}
|
|
508
|
-
}
|
|
509
|
-
]);
|
|
510
|
-
}
|
|
511
|
-
/** Record successful or intentional-silence completion once. */
|
|
512
|
-
async complete(input) {
|
|
513
|
-
await this.events.append(input.conversationId, [
|
|
514
|
-
{
|
|
515
|
-
idempotencyKey: `turn:${input.turnId}:terminal`,
|
|
516
|
-
createdAtMs: input.createdAtMs,
|
|
517
|
-
data: {
|
|
518
|
-
type: "turn_completed",
|
|
519
|
-
turnId: input.turnId,
|
|
520
|
-
outcome: input.outcome
|
|
521
|
-
}
|
|
522
|
-
}
|
|
523
|
-
]);
|
|
524
|
-
}
|
|
525
|
-
/** Record a classified failure once without accepting raw error details. */
|
|
526
|
-
async fail(input) {
|
|
527
|
-
await this.events.append(input.conversationId, [
|
|
528
|
-
{
|
|
529
|
-
idempotencyKey: `turn:${input.turnId}:terminal`,
|
|
530
|
-
createdAtMs: input.createdAtMs,
|
|
531
|
-
data: {
|
|
532
|
-
type: "turn_failed",
|
|
533
|
-
turnId: input.turnId,
|
|
534
|
-
failureCode: input.failureCode,
|
|
535
|
-
...input.eventId ? { eventId: input.eventId } : {}
|
|
536
|
-
}
|
|
537
|
-
}
|
|
538
|
-
]);
|
|
539
|
-
}
|
|
540
|
-
};
|
|
541
|
-
|
|
542
896
|
// src/chat/runtime/turn-user-message.ts
|
|
543
897
|
function getTurnUserMessage(conversation, sessionId) {
|
|
544
898
|
for (let index = conversation.messages.length - 1; index >= 0; index -= 1) {
|
|
@@ -600,6 +954,22 @@ async function persistAuthPauseTurnState(args) {
|
|
|
600
954
|
}
|
|
601
955
|
|
|
602
956
|
export {
|
|
957
|
+
getDispatchConversationId,
|
|
958
|
+
getDispatchInputMessageId,
|
|
959
|
+
getDispatchInputMessageIds,
|
|
960
|
+
getDispatchTurnId,
|
|
961
|
+
isTerminalDispatchStatus,
|
|
962
|
+
getDispatchRecord,
|
|
963
|
+
createOrGetDispatch,
|
|
964
|
+
markDispatchRunning,
|
|
965
|
+
markDispatchAwaitingResume,
|
|
966
|
+
markDispatchBlocked,
|
|
967
|
+
markDispatchCompleted,
|
|
968
|
+
markDispatchFailed,
|
|
969
|
+
confirmDispatchMailboxAppend,
|
|
970
|
+
listPendingDispatchMailboxAppends,
|
|
971
|
+
claimDispatchMailboxAppend,
|
|
972
|
+
getPluginDispatchProjection,
|
|
603
973
|
buildDeliveredTurnStatePatch,
|
|
604
974
|
getTurnUserMessage,
|
|
605
975
|
getTurnUserMessageId,
|
|
@@ -612,7 +982,6 @@ export {
|
|
|
612
982
|
requireTurnFailureEventId,
|
|
613
983
|
getAgentTurnDiagnosticsAttributes,
|
|
614
984
|
finalizeFailedTurnReplyWithEvent,
|
|
615
|
-
ConversationTurnLifecycleService,
|
|
616
985
|
completeAuthPauseTurn,
|
|
617
986
|
persistAuthPauseTurnState
|
|
618
987
|
};
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
// src/chat/experimental.ts
|
|
2
|
+
var EXPERIMENTAL_FEATURES = ["subagents"];
|
|
3
|
+
var EXPERIMENTAL_FEATURE_SET = new Set(EXPERIMENTAL_FEATURES);
|
|
4
|
+
var configuredExperimental = {};
|
|
5
|
+
function isExperimentalFeature(value) {
|
|
6
|
+
return EXPERIMENTAL_FEATURE_SET.has(value);
|
|
7
|
+
}
|
|
8
|
+
function setExperimentalFeatures(config) {
|
|
9
|
+
const previous = { ...configuredExperimental };
|
|
10
|
+
if (config === void 0) {
|
|
11
|
+
configuredExperimental = {};
|
|
12
|
+
return previous;
|
|
13
|
+
}
|
|
14
|
+
const next = {};
|
|
15
|
+
for (const [rawName, enabled] of Object.entries(config)) {
|
|
16
|
+
if (enabled === void 0) {
|
|
17
|
+
continue;
|
|
18
|
+
}
|
|
19
|
+
if (typeof enabled !== "boolean") {
|
|
20
|
+
throw new Error(
|
|
21
|
+
`experimental.${rawName} must be a boolean when provided`
|
|
22
|
+
);
|
|
23
|
+
}
|
|
24
|
+
if (!isExperimentalFeature(rawName)) {
|
|
25
|
+
throw new Error(
|
|
26
|
+
`experimental.${rawName} is not a known experimental feature. Known features: ${EXPERIMENTAL_FEATURES.join(", ")}`
|
|
27
|
+
);
|
|
28
|
+
}
|
|
29
|
+
next[rawName] = enabled;
|
|
30
|
+
}
|
|
31
|
+
configuredExperimental = next;
|
|
32
|
+
return previous;
|
|
33
|
+
}
|
|
34
|
+
function getExperimentalFeatures() {
|
|
35
|
+
return { ...configuredExperimental };
|
|
36
|
+
}
|
|
37
|
+
function isExperimentalFeatureEnabled(feature) {
|
|
38
|
+
return configuredExperimental[feature] === true;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export {
|
|
42
|
+
EXPERIMENTAL_FEATURES,
|
|
43
|
+
setExperimentalFeatures,
|
|
44
|
+
getExperimentalFeatures,
|
|
45
|
+
isExperimentalFeatureEnabled
|
|
46
|
+
};
|