@sentry/junior 0.107.1 → 0.109.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 (68) hide show
  1. package/dist/{agent-hooks-ICPIJAFY.js → agent-hooks-FHHVLNPA.js} +12 -12
  2. package/dist/api/conversations/events.d.ts +1 -1
  3. package/dist/api/conversations/list.d.ts +1 -0
  4. package/dist/api/conversations/schema.d.ts +57 -6
  5. package/dist/api/schema.js +1 -1
  6. package/dist/api.js +116 -27
  7. package/dist/app.d.ts +1 -0
  8. package/dist/app.js +1741 -458
  9. package/dist/{catalog-runtime-FOFNPR3P.js → catalog-runtime-DMGJSLV2.js} +6 -4
  10. package/dist/chat/conversations/history.d.ts +16 -5
  11. package/dist/chat/conversations/message-projection.d.ts +3 -5
  12. package/dist/chat/conversations/sql/store.d.ts +0 -7
  13. package/dist/chat/log-context.d.ts +40 -0
  14. package/dist/chat/logging.d.ts +3 -26
  15. package/dist/chat/resource-events/store.d.ts +6 -0
  16. package/dist/chat/runtime/slack-resume.d.ts +2 -2
  17. package/dist/chat/runtime/slack-runtime.d.ts +3 -0
  18. package/dist/chat/services/turn-session-record.d.ts +0 -2
  19. package/dist/chat/slack/reply.d.ts +7 -19
  20. package/dist/chat/state/conversation.d.ts +1 -0
  21. package/dist/chat/task-execution/slack-work.d.ts +237 -10
  22. package/dist/chat/tool-support/turn-deadline-result.d.ts +3 -0
  23. package/dist/chunk-3SXUV423.js +10 -0
  24. package/dist/{chunk-YFQ7CQDE.js → chunk-72OMGPDS.js} +1017 -39
  25. package/dist/{chunk-2XXEKR44.js → chunk-7Q6WCISF.js} +1 -1
  26. package/dist/{chunk-NVOTGWYX.js → chunk-FENLIFJD.js} +97 -601
  27. package/dist/{chunk-IGHMVDWI.js → chunk-FWJRXN4F.js} +3 -3
  28. package/dist/{chunk-PUAMO4PI.js → chunk-HHVY4SMP.js} +41 -7
  29. package/dist/{chunk-SPUAJVVH.js → chunk-HO5NT6OE.js} +5 -7
  30. package/dist/{chunk-O24R4DCK.js → chunk-ISMWX44P.js} +93 -55
  31. package/dist/{chunk-7FBGKXPE.js → chunk-IVVGFRQQ.js} +5 -0
  32. package/dist/{chunk-EDLNHZH3.js → chunk-IXKSQGIO.js} +226 -191
  33. package/dist/{chunk-SS67LUOK.js → chunk-JNAVF45E.js} +6 -7
  34. package/dist/{chunk-DY7TRYB5.js → chunk-MGUMR35T.js} +1 -1
  35. package/dist/{chunk-CQ7KSO2B.js → chunk-MXW4GL53.js} +11 -15
  36. package/dist/{chunk-RT5BUHR5.js → chunk-O3JMSZLI.js} +1 -1
  37. package/dist/chunk-OCD3OZRK.js +125 -0
  38. package/dist/{chunk-B2Z2H66D.js → chunk-OUHNWH46.js} +60 -5
  39. package/dist/{chunk-VIXFG2VC.js → chunk-PLB7MRCQ.js} +1 -2
  40. package/dist/{chunk-RMVOAJRL.js → chunk-QY636HZU.js} +114 -12
  41. package/dist/{chunk-VH6KWKG2.js → chunk-RGX4V75M.js} +3 -3
  42. package/dist/{chunk-B7PUBQQM.js → chunk-SYAQIR5Z.js} +1 -1
  43. package/dist/chunk-TMBRFULF.js +471 -0
  44. package/dist/{chunk-YIPD3L6L.js → chunk-TT6GMXNA.js} +8 -10
  45. package/dist/{chunk-YT72TOEK.js → chunk-WHMOBKT7.js} +2 -2
  46. package/dist/cli/chat.js +23 -23
  47. package/dist/cli/check.js +8 -6
  48. package/dist/cli/plugins.js +16 -16
  49. package/dist/cli/snapshot-warmup.js +10 -9
  50. package/dist/cli/upgrade.js +314 -3505
  51. package/dist/db/db.d.ts +1 -1
  52. package/dist/{db-DIGO4TGW.js → db-V3NU72O2.js} +8 -6
  53. package/dist/instrumentation.js +3 -2
  54. package/dist/nitro.js +5 -4
  55. package/dist/{runner-ACR2HAIC.js → runner-JYBCQBLK.js} +17 -20
  56. package/dist/{skills-ITNHBARB.js → skills-U6MDAST7.js} +7 -5
  57. package/dist/{validation-AGQ46ZGV.js → validation-72N3JVRU.js} +7 -5
  58. package/dist/version.d.ts +2 -0
  59. package/dist/version.js +7 -0
  60. package/migrations/0006_drop_legacy_conversation_messages.sql +1 -0
  61. package/migrations/README.md +12 -9
  62. package/migrations/meta/0006_snapshot.json +948 -0
  63. package/migrations/meta/_journal.json +7 -0
  64. package/package.json +11 -7
  65. package/dist/chunk-4YF7Z6IA.js +0 -1061
  66. package/dist/chunk-AUUOHQAT.js +0 -115
  67. package/dist/chunk-G3E7SCME.js +0 -28
  68. package/dist/chunk-YNP2ATQX.js +0 -1282
@@ -1,20 +1,21 @@
1
1
  import {
2
- JUNIOR_THREAD_STATE_TTL_MS,
3
- abandonAgentTurnSessionRecord,
4
- buildConversationStatePatch,
5
- getAgentTurnSessionRecord,
6
- getAgentTurnSessionRecordForResume,
7
- upsertAgentTurnSessionRecord
8
- } from "./chunk-4YF7Z6IA.js";
9
- import {
10
- addAgentTurnUsage
2
+ addAgentTurnUsage,
3
+ agentTurnUsageSchema
11
4
  } from "./chunk-MU6HHZEN.js";
12
5
  import {
13
6
  getStateAdapter
14
- } from "./chunk-B2Z2H66D.js";
7
+ } from "./chunk-OUHNWH46.js";
15
8
  import {
16
- getConversationEventStore
17
- } from "./chunk-NVOTGWYX.js";
9
+ contextProvenance,
10
+ createSqlConversationEventStore,
11
+ getConversationEventStore,
12
+ getConversationStore,
13
+ getSqlExecutor,
14
+ instructionActors,
15
+ instructionProvenanceFor,
16
+ sanitizePostgresJson,
17
+ withConversationEventLock
18
+ } from "./chunk-FENLIFJD.js";
18
19
  import {
19
20
  sleep
20
21
  } from "./chunk-4ZNGQH7C.js";
@@ -23,22 +24,345 @@ import {
23
24
  escapeXml,
24
25
  getPiMessageRole,
25
26
  resolveGatewayModel,
27
+ retainRuntimeTurnContext,
26
28
  standardModelId,
29
+ stripRuntimeTurnContext,
27
30
  trimTrailingAssistantMessages
28
- } from "./chunk-SS67LUOK.js";
31
+ } from "./chunk-JNAVF45E.js";
32
+ import {
33
+ toStoredSlackActor
34
+ } from "./chunk-PLB7MRCQ.js";
29
35
  import {
30
36
  getActiveTraceId,
31
37
  logException,
32
38
  logWarn,
33
39
  setSpanAttributes
34
- } from "./chunk-O24R4DCK.js";
40
+ } from "./chunk-ISMWX44P.js";
35
41
  import {
36
42
  isRecord,
43
+ toOptionalNumber,
37
44
  toOptionalString
38
45
  } from "./chunk-OB42YVAE.js";
