@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.
Files changed (103) hide show
  1. package/dist/{agent-hooks-MZOKEP3J.js → agent-hooks-MJXMD7JP.js} +11 -9
  2. package/dist/api/schema/task.d.ts +100 -1
  3. package/dist/api/schema.d.ts +2 -2
  4. package/dist/api/schema.js +9 -1
  5. package/dist/api.js +202 -55
  6. package/dist/app.d.ts +7 -0
  7. package/dist/app.js +601 -1356
  8. package/dist/{catalog-runtime-MD66YLFV.js → catalog-runtime-GCCFYPIK.js} +6 -6
  9. package/dist/chat/agent/request.d.ts +44 -4
  10. package/dist/chat/agent-dispatch/heartbeat.d.ts +5 -0
  11. package/dist/chat/agent-dispatch/types.d.ts +1 -1
  12. package/dist/chat/agent-invocations/errors.d.ts +8 -0
  13. package/dist/chat/agent-invocations/spawn.d.ts +12 -0
  14. package/dist/chat/agent-invocations/store.d.ts +44 -0
  15. package/dist/chat/agent-invocations/types.d.ts +449 -0
  16. package/dist/chat/agent-invocations/work.d.ts +31 -0
  17. package/dist/chat/config.d.ts +1 -0
  18. package/dist/chat/conversations/sql/store.d.ts +8 -0
  19. package/dist/chat/conversations/store.d.ts +7 -0
  20. package/dist/chat/credentials/context.d.ts +14 -0
  21. package/dist/chat/experimental.d.ts +16 -0
  22. package/dist/chat/resource-events/actor.d.ts +23 -0
  23. package/dist/chat/runtime/agent-runner.d.ts +2 -1
  24. package/dist/chat/services/auth-pause.d.ts +0 -1
  25. package/dist/chat/services/mcp-auth-orchestration.d.ts +3 -2
  26. package/dist/chat/services/plugin-auth-orchestration.d.ts +3 -2
  27. package/dist/chat/state/turn-session.d.ts +1 -5
  28. package/dist/chat/task-execution/state.d.ts +23 -11
  29. package/dist/chat/task-execution/store.d.ts +20 -11
  30. package/dist/chat/task-execution/worker.d.ts +2 -2
  31. package/dist/chat/tasks/execution-stats.d.ts +34 -3
  32. package/dist/chat/tasks/read.d.ts +3 -1
  33. package/dist/chat/tools/runtime/spawn-agent.d.ts +31 -0
  34. package/dist/chat/tools/types.d.ts +2 -0
  35. package/dist/{chunk-T6Z5S6PQ.js → chunk-2ECYPYKP.js} +1 -1
  36. package/dist/chunk-3T2JAGRH.js +115 -0
  37. package/dist/chunk-3Y47VMHV.js +6 -0
  38. package/dist/chunk-4V4QIYJH.js +41 -0
  39. package/dist/{chunk-SYCX4DOK.js → chunk-5GSZWRE2.js} +434 -65
  40. package/dist/chunk-5IDFPCON.js +46 -0
  41. package/dist/{chunk-36EG34YI.js → chunk-6OHTDR4F.js} +1 -1
  42. package/dist/chunk-6SU2DT27.js +441 -0
  43. package/dist/{chunk-UG6ZYPBI.js → chunk-75I3UBIL.js} +73 -60
  44. package/dist/chunk-AEQISMMU.js +711 -0
  45. package/dist/{chunk-5PNJMOY5.js → chunk-C2N6MNO4.js} +9 -2
  46. package/dist/{chunk-BZAMASJP.js → chunk-CXH25EL4.js} +2 -2
  47. package/dist/{chunk-K2NCQJKH.js → chunk-DW4PMHNW.js} +3 -3
  48. package/dist/chunk-EM225ITJ.js +56 -0
  49. package/dist/{chunk-UOXATPSI.js → chunk-G52UXKGO.js} +7 -5
  50. package/dist/chunk-GI52BW74.js +207 -0
  51. package/dist/{chunk-3Q342EJJ.js → chunk-GWQSZT4C.js} +2 -2
  52. package/dist/{chunk-EPBBYMNE.js → chunk-HUO3UTAQ.js} +1 -1
  53. package/dist/{chunk-WPPXIAFB.js → chunk-IIESAYDL.js} +17 -434
  54. package/dist/chunk-IUFM5JWV.js +24 -0
  55. package/dist/{chunk-UZQ4VY3M.js → chunk-J3S57AXY.js} +1 -1
  56. package/dist/{chunk-DQXAAVTR.js → chunk-J6EJQCHY.js} +46 -1471
  57. package/dist/{chunk-6ZK6FZIV.js → chunk-KCBZSJRU.js} +6 -27
  58. package/dist/chunk-MQ5RYATN.js +1523 -0
  59. package/dist/{chunk-5G34IEK2.js → chunk-MXTVUV3I.js} +2 -56
  60. package/dist/{chunk-YRPC6MJY.js → chunk-MXVD2BDS.js} +1 -1
  61. package/dist/{chunk-POBSY47Q.js → chunk-PJW6U46K.js} +337 -306
  62. package/dist/chunk-Q6QZTTLS.js +55 -0
  63. package/dist/{chunk-ZFS67WIR.js → chunk-QTBKPZDA.js} +57 -6
  64. package/dist/chunk-R776HKPK.js +81 -0
  65. package/dist/{chunk-77VDLF3Z.js → chunk-RUIXF3YQ.js} +10 -0
  66. package/dist/chunk-U27DSXKB.js +714 -0
  67. package/dist/{chunk-XZ7NPXAB.js → chunk-U7CMWQ7Z.js} +1 -1
  68. package/dist/{chunk-RB4NJKEX.js → chunk-UDBUS533.js} +125 -51
  69. package/dist/chunk-WQRX57EA.js +668 -0
  70. package/dist/chunk-WZH4JV7R.js +320 -0
  71. package/dist/{chunk-HCIVH3JS.js → chunk-YCTDA7BD.js} +28 -0
  72. package/dist/{chunk-YNFERGKP.js → chunk-YGZ2OLXL.js} +1 -1
  73. package/dist/{chunk-EWDSB24B.js → chunk-ZQCGQMF2.js} +16 -776
  74. package/dist/{chunk-KJ4HGRHN.js → chunk-ZYQSPQYR.js} +515 -601
  75. package/dist/cli/chat.js +68 -29
  76. package/dist/cli/check.js +7 -7
  77. package/dist/cli/plugins.js +15 -13
  78. package/dist/cli/snapshot-create.js +11 -10
  79. package/dist/cli/upgrade.js +8 -8
  80. package/dist/conversation-work-KATFHLG3.js +56 -0
  81. package/dist/db/schema/agent-invocations.d.ts +544 -0
  82. package/dist/db/schema/task-executions.d.ts +23 -2
  83. package/dist/db/schema.d.ts +563 -2
  84. package/dist/{db-66E7VDNC.js → db-UXPPGHDF.js} +7 -7
  85. package/dist/experimental-HH6OM6UH.js +13 -0
  86. package/dist/instrumentation.js +4 -4
  87. package/dist/nitro.js +4 -3
  88. package/dist/{oauth-callback-server-7QPD4Q7P.js → oauth-callback-server-4ZYMD5UA.js} +28 -19
  89. package/dist/{runner-L2BKJKOD.js → runner-MZSEONRZ.js} +32 -18
  90. package/dist/{sandbox-egress-signals-A5HWIQH4.js → sandbox-egress-signals-EKPAVWQL.js} +2 -2
  91. package/dist/{skills-HOQ2NSPW.js → skills-IMVPXSQ7.js} +7 -7
  92. package/dist/spawn-S5LSVOCE.js +38 -0
  93. package/dist/{validation-ENHI7S2C.js → validation-ZKCY2BVR.js} +7 -7
  94. package/dist/version.js +1 -1
  95. package/dist/work-GG3XDYRX.js +46 -0
  96. package/dist/worker-23AUELCJ.js +24 -0
  97. package/migrations/0021_first_warhawk.sql +35 -0
  98. package/migrations/0022_task_execution_status.sql +3 -0
  99. package/migrations/meta/0021_snapshot.json +2176 -0
  100. package/migrations/meta/0022_snapshot.json +2186 -0
  101. package/migrations/meta/_journal.json +15 -1
  102. package/package.json +4 -4
  103. 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-POBSY47Q.js";
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-WPPXIAFB.js";
39
+ } from "./chunk-IIESAYDL.js";
34
40
  import {
41
+ getConversationStore,
35
42
  getDb,
36
43
  sameActorIdentity
37
- } from "./chunk-ZFS67WIR.js";
44
+ } from "./chunk-QTBKPZDA.js";
38
45
  import {
39
46
  getStateAdapter
40
- } from "./chunk-5G34IEK2.js";
47
+ } from "./chunk-MXTVUV3I.js";
41
48
  import {
42
49
  ProviderError,
43
50
  findProviderError,
44
51
  getProviderErrorAttributes,
45
52
  getProviderErrorUserMessage
46
- } from "./chunk-UG6ZYPBI.js";
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-T6Z5S6PQ.js";
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-66E7VDNC.js")).getConversationStore();
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, record.actor)
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, record.actor)
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(record, routing.source)).filter(
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
- ...record.actor ? { actor: record.actor } : {},
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
+ };
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  createPluginCatalogRuntime
3
- } from "./chunk-BZAMASJP.js";
3
+ } from "./chunk-CXH25EL4.js";
4
4
 
5
5
  // src/chat/plugins/catalog-runtime.ts
6
6
  var pluginCatalogRuntime = createPluginCatalogRuntime();