39
46
  import {
40
47
  parseSlackMessageTs
41
- } from "./chunk-7FBGKXPE.js";
48
+ } from "./chunk-IVVGFRQQ.js";
49
+
50
+ // src/chat/conversations/projection.ts
51
+ import { isDeepStrictEqual } from "util";
52
+
53
+ // src/chat/pi/messages.ts
54
+ import { z } from "zod";
55
+ var piMessageSchema = z.object({
56
+ role: z.string()
57
+ }).passthrough().transform((value) => value);
58
+ var piContentMessageSchema = z.object({
59
+ content: z.array(z.unknown()),
60
+ role: z.string().min(1)
61
+ }).passthrough().transform((value) => value);
62
+
63
+ // src/chat/pi/conversation-events.ts
64
+ function authorizationObservationMessage(data, createdAtMs) {
65
+ const label = data.kind === "mcp" ? "MCP authorization" : "Authorization";
66
+ return piMessageSchema.parse({
67
+ role: "user",
68
+ content: [
69
+ {
70
+ type: "text",
71
+ text: `${label} completed for provider "${data.provider}". Continue the blocked request and retry the provider operation if needed.`
72
+ }
73
+ ],
74
+ timestamp: createdAtMs
75
+ });
76
+ }
77
+ function messageEventProvenance(data) {
78
+ return data.provenance ?? contextProvenance;
79
+ }
80
+ function durableMessages(message) {
81
+ return stripRuntimeTurnContext([piMessageSchema.parse(message)]);
82
+ }
83
+ function projectConversationEvents(events, options) {
84
+ const messages = [];
85
+ const provenance = [];
86
+ const seqs = [];
87
+ let modelProfile = "standard";
88
+ let modelId;
89
+ for (const event of events) {
90
+ if (options?.maxSeq !== void 0 && event.seq > options.maxSeq) break;
91
+ if (event.data.type === "unknown") {
92
+ throw new Error(
93
+ `Unsupported conversation event "${event.data.originalType}" at seq ${event.seq} (schema version ${event.schemaVersion})`
94
+ );
95
+ }
96
+ if (event.data.type === "compaction" || event.data.type === "handoff" || event.data.type === "rollback") {
97
+ modelProfile = event.data.modelProfile;
98
+ modelId = event.data.modelId;
99
+ for (const replacement of event.data.replacementHistory) {
100
+ for (const message of durableMessages(replacement.message)) {
101
+ messages.push(message);
102
+ provenance.push(replacement.provenance ?? contextProvenance);
103
+ seqs.push(replacement.sourceEventSeq ?? event.seq);
104
+ }
105
+ }
106
+ continue;
107
+ }
108
+ if (event.data.type === "agent_step") {
109
+ for (const message of durableMessages(event.data.message)) {
110
+ messages.push(message);
111
+ provenance.push(messageEventProvenance(event.data));
112
+ seqs.push(event.seq);
113
+ }
114
+ continue;
115
+ }
116
+ if (event.data.type === "authorization_completed") {
117
+ messages.push(
118
+ authorizationObservationMessage(event.data, event.createdAtMs)
119
+ );
120
+ provenance.push(contextProvenance);
121
+ seqs.push(event.seq);
122
+ }
123
+ }
124
+ return { messages, provenance, seqs, modelProfile, modelId };
125
+ }
126
+
127
+ // src/chat/conversations/projection.ts
128
+ function connectedMcpProvidersFromEvents(events) {
129
+ const providers = /* @__PURE__ */ new Set();
130
+ for (const event of events) {
131
+ if (event.data.type === "mcp_provider_connected") {
132
+ providers.add(event.data.provider);
133
+ }
134
+ }
135
+ return [...providers].sort((left, right) => left.localeCompare(right));
136
+ }
137
+ function isUserMessage(message) {
138
+ return message.role === "user";
139
+ }
140
+ function countMatchingPrefix(left, right) {
141
+ const limit = Math.min(left.length, right.length);
142
+ for (let index = 0; index < limit; index += 1) {
143
+ if (!isDeepStrictEqual(left[index], right[index])) {
144
+ return index;
145
+ }
146
+ }
147
+ return limit;
148
+ }
149
+ function normalizeDurableMessage(message) {
150
+ return piMessageSchema.parse(
151
+ JSON.parse(JSON.stringify(sanitizePostgresJson(message)))
152
+ );
153
+ }
154
+ function resolveCommitProvenance(args) {
155
+ if (args.explicitProvenance) {
156
+ if (args.explicitProvenance.length !== args.nextMessages.length) {
157
+ throw new Error("commit provenance must align one-to-one with messages");
158
+ }
159
+ return args.explicitProvenance;
160
+ }
161
+ const matchingPrefix = args.matchingPrefix;
162
+ const provenance = args.nextMessages.map(
163
+ (_, index) => index < matchingPrefix ? args.existing.provenance[index] ?? contextProvenance : contextProvenance
164
+ );
165
+ if (args.newMessageProvenance) {
166
+ for (let index = args.nextMessages.length - 1; index >= matchingPrefix; index -= 1) {
167
+ if (isUserMessage(args.nextMessages[index])) {
168
+ provenance[index] = args.newMessageProvenance;
169
+ break;
170
+ }
171
+ }
172
+ }
173
+ if (args.trailingMessageProvenance) {
174
+ if (args.trailingMessageProvenance.length > provenance.length) {
175
+ throw new Error(
176
+ "trailing commit provenance cannot exceed committed messages"
177
+ );
178
+ }
179
+ const newMessageCount = args.nextMessages.length - matchingPrefix;
180
+ if (args.trailingMessageProvenance.length > newMessageCount) {
181
+ throw new Error(
182
+ "trailing commit provenance must align to newly committed messages"
183
+ );
184
+ }
185
+ const start = provenance.length - args.trailingMessageProvenance.length;
186
+ args.trailingMessageProvenance.forEach((entry, offset) => {
187
+ provenance[start + offset] = entry;
188
+ });
189
+ }
190
+ return provenance;
191
+ }
192
+ async function loadProjection(args) {
193
+ const events = await getConversationEventStore().loadCurrentHistory(
194
+ args.conversationId
195
+ );
196
+ return projectConversationEvents(events).messages;
197
+ }
198
+ async function loadConversationProjection(args) {
199
+ const events = await getConversationEventStore().loadCurrentHistory(
200
+ args.conversationId
201
+ );
202
+ return projectConversationEvents(events);
203
+ }
204
+ async function openConversationProjection(args) {
205
+ const events = await getConversationEventStore().loadCurrentHistory(
206
+ args.conversationId
207
+ );
208
+ return projectConversationEvents(events);
209
+ }
210
+ async function loadTurnProjection(args) {
211
+ const eventStore = getConversationEventStore();
212
+ if (args.committedSeq < 0) {
213
+ return projectConversationEvents(
214
+ await eventStore.loadCurrentHistory(args.conversationId)
215
+ );
216
+ }
217
+ const historyEvents = await eventStore.loadHistoryContaining(
218
+ args.conversationId,
219
+ args.committedSeq,
220
+ args.includeTail ? void 0 : args.committedSeq
221
+ );
222
+ if (!historyEvents) {
223
+ return void 0;
224
+ }
225
+ return projectConversationEvents(historyEvents);
226
+ }
227
+ async function loadConnectedMcpProviders(args) {
228
+ const events = await getConversationEventStore().loadCurrentHistory(
229
+ args.conversationId
230
+ );
231
+ return connectedMcpProvidersFromEvents(events);
232
+ }
233
+ function messageTimestamp(message) {
234
+ const timestamp = message.timestamp;
235
+ return typeof timestamp === "number" ? timestamp : Date.now();
236
+ }
237
+ async function commitMessages(args) {
238
+ const executor = args.executor ?? getSqlExecutor();
239
+ return await withConversationEventLock(
240
+ executor,
241
+ args.conversationId,
242
+ async () => executor.transaction(
243
+ async () => await commitMessagesLocked(args, executor)
244
+ )
245
+ );
246
+ }
247
+ async function commitMessagesLocked(args, executor) {
248
+ const eventStore = createSqlConversationEventStore(executor);
249
+ const currentEvents = await eventStore.loadCurrentHistory(
250
+ args.conversationId
251
+ );
252
+ const current = projectConversationEvents(currentEvents);
253
+ const nextLocalMessages = stripRuntimeTurnContext(args.messages).map(
254
+ normalizeDurableMessage
255
+ );
256
+ const matchingPrefix = countMatchingPrefix(
257
+ current.messages,
258
+ nextLocalMessages
259
+ );
260
+ const nextLocalProvenance = resolveCommitProvenance({
261
+ existing: current,
262
+ nextMessages: nextLocalMessages,
263
+ matchingPrefix,
264
+ ...args.provenance ? { explicitProvenance: args.provenance } : {},
265
+ ...args.trailingMessageProvenance ? { trailingMessageProvenance: args.trailingMessageProvenance } : {},
266
+ ...args.newMessageProvenance ? { newMessageProvenance: args.newMessageProvenance } : {}
267
+ });
268
+ if (matchingPrefix === current.messages.length) {
269
+ const newMessages = nextLocalMessages.slice(matchingPrefix);
270
+ await eventStore.append(
271
+ args.conversationId,
272
+ newMessages.map((message, index) => ({
273
+ data: {
274
+ type: "agent_step",
275
+ message,
276
+ provenance: nextLocalProvenance[matchingPrefix + index]
277
+ },
278
+ createdAtMs: messageTimestamp(message)
279
+ }))
280
+ );
281
+ } else {
282
+ throw new Error(
283
+ `Agent history for ${args.conversationId} changed before its committed boundary`
284
+ );
285
+ }
286
+ const committedEvents = await eventStore.loadCurrentHistory(
287
+ args.conversationId
288
+ );
289
+ const committed = projectConversationEvents(committedEvents);
290
+ return {
291
+ committedSeq: committedEvents.at(-1)?.seq ?? -1,
292
+ historyVersion: committedEvents.at(-1)?.historyVersion ?? 0,
293
+ messageSeqs: committed.seqs,
294
+ messages: nextLocalMessages,
295
+ provenance: nextLocalProvenance
296
+ };
297
+ }
298
+ async function recordMcpProviderConnected(args) {
299
+ const eventStore = getConversationEventStore();
300
+ const events = await eventStore.loadCurrentHistory(args.conversationId);
301
+ if (connectedMcpProvidersFromEvents(events).includes(args.provider)) {
302
+ return;
303
+ }
304
+ await eventStore.append(args.conversationId, [
305
+ {
306
+ data: { type: "mcp_provider_connected", provider: args.provider },
307
+ createdAtMs: Date.now()
308
+ }
309
+ ]);
310
+ }
311
+ async function recordAuthorizationRequested(args) {
312
+ const eventStore = getConversationEventStore();
313
+ const events = await eventStore.loadCurrentHistory(args.conversationId);
314
+ if (events.some(
315
+ (event) => event.data.type === "authorization_requested" && event.data.authorizationId === args.authorizationId
316
+ )) {
317
+ return;
318
+ }
319
+ await eventStore.append(args.conversationId, [
320
+ {
321
+ data: {
322
+ type: "authorization_requested",
323
+ kind: args.kind,
324
+ provider: args.provider,
325
+ actorId: args.actorId,
326
+ authorizationId: args.authorizationId,
327
+ delivery: args.delivery
328
+ },
329
+ createdAtMs: Date.now()
330
+ }
331
+ ]);
332
+ }
333
+ async function recordAuthorizationCompleted(args) {
334
+ const eventStore = getConversationEventStore();
335
+ const events = await eventStore.loadCurrentHistory(args.conversationId);
336
+ if (events.some(
337
+ (event) => event.data.type === "authorization_completed" && event.data.authorizationId === args.authorizationId
338
+ )) {
339
+ return;
340
+ }
341
+ await eventStore.append(args.conversationId, [
342
+ {
343
+ data: {
344
+ type: "authorization_completed",
345
+ kind: args.kind,
346
+ provider: args.provider,
347
+ actorId: args.actorId,
348
+ authorizationId: args.authorizationId
349
+ },
350
+ createdAtMs: Date.now()
351
+ }
352
+ ]);
353
+ }
354
+ async function recordToolExecutionStarted(args) {
355
+ await getConversationEventStore().append(args.conversationId, [
356
+ {
357
+ data: {
358
+ type: "tool_execution_started",
359
+ toolCallId: args.toolCallId,
360
+ toolName: args.toolName
361
+ },
362
+ createdAtMs: args.createdAtMs ?? Date.now()
363
+ }
364
+ ]);
365
+ }
42
366
 
43
367
  // src/chat/state/turn-id.ts
44
368
  function buildDeterministicTurnId(messageId) {
@@ -145,6 +469,531 @@ async function persistWithRetry(persist) {
145
469
  throw lastError;
146
470
  }
147
471
 
472
+ // src/chat/state/turn-session.ts
473
+ import { THREAD_STATE_TTL_MS } from "chat";
474
+ import {
475
+ actorSchema,
476
+ destinationSchema,
477
+ sourceSchema
478
+ } from "@sentry/junior-plugin-api";
479
+ import { z as z2 } from "zod";
480
+
481
+ // src/chat/state/turn-session-keys.ts
482
+ var AGENT_TURN_SESSION_PREFIX = "junior:agent_turn_session";
483
+ function agentTurnSessionKey(conversationId, sessionId) {
484
+ return `${AGENT_TURN_SESSION_PREFIX}:${conversationId}:${sessionId}`;
485
+ }
486
+ function agentTurnSessionConversationIndexKey(conversationId) {
487
+ return `${AGENT_TURN_SESSION_PREFIX}:conversation:${conversationId}:index`;
488
+ }
489
+
490
+ // src/chat/state/turn-session.ts
491
+ var AGENT_TURN_SESSION_INDEX_KEY = `${AGENT_TURN_SESSION_PREFIX}:index`;
492
+ var AGENT_TURN_SESSION_INDEX_MAX_LENGTH = 5e3;
493
+ var AGENT_TURN_SESSION_TTL_MS = THREAD_STATE_TTL_MS;
494
+ var agentTurnSessionStatusSchema = z2.enum([
495
+ "running",
496
+ "awaiting_resume",
497
+ "completed",
498
+ "failed",
499
+ "abandoned"
500
+ ]);
501
+ var agentTurnSurfaceSchema = z2.enum([
502
+ "slack",
503
+ "api",
504
+ "scheduler",
505
+ "internal"
506
+ ]);
507
+ var agentTurnResumeReasonSchema = z2.enum([
508
+ "timeout",
509
+ "auth",
510
+ "yield",
511
+ "retry"
512
+ ]);
513
+ var nonNegativeNumberSchema = z2.number().finite().nonnegative();
514
+ var seqCursorSchema = z2.number().int().min(-1);
515
+ var agentTurnSessionSummarySchema = z2.object({
516
+ channelName: z2.string().min(1).optional(),
517
+ version: z2.number().int().nonnegative(),
518
+ conversationId: z2.string().min(1),
519
+ cumulativeDurationMs: nonNegativeNumberSchema,
520
+ cumulativeUsage: agentTurnUsageSchema.optional(),
521
+ destination: destinationSchema.optional(),
522
+ source: sourceSchema.optional(),
523
+ lastProgressAtMs: nonNegativeNumberSchema,
524
+ loadedSkillNames: z2.array(z2.string()).optional(),
525
+ modelId: z2.string().min(1).optional(),
526
+ reasoningLevel: z2.string().min(1).optional(),
527
+ actor: actorSchema.optional(),
528
+ resumeReason: agentTurnResumeReasonSchema.optional(),
529
+ resumedFromSliceId: z2.number().int().nonnegative().optional(),
530
+ sessionId: z2.string().min(1),
531
+ sliceId: z2.number().int().nonnegative(),
532
+ startedAtMs: nonNegativeNumberSchema,
533
+ state: agentTurnSessionStatusSchema,
534
+ surface: agentTurnSurfaceSchema.optional(),
535
+ traceId: z2.string().optional(),
536
+ updatedAtMs: nonNegativeNumberSchema
537
+ }).strict();
538
+ var storedAgentTurnSessionRecordSchema = agentTurnSessionSummarySchema.extend({
539
+ actors: z2.array(actorSchema).optional(),
540
+ committedSeq: seqCursorSchema,
541
+ historyVersion: z2.number().int().nonnegative().optional(),
542
+ errorMessage: z2.string().optional(),
543
+ turnStartSeq: seqCursorSchema.optional(),
544
+ runtimeContext: z2.array(piMessageSchema).optional()
545
+ }).strict();
546
+ function conversationExecutionFromSummary(summary) {
547
+ const status = summary.state === "completed" || summary.state === "abandoned" ? "idle" : summary.state;
548
+ return {
549
+ status,
550
+ runId: summary.sessionId,
551
+ updatedAtMs: summary.updatedAtMs
552
+ };
553
+ }
554
+ function sessionLogActor(actor) {
555
+ return actor?.platform === "slack" ? toStoredSlackActor(actor) : void 0;
556
+ }
557
+ function parseAgentTurnSessionRecord(value) {
558
+ return storedAgentTurnSessionRecordSchema.parse(value);
559
+ }
560
+ function parseAgentTurnSessionSummary(value) {
561
+ return agentTurnSessionSummarySchema.parse(value);
562
+ }
563
+ async function appendAgentTurnSessionSummary(summary, ttlMs) {
564
+ const stateAdapter = getStateAdapter();
565
+ await Promise.all([
566
+ stateAdapter.appendToList(AGENT_TURN_SESSION_INDEX_KEY, summary, {
567
+ maxLength: AGENT_TURN_SESSION_INDEX_MAX_LENGTH,
568
+ ttlMs
569
+ }),
570
+ stateAdapter.appendToList(
571
+ agentTurnSessionConversationIndexKey(summary.conversationId),
572
+ summary,
573
+ { ttlMs }
574
+ )
575
+ ]);
576
+ }
577
+ async function recordConversationActivityMetadata(args) {
578
+ const conversationStore = args.conversationStore ?? getConversationStore();
579
+ const source = args.summary.destination?.platform === "local" ? "local" : args.summary.surface;
580
+ await conversationStore.recordActivity({
581
+ activityAtMs: args.summary.updatedAtMs,
582
+ channelName: args.summary.channelName,
583
+ conversationId: args.summary.conversationId,
584
+ destination: args.summary.destination,
585
+ nowMs: args.nowMs,
586
+ actor: sessionLogActor(args.summary.actor),
587
+ source,
588
+ visibility: args.destinationVisibility
589
+ });
590
+ await conversationStore.recordExecution({
591
+ channelName: args.summary.channelName,
592
+ conversationId: args.summary.conversationId,
593
+ createdAtMs: args.summary.startedAtMs,
594
+ destination: args.summary.destination,
595
+ execution: conversationExecutionFromSummary(args.summary),
596
+ lastActivityAtMs: args.summary.updatedAtMs,
597
+ metrics: {
598
+ durationMs: args.summary.cumulativeDurationMs,
599
+ ...args.summary.cumulativeUsage ? { usage: args.summary.cumulativeUsage } : {}
600
+ },
601
+ actor: sessionLogActor(args.summary.actor),
602
+ source,
603
+ updatedAtMs: args.nowMs,
604
+ visibility: args.destinationVisibility
605
+ });
606
+ }
607
+ function materializeAgentTurnSessionRecord(stored, piProjection, turnStartMessageIndex, restoreVolatileContext = true) {
608
+ const piMessages = restoreVolatileContext && (stored.state === "running" || stored.state === "awaiting_resume") ? restoreRuntimeContext(piProjection.messages, stored.runtimeContext) : piProjection.messages;
609
+ return {
610
+ version: stored.version,
611
+ ...stored.channelName ? { channelName: stored.channelName } : {},
612
+ conversationId: stored.conversationId,
613
+ sessionId: stored.sessionId,
614
+ sliceId: stored.sliceId,
615
+ state: stored.state,
616
+ startedAtMs: stored.startedAtMs,
617
+ lastProgressAtMs: stored.lastProgressAtMs,
618
+ updatedAtMs: stored.updatedAtMs,
619
+ piMessages,
620
+ piMessageProvenance: piProjection.provenance,
621
+ actors: stored.actors ?? instructionActors(piProjection.provenance),
622
+ cumulativeDurationMs: stored.cumulativeDurationMs,
623
+ ...stored.destination ? { destination: stored.destination } : {},
624
+ ...stored.source ? { source: stored.source } : {},
625
+ ...stored.cumulativeUsage ? { cumulativeUsage: stored.cumulativeUsage } : {},
626
+ ...stored.resumeReason ? { resumeReason: stored.resumeReason } : {},
627
+ ...stored.errorMessage ? { errorMessage: stored.errorMessage } : {},
628
+ ...stored.loadedSkillNames ? { loadedSkillNames: stored.loadedSkillNames } : {},
629
+ ...stored.modelId ? { modelId: stored.modelId } : {},
630
+ ...stored.reasoningLevel ? { reasoningLevel: stored.reasoningLevel } : {},
631
+ ...stored.actor ? { actor: stored.actor } : {},
632
+ ...stored.resumedFromSliceId !== void 0 ? { resumedFromSliceId: stored.resumedFromSliceId } : {},
633
+ ...stored.surface ? { surface: stored.surface } : {},
634
+ ...stored.traceId ? { traceId: stored.traceId } : {},
635
+ ...turnStartMessageIndex !== void 0 ? { turnStartMessageIndex } : {}
636
+ };
637
+ }
638
+ function restoreRuntimeContext(messages, runtimeContext) {
639
+ if (!runtimeContext || runtimeContext.length === 0) return messages;
640
+ const restored = [...messages];
641
+ for (const runtimeMessage of runtimeContext) {
642
+ const runtime = runtimeMessage;
643
+ const targetIndex = restored.findIndex((message) => {
644
+ const candidate = message;
645
+ return candidate.role === "user" && candidate.timestamp === runtime.timestamp;
646
+ });
647
+ if (targetIndex < 0) {
648
+ restored.push(runtimeMessage);
649
+ continue;
650
+ }
651
+ const target = restored[targetIndex];
652
+ restored[targetIndex] = {
653
+ ...restored[targetIndex],
654
+ content: [
655
+ ...Array.isArray(runtime.content) ? runtime.content : [],
656
+ ...Array.isArray(target.content) ? target.content : []
657
+ ]
658
+ };
659
+ }
660
+ return restored;
661
+ }
662
+ async function getStoredAgentTurnSessionRecord(conversationId, sessionId) {
663
+ const stateAdapter = getStateAdapter();
664
+ await stateAdapter.connect();
665
+ const value = await stateAdapter.get(
666
+ agentTurnSessionKey(conversationId, sessionId)
667
+ );
668
+ return value == null ? void 0 : parseAgentTurnSessionRecord(value);
669
+ }
670
+ async function materializeStoredAgentTurnSessionRecord(conversationId, sessionId, followCurrentReplacement) {
671
+ const parsed = await getStoredAgentTurnSessionRecord(
672
+ conversationId,
673
+ sessionId
674
+ );
675
+ if (!parsed) {
676
+ return void 0;
677
+ }
678
+ const pinnedProjection = await loadTurnProjection({
679
+ conversationId,
680
+ committedSeq: parsed.committedSeq,
681
+ // Unfinished records include the current-epoch tail so parked input
682
+ // appended after the last safe boundary stays model-visible on resume.
683
+ includeTail: parsed.state === "running" || parsed.state === "awaiting_resume"
684
+ });
685
+ if (!pinnedProjection) {
686
+ return void 0;
687
+ }
688
+ const currentHistory = await getConversationEventStore().loadCurrentHistory(conversationId);
689
+ const currentHistoryVersion = currentHistory.at(-1)?.historyVersion ?? parsed.historyVersion ?? 0;
690
+ const followsReplacement = followCurrentReplacement && (parsed.state === "running" || parsed.state === "awaiting_resume") && parsed.historyVersion !== void 0 && parsed.historyVersion !== currentHistoryVersion;
691
+ const piProjection = followsReplacement ? projectConversationEvents(currentHistory) : pinnedProjection;
692
+ const turnStartMessageIndex = followsReplacement ? 0 : parsed.turnStartSeq === void 0 ? void 0 : piProjection.seqs.filter((seq) => seq <= parsed.turnStartSeq).length;
693
+ return materializeAgentTurnSessionRecord(
694
+ parsed,
695
+ piProjection,
696
+ turnStartMessageIndex,
697
+ !followsReplacement && (parsed.historyVersion === void 0 || parsed.historyVersion === currentHistoryVersion)
698
+ );
699
+ }
700
+ async function getAgentTurnSessionRecord(conversationId, sessionId) {
701
+ return await materializeStoredAgentTurnSessionRecord(
702
+ conversationId,
703
+ sessionId,
704
+ false
705
+ );
706
+ }
707
+ async function getAgentTurnSessionRecordForResume(conversationId, sessionId) {
708
+ return await materializeStoredAgentTurnSessionRecord(
709
+ conversationId,
710
+ sessionId,
711
+ true
712
+ );
713
+ }
714
+ function buildStoredRecord(args) {
715
+ const nowMs = Date.now();
716
+ return {
717
+ version: (args.previousVersion ?? 0) + 1,
718
+ ...args.channelName ? { channelName: args.channelName } : {},
719
+ conversationId: args.conversationId,
720
+ sessionId: args.sessionId,
721
+ sliceId: args.sliceId,
722
+ state: args.state,
723
+ startedAtMs: args.startedAtMs ?? nowMs,
724
+ lastProgressAtMs: args.lastProgressAtMs ?? nowMs,
725
+ updatedAtMs: nowMs,
726
+ committedSeq: args.committedSeq,
727
+ ...args.historyVersion !== void 0 ? { historyVersion: args.historyVersion } : {},
728
+ ...args.turnStartSeq !== void 0 ? { turnStartSeq: args.turnStartSeq } : {},
729
+ ...args.runtimeContext && args.runtimeContext.length > 0 ? { runtimeContext: args.runtimeContext } : {},
730
+ cumulativeDurationMs: args.cumulativeDurationMs,
731
+ ...args.cumulativeUsage ? { cumulativeUsage: args.cumulativeUsage } : {},
732
+ ...args.destination ? { destination: args.destination } : {},
733
+ ...args.source ? { source: args.source } : {},
734
+ ...args.actor ? { actor: args.actor } : {},
735
+ ...args.actors ? { actors: args.actors } : {},
736
+ ...args.loadedSkillNames ? { loadedSkillNames: args.loadedSkillNames } : {},
737
+ ...args.modelId ? { modelId: args.modelId } : {},
738
+ ...args.reasoningLevel ? { reasoningLevel: args.reasoningLevel } : {},
739
+ ...args.resumeReason ? { resumeReason: args.resumeReason } : {},
740
+ ...args.errorMessage ? { errorMessage: args.errorMessage } : {},
741
+ ...args.resumedFromSliceId !== void 0 ? { resumedFromSliceId: args.resumedFromSliceId } : {},
742
+ ...args.surface ? { surface: args.surface } : {},
743
+ ...args.traceId ? { traceId: args.traceId } : {}
744
+ };
745
+ }
746
+ async function setStoredRecord(args) {
747
+ const stateAdapter = getStateAdapter();
748
+ await stateAdapter.connect();
749
+ await recordConversationActivityMetadata({
750
+ conversationStore: args.conversationStore,
751
+ destinationVisibility: args.destinationVisibility,
752
+ nowMs: Date.now(),
753
+ summary: args.record
754
+ });
755
+ await stateAdapter.set(
756
+ agentTurnSessionKey(args.record.conversationId, args.record.sessionId),
757
+ args.record,
758
+ args.ttlMs
759
+ );
760
+ const {
761
+ actors: _actors,
762
+ committedSeq: _committedSeq,
763
+ historyVersion: _historyVersion,
764
+ errorMessage: _errorMessage,
765
+ turnStartSeq: _turnStartSeq,
766
+ runtimeContext: _runtimeContext,
767
+ ...summary
768
+ } = args.record;
769
+ await appendAgentTurnSessionSummary(summary, args.ttlMs);
770
+ return materializeAgentTurnSessionRecord(
771
+ args.record,
772
+ {
773
+ messages: [...args.piMessages],
774
+ provenance: [...args.piMessageProvenance]
775
+ },
776
+ args.turnStartMessageIndex
777
+ );
778
+ }
779
+ async function updateAgentTurnSessionState(args) {
780
+ const parsed = await getStoredAgentTurnSessionRecord(
781
+ args.existing.conversationId,
782
+ args.existing.sessionId
783
+ );
784
+ if (!parsed || parsed.version !== args.existing.version) {
785
+ return void 0;
786
+ }
787
+ return await setStoredRecord({
788
+ piMessages: args.existing.piMessages,
789
+ piMessageProvenance: args.existing.piMessageProvenance,
790
+ ttlMs: AGENT_TURN_SESSION_TTL_MS,
791
+ ...args.existing.turnStartMessageIndex !== void 0 ? { turnStartMessageIndex: args.existing.turnStartMessageIndex } : {},
792
+ record: buildStoredRecord({
793
+ conversationId: args.existing.conversationId,
794
+ sessionId: args.existing.sessionId,
795
+ sliceId: args.existing.sliceId,
796
+ state: args.state,
797
+ committedSeq: parsed.committedSeq,
798
+ ...parsed.historyVersion !== void 0 ? { historyVersion: parsed.historyVersion } : {},
799
+ ...parsed.turnStartSeq !== void 0 ? { turnStartSeq: parsed.turnStartSeq } : {},
800
+ ...parsed.runtimeContext ? { runtimeContext: parsed.runtimeContext } : {},
801
+ ...parsed.channelName ? { channelName: parsed.channelName } : {},
802
+ startedAtMs: parsed.startedAtMs,
803
+ lastProgressAtMs: parsed.lastProgressAtMs,
804
+ previousVersion: parsed.version,
805
+ cumulativeDurationMs: args.existing.cumulativeDurationMs,
806
+ ...args.existing.cumulativeUsage ? { cumulativeUsage: args.existing.cumulativeUsage } : {},
807
+ ...args.existing.destination ? { destination: args.existing.destination } : {},
808
+ ...args.existing.source ? { source: args.existing.source } : {},
809
+ ...args.existing.loadedSkillNames ? { loadedSkillNames: args.existing.loadedSkillNames } : {},
810
+ ...args.existing.modelId ? { modelId: args.existing.modelId } : {},
811
+ ...args.existing.reasoningLevel ? { reasoningLevel: args.existing.reasoningLevel } : {},
812
+ ...args.existing.actor ? { actor: args.existing.actor } : {},
813
+ actors: args.existing.actors,
814
+ ...args.existing.resumeReason ? { resumeReason: args.existing.resumeReason } : {},
815
+ ...args.existing.resumedFromSliceId !== void 0 ? { resumedFromSliceId: args.existing.resumedFromSliceId } : {},
816
+ ...args.existing.surface ? { surface: args.existing.surface } : {},
817
+ ...args.existing.traceId ? { traceId: args.existing.traceId } : {},
818
+ ...args.errorMessage ?? args.existing.errorMessage ? { errorMessage: args.errorMessage ?? args.existing.errorMessage } : {}
819
+ })
820
+ });
821
+ }
822
+ async function upsertAgentTurnSessionRecord(args) {
823
+ const existingRecord = await getStoredAgentTurnSessionRecord(
824
+ args.conversationId,
825
+ args.sessionId
826
+ );
827
+ const ttlMs = Math.max(1, args.ttlMs ?? AGENT_TURN_SESSION_TTL_MS);
828
+ const instructionActor = args.actor ?? existingRecord?.actor;
829
+ const commit = await commitMessages({
830
+ conversationId: args.conversationId,
831
+ messages: args.piMessages,
832
+ ...instructionActor ? { newMessageProvenance: instructionProvenanceFor(instructionActor) } : {},
833
+ ...args.trailingMessageProvenance ? { trailingMessageProvenance: args.trailingMessageProvenance } : {}
834
+ });
835
+ const durableTurnStartMessageIndex = args.turnStartMessageIndex === void 0 ? void 0 : stripRuntimeTurnContext(
836
+ args.piMessages.slice(0, args.turnStartMessageIndex)
837
+ ).length;
838
+ const runtimeContext = retainRuntimeTurnContext(args.piMessages);
839
+ const retainedRuntimeContext = runtimeContext.length > 0 ? runtimeContext : existingRecord?.historyVersion === commit.historyVersion ? existingRecord.runtimeContext : void 0;
840
+ const turnStartSeq = durableTurnStartMessageIndex === void 0 ? existingRecord?.turnStartSeq : durableTurnStartMessageIndex <= 0 ? -1 : commit.messageSeqs[durableTurnStartMessageIndex - 1] ?? commit.committedSeq;
841
+ const turnStartMessageIndex = durableTurnStartMessageIndex ?? (turnStartSeq === void 0 ? void 0 : commit.messageSeqs.filter((seq) => seq <= turnStartSeq).length);
842
+ return await setStoredRecord({
843
+ conversationStore: args.conversationStore,
844
+ destinationVisibility: args.destinationVisibility,
845
+ piMessages: commit.messages,
846
+ piMessageProvenance: commit.provenance,
847
+ ttlMs,
848
+ ...turnStartMessageIndex !== void 0 ? { turnStartMessageIndex } : {},
849
+ record: buildStoredRecord({
850
+ ...args.channelName ?? existingRecord?.channelName ? { channelName: args.channelName ?? existingRecord?.channelName } : {},
851
+ conversationId: args.conversationId,
852
+ sessionId: args.sessionId,
853
+ sliceId: args.sliceId,
854
+ state: args.state,
855
+ ...existingRecord?.startedAtMs !== void 0 ? { startedAtMs: existingRecord.startedAtMs } : {},
856
+ ...args.lastProgressAtMs !== void 0 ? { lastProgressAtMs: args.lastProgressAtMs } : {},
857
+ committedSeq: commit.committedSeq,
858
+ historyVersion: commit.historyVersion,
859
+ ...turnStartSeq !== void 0 ? { turnStartSeq } : {},
860
+ ...retainedRuntimeContext ? { runtimeContext: retainedRuntimeContext } : {},
861
+ previousVersion: existingRecord?.version,
862
+ cumulativeDurationMs: args.cumulativeDurationMs ?? existingRecord?.cumulativeDurationMs ?? 0,
863
+ ...args.cumulativeUsage ? { cumulativeUsage: args.cumulativeUsage } : {},
864
+ ...args.destination ?? existingRecord?.destination ? { destination: args.destination ?? existingRecord?.destination } : {},
865
+ ...args.source ?? existingRecord?.source ? { source: args.source ?? existingRecord?.source } : {},
866
+ ...args.loadedSkillNames ? { loadedSkillNames: args.loadedSkillNames } : {},
867
+ modelId: args.modelId,
868
+ ...args.reasoningLevel ?? existingRecord?.reasoningLevel ? {
869
+ reasoningLevel: args.reasoningLevel ?? existingRecord?.reasoningLevel
870
+ } : {},
871
+ ...args.actor ?? existingRecord?.actor ? { actor: args.actor ?? existingRecord?.actor } : {},
872
+ actors: instructionActors([
873
+ ...(existingRecord?.actors ?? []).map(instructionProvenanceFor),
874
+ ...(args.actors ?? []).map(instructionProvenanceFor),
875
+ ...commit.provenance
876
+ ]),
877
+ ...args.resumeReason ? { resumeReason: args.resumeReason } : {},
878
+ ...args.errorMessage ? { errorMessage: args.errorMessage } : {},
879
+ ...args.resumedFromSliceId !== void 0 ? { resumedFromSliceId: args.resumedFromSliceId } : {},
880
+ ...args.surface ?? existingRecord?.surface ? { surface: args.surface ?? existingRecord?.surface } : {},
881
+ ...args.traceId ?? existingRecord?.traceId ? { traceId: args.traceId ?? existingRecord?.traceId } : {}
882
+ })
883
+ });
884
+ }
885
+ async function recordAgentTurnSessionSummary(args) {
886
+ const existing = await getStoredAgentTurnSessionRecord(
887
+ args.conversationId,
888
+ args.sessionId
889
+ );
890
+ const nowMs = Date.now();
891
+ const ttlMs = Math.max(1, args.ttlMs ?? AGENT_TURN_SESSION_TTL_MS);
892
+ const summary = {
893
+ version: existing?.version ?? 0,
894
+ ...args.channelName ?? existing?.channelName ? { channelName: args.channelName ?? existing?.channelName } : {},
895
+ conversationId: args.conversationId,
896
+ sessionId: args.sessionId,
897
+ sliceId: args.sliceId,
898
+ startedAtMs: existing?.startedAtMs ?? args.startedAtMs ?? nowMs,
899
+ lastProgressAtMs: args.lastProgressAtMs ?? nowMs,
900
+ state: args.state,
901
+ updatedAtMs: nowMs,
902
+ cumulativeDurationMs: args.cumulativeDurationMs ?? existing?.cumulativeDurationMs ?? 0,
903
+ ...args.cumulativeUsage ?? existing?.cumulativeUsage ? { cumulativeUsage: args.cumulativeUsage ?? existing?.cumulativeUsage } : {},
904
+ ...args.destination ?? existing?.destination ? { destination: args.destination ?? existing?.destination } : {},
905
+ ...args.source ?? existing?.source ? { source: args.source ?? existing?.source } : {},
906
+ ...args.actor ?? existing?.actor ? { actor: args.actor ?? existing?.actor } : {},
907
+ ...args.loadedSkillNames ? { loadedSkillNames: args.loadedSkillNames } : existing?.loadedSkillNames ? { loadedSkillNames: existing.loadedSkillNames } : {},
908
+ ...args.modelId ?? existing?.modelId ? { modelId: args.modelId ?? existing?.modelId } : {},
909
+ ...args.reasoningLevel ?? existing?.reasoningLevel ? { reasoningLevel: args.reasoningLevel ?? existing?.reasoningLevel } : {},
910
+ ...args.resumeReason ? { resumeReason: args.resumeReason } : {},
911
+ ...args.surface ?? existing?.surface ? { surface: args.surface ?? existing?.surface } : {},
912
+ ...args.traceId ?? existing?.traceId ? { traceId: args.traceId ?? existing?.traceId } : {}
913
+ };
914
+ await recordConversationActivityMetadata({
915
+ conversationStore: args.conversationStore,
916
+ destinationVisibility: args.destinationVisibility,
917
+ nowMs,
918
+ summary
919
+ });
920
+ await appendAgentTurnSessionSummary(summary, ttlMs);
921
+ }
922
+ async function readAgentTurnSessionSummariesFromIndex(key, stateAdapter) {
923
+ await stateAdapter.connect();
924
+ const values = await stateAdapter.getList(key);
925
+ const summaries = /* @__PURE__ */ new Map();
926
+ for (const value of [...values].reverse()) {
927
+ let summary;
928
+ try {
929
+ summary = parseAgentTurnSessionSummary(value);
930
+ } catch (error) {
931
+ logWarn(
932
+ "agent_turn_session_summary_parse_failed",
933
+ {},
934
+ {
935
+ "app.state.key": key,
936
+ "exception.message": error instanceof Error ? error.message : String(error)
937
+ },
938
+ "Skipping an invalid turn-session summary index entry"
939
+ );
940
+ continue;
941
+ }
942
+ const summaryKey = `${summary.conversationId}:${summary.sessionId}`;
943
+ if (!summaries.has(summaryKey)) {
944
+ summaries.set(summaryKey, summary);
945
+ }
946
+ }
947
+ return [...summaries.values()].sort(
948
+ (left, right) => right.updatedAtMs - left.updatedAtMs
949
+ );
950
+ }
951
+ async function listAgentTurnSessionSummariesForConversation(conversationId) {
952
+ const stateAdapter = getStateAdapter();
953
+ const summaries = await listBoundedAgentTurnSessionSummariesForConversation(conversationId);
954
+ if (summaries.length > 0) {
955
+ return summaries;
956
+ }
957
+ return (await readAgentTurnSessionSummariesFromIndex(
958
+ AGENT_TURN_SESSION_INDEX_KEY,
959
+ stateAdapter
960
+ )).filter((summary) => summary.conversationId === conversationId);
961
+ }
962
+ async function listBoundedAgentTurnSessionSummariesForConversation(conversationId) {
963
+ return readAgentTurnSessionSummariesFromIndex(
964
+ agentTurnSessionConversationIndexKey(conversationId),
965
+ getStateAdapter()
966
+ );
967
+ }
968
+ async function abandonAgentTurnSessionRecord(args) {
969
+ const existing = await getAgentTurnSessionRecord(
970
+ args.conversationId,
971
+ args.sessionId
972
+ );
973
+ if (!existing || existing.state === "completed" || existing.state === "failed" || existing.state === "abandoned") {
974
+ return void 0;
975
+ }
976
+ return await updateAgentTurnSessionState({
977
+ existing,
978
+ state: "abandoned",
979
+ errorMessage: args.errorMessage ?? existing.errorMessage
980
+ });
981
+ }
982
+ async function failAgentTurnSessionRecord(args) {
983
+ const existing = await getAgentTurnSessionRecord(
984
+ args.conversationId,
985
+ args.sessionId
986
+ );
987
+ if (!existing || existing.state === "completed" || existing.state === "failed" || existing.state === "abandoned" || existing.version !== args.expectedVersion) {
988
+ return void 0;
989
+ }
990
+ return await updateAgentTurnSessionState({
991
+ existing,
992
+ state: "failed",
993
+ errorMessage: args.errorMessage ?? existing.errorMessage
994
+ });
995
+ }
996
+
148
997
  // src/chat/services/turn-limit.ts
149
998
  var TurnSliceLimitExceededError = class extends Error {
150
999
  constructor(maxSlices) {
@@ -162,9 +1011,9 @@ function logSessionRecordError(error, eventName, args, attributes, message) {
162
1011
  error,
163
1012
  eventName,
164
1013
  {
165
- slackThreadId: args.logContext.threadId,
166
- slackUserId: args.logContext.actorId,
167
- slackChannelId: args.logContext.channelId,
1014
+ messageConversationId: args.logContext.threadId,
1015
+ userId: args.logContext.actorId,
1016
+ destinationName: args.logContext.channelId,
168
1017
  runId: args.logContext.runId,
169
1018
  assistantUserName: args.logContext.assistantUserName,
170
1019
  modelId: args.modelId
@@ -319,7 +1168,6 @@ async function completeDeliveredTurn(args) {
319
1168
  sliceId: args.sliceId,
320
1169
  allMessages: args.messages,
321
1170
  loadedSkillNames: args.loadedSkillNames,
322
- logContext: args.logContext,
323
1171
  modelId: args.modelId,
324
1172
  actor: args.actor,
325
1173
  reasoningLevel: args.reasoningLevel,
@@ -530,6 +1378,122 @@ async function persistYieldSessionRecord(args) {
530
1378
  }
531
1379
  }
532
1380
 
1381
+ // src/chat/state/conversation.ts
1382
+ function defaultConversationState() {
1383
+ const nowMs = Date.now();
1384
+ return {
1385
+ schemaVersion: 1,
1386
+ messages: [],
1387
+ compactions: [],
1388
+ backfill: {},
1389
+ processing: {},
1390
+ stats: {
1391
+ estimatedContextTokens: 0,
1392
+ totalMessageCount: 0,
1393
+ compactedMessageCount: 0,
1394
+ updatedAtMs: nowMs
1395
+ },
1396
+ vision: {
1397
+ byFileId: {}
1398
+ }
1399
+ };
1400
+ }
1401
+ function coercePendingAuthState(value) {
1402
+ if (!isRecord(value)) {
1403
+ return void 0;
1404
+ }
1405
+ const kind = value.kind;
1406
+ const provider = toOptionalString(value.provider);
1407
+ const actorId = toOptionalString(value.actorId);
1408
+ const authSessionId = toOptionalString(value.authSessionId);
1409
+ const scope = toOptionalString(value.scope);
1410
+ const sessionId = toOptionalString(value.sessionId);
1411
+ const linkSentAtMs = toOptionalNumber(value.linkSentAtMs);
1412
+ if (kind !== "mcp" && kind !== "plugin" || !provider || !actorId || kind === "mcp" && !authSessionId || !sessionId || typeof linkSentAtMs !== "number") {
1413
+ return void 0;
1414
+ }
1415
+ const base = {
1416
+ provider,
1417
+ actorId,
1418
+ ...scope ? { scope } : {},
1419
+ sessionId,
1420
+ linkSentAtMs
1421
+ };
1422
+ return kind === "mcp" ? { ...base, authSessionId, kind } : { ...base, kind };
1423
+ }
1424
+ function coerceThreadConversationState(value) {
1425
+ if (!isRecord(value)) {
1426
+ return defaultConversationState();
1427
+ }
1428
+ const root = value;
1429
+ const rawConversation = isRecord(root.conversation) ? root.conversation : {};
1430
+ const base = defaultConversationState();
1431
+ const messages = [];
1432
+ const compactions = [];
1433
+ const rawBackfill = isRecord(rawConversation.backfill) ? rawConversation.backfill : {};
1434
+ const backfill = {
1435
+ completedAtMs: toOptionalNumber(rawBackfill.completedAtMs),
1436
+ source: rawBackfill.source === "recent_messages" || rawBackfill.source === "thread_fetch" ? rawBackfill.source : void 0
1437
+ };
1438
+ const rawProcessing = isRecord(rawConversation.processing) ? rawConversation.processing : {};
1439
+ const processing = {
1440
+ activeTurnId: toOptionalString(rawProcessing.activeTurnId),
1441
+ lastCompletedAtMs: toOptionalNumber(rawProcessing.lastCompletedAtMs),
1442
+ pendingAuth: coercePendingAuthState(rawProcessing.pendingAuth)
1443
+ };
1444
+ const rawStats = isRecord(rawConversation.stats) ? rawConversation.stats : {};
1445
+ const stats = {
1446
+ estimatedContextTokens: toOptionalNumber(rawStats.estimatedContextTokens) ?? base.stats.estimatedContextTokens,
1447
+ totalMessageCount: toOptionalNumber(rawStats.totalMessageCount) ?? messages.length,
1448
+ compactedMessageCount: toOptionalNumber(rawStats.compactedMessageCount) ?? 0,
1449
+ updatedAtMs: toOptionalNumber(rawStats.updatedAtMs) ?? base.stats.updatedAtMs
1450
+ };
1451
+ const rawVision = isRecord(rawConversation.vision) ? rawConversation.vision : {};
1452
+ const rawVisionByFileId = isRecord(rawVision.byFileId) ? rawVision.byFileId : {};
1453
+ const byFileId = {};
1454
+ for (const [fileId, value2] of Object.entries(rawVisionByFileId)) {
1455
+ if (typeof fileId !== "string" || fileId.trim().length === 0) continue;
1456
+ if (!isRecord(value2)) continue;
1457
+ const summary = toOptionalString(value2.summary);
1458
+ const analyzedAtMs = toOptionalNumber(value2.analyzedAtMs);
1459
+ if (!summary || !analyzedAtMs) continue;
1460
+ byFileId[fileId] = {
1461
+ summary,
1462
+ analyzedAtMs
1463
+ };
1464
+ }
1465
+ return {
1466
+ schemaVersion: 1,
1467
+ messages,
1468
+ compactions,
1469
+ backfill,
1470
+ processing,
1471
+ stats,
1472
+ vision: {
1473
+ backfillCompletedAtMs: toOptionalNumber(rawVision.backfillCompletedAtMs),
1474
+ byFileId
1475
+ }
1476
+ };
1477
+ }
1478
+ function buildConversationStatePatch(conversation) {
1479
+ const {
1480
+ compactions: _compactions,
1481
+ messages: _messages,
1482
+ ...scratch
1483
+ } = conversation;
1484
+ return {
1485
+ conversation: {
1486
+ ...scratch,
1487
+ schemaVersion: 1,
1488
+ stats: {
1489
+ ...conversation.stats,
1490
+ totalMessageCount: conversation.messages.length,
1491
+ updatedAtMs: Date.now()
1492
+ }
1493
+ }
1494
+ };
1495
+ }
1496
+
533
1497
  // src/chat/services/context-budget.ts
534
1498
  var COMPACTION_TRIGGER_INPUT_RATIO = 0.75;
535
1499
  var COMPACTION_OUTPUT_RESERVE_RATIO = 0.25;
@@ -804,9 +1768,9 @@ async function summarizeConversationChunk(messages, conversation, context, deps)
804
1768
  logWarn(
805
1769
  "conversation_compaction_summary_failed",
806
1770
  {
807
- slackThreadId: context.threadId,
808
- slackUserId: context.actorId,
809
- slackChannelId: context.channelId,
1771
+ messageConversationId: context.threadId,
1772
+ userId: context.actorId,
1773
+ destinationName: context.channelId,
810
1774
  runId: context.runId,
811
1775
  assistantUserName: botConfig.userName,
812
1776
  modelId: botConfig.fastModelId
@@ -923,10 +1887,10 @@ function getConversationMessageSlackTs(message) {
923
1887
 
924
1888
  // src/chat/conversations/messages.ts
925
1889
  import { createHash } from "crypto";
926
- import { isDeepStrictEqual as isDeepStrictEqual2 } from "util";
1890
+ import { isDeepStrictEqual as isDeepStrictEqual3 } from "util";
927
1891
 
928
1892
  // src/chat/conversations/message-summaries.ts
929
- import { isDeepStrictEqual } from "util";
1893
+ import { isDeepStrictEqual as isDeepStrictEqual2 } from "util";
930
1894
  function projectConversationMessageSummaries(events) {
931
1895
  for (let index = events.length - 1; index >= 0; index -= 1) {
932
1896
  const data = events[index]?.data;
@@ -940,7 +1904,7 @@ async function persistConversationMessageSummaries(args) {
940
1904
  const eventStore = getConversationEventStore();
941
1905
  const history = await eventStore.loadMessageHistory(args.conversationId);
942
1906
  const existing = history.compaction ? projectConversationMessageSummaries([history.compaction]) : [];
943
- if (isDeepStrictEqual(existing, args.conversation.compactions)) {
1907
+ if (isDeepStrictEqual2(existing, args.conversation.compactions)) {
944
1908
  return;
945
1909
  }
946
1910
  const liveMessageIds = new Set(
@@ -949,10 +1913,7 @@ async function persistConversationMessageSummaries(args) {
949
1913
  const historyFromSeq = history.events.find(
950
1914
  (event) => event.data.type === "message" && liveMessageIds.has(event.data.messageId)
951
1915
  )?.seq;
952
- const nextHistoryFromSeq = historyFromSeq ?? Math.max(
953
- history.compaction?.data.type === "messages_summarized" ? history.compaction.data.historyFromSeq : 0,
954
- (history.events.at(-1)?.seq ?? -1) + 1
955
- );
1916
+ const nextHistoryFromSeq = historyFromSeq ?? Math.max(history.historyFromSeq, (history.events.at(-1)?.seq ?? -1) + 1);
956
1917
  await eventStore.append(args.conversationId, [
957
1918
  {
958
1919
  data: {
@@ -983,9 +1944,9 @@ function missingBaselineError(event) {
983
1944
  `Message event ${event.data.type} at seq ${event.seq} references ${event.data.messageId} before message`
984
1945
  );
985
1946
  }
986
- function projectConversationMessages(events, options = {}) {
1947
+ function projectConversationMessages(history) {
987
1948
  const byId = /* @__PURE__ */ new Map();
988
- for (const event of events) {
1949
+ for (const event of history.events) {
989
1950
  if (!isMessageEvent(event)) continue;
990
1951
  const data = event.data;
991
1952
  const current = byId.get(data.messageId);
@@ -1002,7 +1963,7 @@ function projectConversationMessages(events, options = {}) {
1002
1963
  `Duplicate message event for ${data.messageId} at seq ${event.seq}`
1003
1964
  );
1004
1965
  if (data.type === "message_updated" && !current) {
1005
- if ((options.historyFromSeq ?? 0) > 0) continue;
1966
+ if (history.historyFromSeq > 0) continue;
1006
1967
  throw missingBaselineError(event);
1007
1968
  }
1008
1969
  byId.set(data.messageId, {
@@ -1015,7 +1976,7 @@ function projectConversationMessages(events, options = {}) {
1015
1976
  continue;
1016
1977
  }
1017
1978
  if (!current) {
1018
- if ((options.historyFromSeq ?? 0) > 0) continue;
1979
+ if (history.historyFromSeq > 0) continue;
1019
1980
  throw missingBaselineError(event);
1020
1981
  }
1021
1982
  current.repliedAtMs ??= event.createdAtMs;
@@ -1063,10 +2024,7 @@ async function hydrateConversationMessages(args) {
1063
2024
  args.conversationId
1064
2025
  );
1065
2026
  args.conversation.compactions = history.compaction ? projectConversationMessageSummaries([history.compaction]) : [];
1066
- const historyFromSeq = history.compaction?.data.type === "messages_summarized" ? history.compaction.data.historyFromSeq : 0;
1067
- args.conversation.messages = projectConversationMessages(history.events, {
1068
- historyFromSeq
1069
- });
2027
+ args.conversation.messages = projectConversationMessages(history);
1070
2028
  updateConversationStats(args.conversation);
1071
2029
  }
1072
2030
  async function persistConversationMessages(args) {
@@ -1075,7 +2033,7 @@ async function persistConversationMessages(args) {
1075
2033
  args.conversationId
1076
2034
  );
1077
2035
  const existingMessages = new Map(
1078
- projectConversationMessages(history.events).map((message) => [
2036
+ projectConversationMessages(history).map((message) => [
1079
2037
  message.id,
1080
2038
  message
1081
2039
  ])
@@ -1084,7 +2042,7 @@ async function persistConversationMessages(args) {
1084
2042
  const events = args.conversation.messages.flatMap((message) => {
1085
2043
  const stored = toStoredConversationMessage(message);
1086
2044
  const existing = existingMessages.get(stored.messageId);
1087
- const changed = existing !== void 0 && !isDeepStrictEqual2(toStoredConversationMessage(existing), stored);
2045
+ const changed = existing !== void 0 && !isDeepStrictEqual3(toStoredConversationMessage(existing), stored);
1088
2046
  const messageEvent = existing ? changed ? [
1089
2047
  {
1090
2048
  idempotencyKey: `message:${stored.messageId}:update:${messageStateKey(stored)}`,
@@ -1378,6 +2336,9 @@ function createChannelConfigurationService(storage) {
1378
2336
  };
1379
2337
  }
1380
2338
 
2339
+ // src/chat/state/ttl.ts
2340
+ var JUNIOR_THREAD_STATE_TTL_MS = 7 * 24 * 60 * 60 * 1e3;
2341
+
1381
2342
  // src/chat/runtime/thread-state.ts
1382
2343
  function threadStateKey(threadId) {
1383
2344
  return `thread-state:${threadId}`;
@@ -1571,6 +2532,15 @@ function isPendingAuthLatestRequest(conversation, pendingAuth) {
1571
2532
  }
1572
2533
 
1573
2534
  export {
2535
+ loadProjection,
2536
+ loadConversationProjection,
2537
+ openConversationProjection,
2538
+ loadConnectedMcpProviders,
2539
+ commitMessages,
2540
+ recordMcpProviderConnected,
2541
+ recordAuthorizationRequested,
2542
+ recordAuthorizationCompleted,
2543
+ recordToolExecutionStarted,
1574
2544
  buildDeterministicTurnId,
1575
2545
  buildDeterministicAssistantMessageId,
1576
2546
  ConversationTurnBoundaryError,
@@ -1585,6 +2555,12 @@ export {
1585
2555
  markTurnClosed,
1586
2556
  markTurnCompleted,
1587
2557
  markTurnFailed,
2558
+ getAgentTurnSessionRecord,
2559
+ getAgentTurnSessionRecordForResume,
2560
+ recordAgentTurnSessionSummary,
2561
+ listAgentTurnSessionSummariesForConversation,
2562
+ abandonAgentTurnSessionRecord,
2563
+ failAgentTurnSessionRecord,
1588
2564
  persistWithRetry,
1589
2565
  TurnSliceLimitExceededError,
1590
2566
  buildTurnLimitResponse,
@@ -1596,11 +2572,13 @@ export {
1596
2572
  persistContinuationSessionRecord,
1597
2573
  persistYieldSessionRecord,
1598
2574
  getInterruptionMarker,
2575
+ JUNIOR_THREAD_STATE_TTL_MS,
1599
2576
  canReusePendingAuthLink,
1600
2577
  getConversationPendingAuth,
1601
2578
  clearPendingAuth,
1602
2579
  abandonReplacedPendingAuth,
1603
2580
  isPendingAuthLatestRequest,
2581
+ coerceThreadConversationState,
1604
2582
  persistConversationMessageSummaries,
1605
2583
  estimateTextTokens,
1606
2584
  getAgentContextCompactionTriggerTokens,