@sentry/junior 0.140.0 → 0.142.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 (83) hide show
  1. package/dist/{agent-hooks-BFTDRFUO.js → agent-hooks-WDJQQAPH.js} +10 -10
  2. package/dist/api/conversations/detail.d.ts +1 -0
  3. package/dist/api/conversations/event-list.d.ts +1 -0
  4. package/dist/api/schema/conversation.d.ts +4 -0
  5. package/dist/api/schema/task.d.ts +8 -0
  6. package/dist/api/schema.js +1 -1
  7. package/dist/api.js +35 -23
  8. package/dist/app.js +63 -92
  9. package/dist/{catalog-runtime-RK664S5T.js → catalog-runtime-CLHTYBHB.js} +5 -5
  10. package/dist/chat/agent/repository-context.d.ts +1 -0
  11. package/dist/chat/conversations/history.d.ts +2 -0
  12. package/dist/chat/conversations/projection.d.ts +8 -0
  13. package/dist/chat/conversations/store.d.ts +5 -0
  14. package/dist/chat/conversations/structured-events.d.ts +11 -0
  15. package/dist/chat/event-tasks/types.d.ts +7 -1
  16. package/dist/chat/runtime/thread-state.d.ts +3 -1
  17. package/dist/chat/runtime/turn.d.ts +0 -4
  18. package/dist/chat/scheduled-tasks/tools/create.d.ts +1 -0
  19. package/dist/chat/scheduled-tasks/types.d.ts +5 -0
  20. package/dist/chat/services/conversation-memory.d.ts +2 -1
  21. package/dist/chat/services/guardian-action-policy.d.ts +1 -1
  22. package/dist/chat/services/short-title.d.ts +25 -0
  23. package/dist/chat/state/conversation.d.ts +6 -17
  24. package/dist/chat/state/turn-session.d.ts +3 -4
  25. package/dist/chat/tools/create-event-task.d.ts +1 -0
  26. package/dist/{chunk-MYVCHWDG.js → chunk-4EZV6URQ.js} +107 -37
  27. package/dist/{chunk-557HLSNH.js → chunk-4L7PKEHM.js} +5 -1
  28. package/dist/{chunk-WTGLUX4K.js → chunk-5ITSZIAP.js} +1 -1
  29. package/dist/{chunk-3CUBAGA3.js → chunk-5QVVW3FY.js} +7 -7
  30. package/dist/{chunk-XX2QNQN4.js → chunk-6IMKBHCG.js} +81 -47
  31. package/dist/{chunk-3B7O3GNU.js → chunk-7R3CGEMF.js} +3 -3
  32. package/dist/{chunk-S3FAOKQU.js → chunk-A646AKNB.js} +29 -29
  33. package/dist/{chunk-BMFRFSTM.js → chunk-B2H7NI5C.js} +165 -535
  34. package/dist/{chunk-AA2EPMUF.js → chunk-CA4WLRA3.js} +4 -4
  35. package/dist/{chunk-D3X2PI3D.js → chunk-CBMIVCPM.js} +2 -2
  36. package/dist/chunk-DRTWANQF.js +67 -0
  37. package/dist/{chunk-CDQ4YIZM.js → chunk-HLXXQI5Q.js} +55 -1
  38. package/dist/{chunk-TKTZ4L4Z.js → chunk-HVTWCMDS.js} +1 -1
  39. package/dist/{chunk-CPPTBHQN.js → chunk-JIWZ7T4C.js} +1 -1
  40. package/dist/{chunk-UXMRN2ZX.js → chunk-LGY7YX5O.js} +1 -1
  41. package/dist/{chunk-YB363QGA.js → chunk-LL3BGP7J.js} +1 -1
  42. package/dist/{chunk-TGIKZJ3Y.js → chunk-LXLBYA6N.js} +2 -2
  43. package/dist/{chunk-XROWZSOY.js → chunk-N3H7G5K6.js} +1 -1
  44. package/dist/{chunk-J5H65GY2.js → chunk-NWO5IB7V.js} +1 -1
  45. package/dist/{chunk-YWM6MJT4.js → chunk-OGQGJAD7.js} +2 -0
  46. package/dist/{chunk-VRDHAITB.js → chunk-OQAPYAXH.js} +3 -3
  47. package/dist/{chunk-4PIT6O7A.js → chunk-PWDCFIQG.js} +6 -6
  48. package/dist/{chunk-OGNL2QY7.js → chunk-QDRMQI5W.js} +4 -4
  49. package/dist/{chunk-5KAZMR5U.js → chunk-R7E476CH.js} +6 -6
  50. package/dist/{chunk-HEFBGCCU.js → chunk-RYYBUI34.js} +2 -2
  51. package/dist/{chunk-WSWM6A3R.js → chunk-SCHH6264.js} +19 -3
  52. package/dist/{chunk-I3OZR3HG.js → chunk-SF6YKONJ.js} +1 -1
  53. package/dist/{chunk-DMDOKRPC.js → chunk-T4NFQ5SG.js} +5 -5
  54. package/dist/{chunk-5YAWB2AX.js → chunk-VJMBLWYR.js} +15 -17
  55. package/dist/{chunk-A6BI25J3.js → chunk-WJEW7UNC.js} +341 -16
  56. package/dist/{chunk-N2I7W2VS.js → chunk-WYLSMTWU.js} +1 -1
  57. package/dist/{chunk-NPL66ZWL.js → chunk-XAW6IN6M.js} +1 -1
  58. package/dist/chunk-XFHSZNOX.js +75 -0
  59. package/dist/{chunk-Z2A7BDHT.js → chunk-ZPNUI4L3.js} +23 -26
  60. package/dist/cli/chat.js +33 -31
  61. package/dist/cli/check.js +6 -6
  62. package/dist/cli/plugins.js +13 -13
  63. package/dist/cli/snapshot-create.js +9 -9
  64. package/dist/cli/upgrade.js +7 -7
  65. package/dist/db/schema/event-tasks.d.ts +20 -52
  66. package/dist/db/schema/scheduled-tasks.d.ts +17 -0
  67. package/dist/db/schema.d.ts +36 -52
  68. package/dist/{db-QOGYDDQM.js → db-6N4CD5AI.js} +7 -7
  69. package/dist/instrumentation.js +4 -4
  70. package/dist/nitro.js +2 -2
  71. package/dist/{oauth-callback-server-CT4RMGCP.js → oauth-callback-server-G4K2X56R.js} +24 -22
  72. package/dist/{runner-PHMLHNLX.js → runner-DEIHVRUE.js} +25 -26
  73. package/dist/{skills-AYRM4QGF.js → skills-FZVJ6ZFW.js} +6 -6
  74. package/dist/spawn-WD4F67SZ.js +38 -0
  75. package/dist/{validation-M5JNGRGL.js → validation-EOLSG7B6.js} +6 -6
  76. package/dist/version.js +1 -1
  77. package/dist/{work-LZWE6HK4.js → work-AS3IBMLA.js} +17 -17
  78. package/dist/{worker-KOGPXFYK.js → worker-VGGNGOUU.js} +10 -10
  79. package/migrations/0023_task_title_columns.sql +2 -0
  80. package/migrations/meta/0023_snapshot.json +2198 -0
  81. package/migrations/meta/_journal.json +7 -0
  82. package/package.json +4 -4
  83. package/dist/spawn-OGRQFUNK.js +0 -38
@@ -5,10 +5,10 @@ import {
5
5
  JUNIOR_NATIVE_EVENT_NAMESPACE,
6
6
  ToolInputError,
7
7
  addAgentTurnUsage,
8
- agentTurnUsageSchema,
8
+ agentsInstructionsUpdatedEvent,
9
9
  authenticationLinkedEvent,
10
10
  authenticationUnlinkedEvent
11
- } from "./chunk-CDQ4YIZM.js";
11
+ } from "./chunk-HLXXQI5Q.js";
12
12
  import {
13
13
  agentHistoryItemSchema,
14
14
  bindProviderConversation,
@@ -21,35 +21,28 @@ import {
21
21
  instructionProvenanceFor,
22
22
  sanitizePostgresJson,
23
23
  withConversationEventLock
24
- } from "./chunk-WSWM6A3R.js";
24
+ } from "./chunk-SCHH6264.js";
25
25
  import {
26
26
  SANDBOX_WORKSPACE_ROOT,
27
27
  sleep
28
28
  } from "./chunk-EM225ITJ.js";
29
29
  import {
30
30
  getStateAdapter
31
- } from "./chunk-J5H65GY2.js";
31
+ } from "./chunk-NWO5IB7V.js";
32
32
  import {
33
33
  botConfig,
34
- escapeXml,
35
- resolveGatewayModel,
36
34
  unwrapCurrentInstruction
37
- } from "./chunk-S3FAOKQU.js";
35
+ } from "./chunk-A646AKNB.js";
38
36
  import {
39
37
  toStoredSlackActor
40
38
  } from "./chunk-HZF5P5UN.js";
41
- import {
42
- parseSlackMessageTs
43
- } from "./chunk-F3B2YJ76.js";
44
39
  import {
45
40
  getActiveTraceId,
46
41
  isRecord,
47
42
  logException,
48
- logWarn,
49
- setSpanAttributes,
50
43
  toOptionalNumber,
51
44
  toOptionalString
52
- } from "./chunk-NPL66ZWL.js";
45
+ } from "./chunk-XAW6IN6M.js";
53
46
 
54
47
  // src/chat/turn-context-tag.ts
55
48
  var TURN_CONTEXT_TAG = "runtime-turn-context";
@@ -188,392 +181,6 @@ function stripRuntimeTurnContext(messages) {
188
181
  });
189
182
  }
190
183
 
191
- // src/chat/services/context-budget.ts
192
- var COMPACTION_TRIGGER_RATIO = 0.9;
193
- var CONTEXT_INPUT_LIMIT_RATIO = 0.95;
194
- var COMPACTION_TARGET_RATIO = 0.8;
195
- var FALLBACK_CONTEXT_WINDOW_TOKENS = 4e5;
196
- function positiveInteger(value, fallback) {
197
- return Number.isFinite(value) && value > 0 ? Math.floor(value) : fallback;
198
- }
199
- function estimateTextTokens(text) {
200
- return Math.ceil(text.length / 4);
201
- }
202
- function calculateContextCompactionTriggerTokens(model) {
203
- const contextWindow = positiveInteger(
204
- model.contextWindow,
205
- FALLBACK_CONTEXT_WINDOW_TOKENS
206
- );
207
- return Math.max(1, Math.floor(contextWindow * COMPACTION_TRIGGER_RATIO));
208
- }
209
- function calculateContextInputLimitTokens(model) {
210
- const contextWindow = positiveInteger(
211
- model.contextWindow,
212
- FALLBACK_CONTEXT_WINDOW_TOKENS
213
- );
214
- return Math.max(1, Math.floor(contextWindow * CONTEXT_INPUT_LIMIT_RATIO));
215
- }
216
- function calculateContextCompactionTargetTokens(triggerTokens) {
217
- return Math.max(1, Math.floor(triggerTokens * COMPACTION_TARGET_RATIO));
218
- }
219
- function getModelContextBudget(modelId) {
220
- const model = resolveGatewayModel(modelId);
221
- const advertisedContextWindow = positiveInteger(
222
- model.contextWindow,
223
- FALLBACK_CONTEXT_WINDOW_TOKENS
224
- );
225
- return {
226
- contextWindow: Math.min(
227
- botConfig.contextWindowTokens,
228
- advertisedContextWindow
229
- )
230
- };
231
- }
232
- function getAgentContextCompactionTriggerTokens(modelId) {
233
- return calculateContextCompactionTriggerTokens(
234
- getModelContextBudget(modelId)
235
- );
236
- }
237
- function getAgentContextInputLimitTokens(modelId) {
238
- return calculateContextInputLimitTokens(getModelContextBudget(modelId));
239
- }
240
- function getConversationContextCompactionTriggerTokens() {
241
- return calculateContextCompactionTriggerTokens(
242
- getModelContextBudget(botConfig.fastModelId)
243
- );
244
- }
245
-
246
- // src/chat/services/conversation-memory.ts
247
- var CONTEXT_MIN_LIVE_MESSAGES = 12;
248
- var CONTEXT_COMPACTION_BATCH_SIZE = 24;
249
- var CONTEXT_MAX_COMPACTIONS = 16;
250
- var CONTEXT_MAX_MESSAGE_CHARS = 3200;
251
- var SLACK_USER_ID_DISPLAY_PATTERN = /^[UW][A-Z0-9]{5,}$/;
252
- function generateConversationId(prefix) {
253
- return `${prefix}_${Date.now()}_${Math.random().toString(36).slice(2, 10)}`;
254
- }
255
- function normalizeConversationText(text) {
256
- return text.replace(/\r\n?/g, "\n").trim();
257
- }
258
- function buildImageContextSuffix(message, conversation) {
259
- const byFileId = conversation?.vision.byFileId;
260
- const imageFileIds = message.meta?.imageFileIds ?? [];
261
- if (!byFileId || imageFileIds.length === 0) {
262
- return "";
263
- }
264
- const summaries = imageFileIds.map((fileId) => byFileId[fileId]?.summary?.trim()).filter((summary) => Boolean(summary));
265
- if (summaries.length === 0) {
266
- return "";
267
- }
268
- return ` [image context: ${summaries.join(" | ")}]`;
269
- }
270
- function renderConversationMessageLine(message, conversation) {
271
- const displayName = conversationAuthorDisplayName(message);
272
- const text = message.text.replace(/\s+/g, " ").slice(0, CONTEXT_MAX_MESSAGE_CHARS);
273
- const markers = [];
274
- if (message.meta?.replied === false) {
275
- markers.push(
276
- `assistant skipped: ${message.meta?.skippedReason ?? "no-reply route"}`
277
- );
278
- }
279
- if (message.meta?.explicitMention) {
280
- markers.push("explicit mention");
281
- }
282
- const markerSuffix = markers.length > 0 ? ` (${markers.join("; ")})` : "";
283
- const imageContext = buildImageContextSuffix(message, conversation);
284
- return `[${message.role}] ${displayName}: ${text}${imageContext}${markerSuffix}`;
285
- }
286
- function conversationAuthorDisplayName(message) {
287
- const author = message.author;
288
- const fullName = authorDisplayField(author?.fullName, author?.userId);
289
- const userName = authorDisplayField(author?.userName, author?.userId);
290
- return fullName ?? userName ?? (message.role === "assistant" ? botConfig.userName : message.role);
291
- }
292
- function authorDisplayField(value, userId) {
293
- if (!value || value === userId || SLACK_USER_ID_DISPLAY_PATTERN.test(value)) {
294
- return void 0;
295
- }
296
- return value;
297
- }
298
- function updateConversationStats(conversation) {
299
- const contextText = buildConversationContext(conversation);
300
- conversation.stats.estimatedContextTokens = estimateTextTokens(
301
- contextText ?? ""
302
- );
303
- conversation.stats.totalMessageCount = conversation.messages.length;
304
- conversation.stats.updatedAtMs = Date.now();
305
- }
306
- function upsertConversationMessage(conversation, message) {
307
- const existingIndex = conversation.messages.findIndex(
308
- (entry) => entry.id === message.id
309
- );
310
- if (existingIndex >= 0) {
311
- conversation.messages[existingIndex] = {
312
- ...conversation.messages[existingIndex],
313
- ...message,
314
- meta: {
315
- ...conversation.messages[existingIndex]?.meta,
316
- ...message.meta
317
- }
318
- };
319
- updateConversationStats(conversation);
320
- return message.id;
321
- }
322
- conversation.messages.push(message);
323
- updateConversationStats(conversation);
324
- return message.id;
325
- }
326
- function recordDeliveredAssistantMessage(args) {
327
- const prefix = `${args.sessionId}:assistant:`;
328
- const ordinal = args.conversation.messages.filter(
329
- (message) => message.id.startsWith(prefix)
330
- ).length + 1;
331
- const messageId = `${prefix}${ordinal}`;
332
- markConversationMessage(args.conversation, args.userMessageId, {
333
- replied: true,
334
- skippedReason: void 0
335
- });
336
- upsertConversationMessage(args.conversation, {
337
- id: messageId,
338
- role: "assistant",
339
- text: normalizeConversationText(args.text),
340
- createdAtMs: Date.now(),
341
- author: {
342
- userName: botConfig.userName,
343
- isBot: true
344
- },
345
- meta: {
346
- replied: true
347
- }
348
- });
349
- return messageId;
350
- }
351
- function turnHasReply(conversation, turnId) {
352
- const assistantPrefix = `${turnId}:assistant:`;
353
- return conversation.messages.some(
354
- (message) => message.role === "assistant" && message.id.startsWith(assistantPrefix)
355
- );
356
- }
357
- function markConversationMessage(conversation, messageId, patch) {
358
- if (!messageId) return;
359
- const messageIndex = conversation.messages.findIndex(
360
- (entry) => entry.id === messageId
361
- );
362
- if (messageIndex < 0) return;
363
- const current = conversation.messages[messageIndex];
364
- conversation.messages[messageIndex] = {
365
- ...current,
366
- meta: {
367
- ...current.meta ?? {},
368
- ...patch
369
- }
370
- };
371
- updateConversationStats(conversation);
372
- }
373
- function buildConversationContext(conversation, options = {}) {
374
- const messages = conversation.messages.filter(
375
- (entry) => entry.id !== options.excludeMessageId
376
- );
377
- if (messages.length === 0 && conversation.compactions.length === 0) {
378
- return void 0;
379
- }
380
- const lines = [];
381
- if (conversation.compactions.length > 0) {
382
- lines.push("<thread-compactions>");
383
- for (const [index, compaction] of conversation.compactions.entries()) {
384
- lines.push(
385
- ` <compaction index="${index + 1}" covered_messages="${compaction.coveredMessageCount}" created_at="${new Date(compaction.createdAtMs).toISOString()}">`,
386
- compaction.summary,
387
- " </compaction>"
388
- );
389
- }
390
- lines.push("</thread-compactions>");
391
- }
392
- if (messages.length > 0) {
393
- if (lines.length > 0) {
394
- lines.push("");
395
- }
396
- lines.push("<thread-transcript>");
397
- for (const [index, message] of messages.entries()) {
398
- const author = escapeXml(conversationAuthorDisplayName(message));
399
- const actorIdAttr = message.author?.userId ? ` actor_id="${escapeXml(message.author.userId)}"` : "";
400
- const ts = new Date(message.createdAtMs).toISOString();
401
- const slackTsAttr = message.meta?.slackTs ? ` slack_ts="${escapeXml(message.meta.slackTs)}"` : "";
402
- lines.push(
403
- ` <message index="${index + 1}" ts="${ts}" role="${message.role}" author="${author}"${actorIdAttr}${slackTsAttr}>`,
404
- renderConversationMessageLine(message, conversation),
405
- " </message>"
406
- );
407
- }
408
- lines.push("</thread-transcript>");
409
- }
410
- return lines.join("\n");
411
- }
412
- function pruneCompactions(compactions) {
413
- if (compactions.length <= CONTEXT_MAX_COMPACTIONS) {
414
- return compactions;
415
- }
416
- const overflowCount = compactions.length - CONTEXT_MAX_COMPACTIONS + 1;
417
- const merged = compactions.slice(0, overflowCount);
418
- const mergedSummary = merged.map((entry) => entry.summary).join("\n").slice(0, 3500);
419
- const coveredMessageCount = merged.reduce(
420
- (count, entry) => count + entry.coveredMessageCount,
421
- 0
422
- );
423
- const compacted = {
424
- id: generateConversationId("compaction"),
425
- createdAtMs: Date.now(),
426
- summary: mergedSummary,
427
- coveredMessageCount
428
- };
429
- return [compacted, ...compactions.slice(overflowCount)];
430
- }
431
- async function summarizeConversationChunk(messages, conversation, context, deps) {
432
- const transcript = messages.map((message) => renderConversationMessageLine(message, conversation)).join("\n");
433
- try {
434
- const result = await deps.completeText({
435
- modelId: botConfig.fastModelId,
436
- temperature: 0,
437
- messages: [
438
- {
439
- role: "user",
440
- content: [
441
- "Summarize the following older Slack thread transcript segment for future assistant turns.",
442
- "Keep the summary factual and concise. Do not invent details.",
443
- "",
444
- "Output exactly three XML sections in this order:",
445
- "<active-asks> one bullet per outstanding user ask that has not been narrowed, answered, or replaced by a later turn. Omit the section body if none. </active-asks>",
446
- "<resolved-or-replaced-asks> one bullet per ask that has been replaced, narrowed, answered, or already acted on in this segment. Include the replacement/outcome inline. Omit the section body if none. </resolved-or-replaced-asks>",
447
- "<facts> one bullet per durable fact useful regardless of scope: names, ids, URLs, decisions, locations, preferences, constraints that remain true. Omit the section body if none. </facts>",
448
- "",
449
- "Do not output any text outside the three sections.",
450
- "",
451
- transcript
452
- ].join("\n"),
453
- timestamp: Date.now()
454
- }
455
- ],
456
- metadata: {
457
- modelId: botConfig.fastModelId,
458
- threadId: context.threadId ?? "",
459
- channelId: context.channelId ?? "",
460
- actorId: context.actorId ?? "",
461
- runId: context.runId ?? ""
462
- }
463
- });
464
- const summary = result.text.trim();
465
- if (summary.length > 0) {
466
- return summary.slice(0, 3500);
467
- }
468
- } catch (error) {
469
- logWarn("conversation.compaction.summary.failed", {
470
- "exception.message": error instanceof Error ? error.message : String(error),
471
- "app.compaction_messages_covered": messages.length
472
- });
473
- }
474
- return transcript.slice(0, 2800);
475
- }
476
- async function generateThreadTitleWithDeps(sourceText, deps) {
477
- const result = await deps.completeText({
478
- modelId: botConfig.fastModelId,
479
- temperature: 0,
480
- messages: [
481
- {
482
- role: "user",
483
- content: [
484
- "Generate a concise 5-8 word Slack conversation title from the first user message below.",
485
- "Capture the user's main request.",
486
- "Reply with ONLY the title, with no quotes or trailing punctuation.",
487
- "",
488
- `First user message: ${sourceText.slice(0, 500)}`
489
- ].join("\n"),
490
- timestamp: Date.now()
491
- }
492
- ],
493
- metadata: {
494
- modelId: botConfig.fastModelId
495
- }
496
- });
497
- return result.text.trim().slice(0, 60);
498
- }
499
- function getThreadTitleSourceMessage(conversation) {
500
- let firstMessage;
501
- for (const message of conversation.messages) {
502
- if (!isHumanConversationMessage(message)) {
503
- continue;
504
- }
505
- if (!firstMessage) {
506
- firstMessage = message;
507
- continue;
508
- }
509
- if (message.createdAtMs < firstMessage.createdAtMs) {
510
- firstMessage = message;
511
- continue;
512
- }
513
- if (message.createdAtMs === firstMessage.createdAtMs && message.id < firstMessage.id) {
514
- firstMessage = message;
515
- }
516
- }
517
- return firstMessage;
518
- }
519
- async function compactConversationIfNeededWithDeps(conversation, context, deps) {
520
- updateConversationStats(conversation);
521
- let estimatedTokens = conversation.stats.estimatedContextTokens;
522
- setSpanAttributes({
523
- "app.context_tokens_estimated": estimatedTokens
524
- });
525
- const triggerTokens = getConversationContextCompactionTriggerTokens();
526
- const targetTokens = calculateContextCompactionTargetTokens(triggerTokens);
527
- while (estimatedTokens > triggerTokens && conversation.messages.length > CONTEXT_MIN_LIVE_MESSAGES) {
528
- const compactCount = Math.min(
529
- CONTEXT_COMPACTION_BATCH_SIZE,
530
- conversation.messages.length - CONTEXT_MIN_LIVE_MESSAGES
531
- );
532
- if (compactCount <= 0) {
533
- break;
534
- }
535
- const compactedChunk = conversation.messages.slice(0, compactCount);
536
- const summary = await summarizeConversationChunk(
537
- compactedChunk,
538
- conversation,
539
- context,
540
- deps
541
- );
542
- conversation.compactions.push({
543
- id: generateConversationId("compaction"),
544
- createdAtMs: Date.now(),
545
- summary,
546
- coveredMessageCount: compactedChunk.length
547
- });
548
- conversation.compactions = pruneCompactions(conversation.compactions);
549
- conversation.messages = conversation.messages.slice(compactCount);
550
- conversation.stats.compactedMessageCount += compactCount;
551
- updateConversationStats(conversation);
552
- estimatedTokens = conversation.stats.estimatedContextTokens;
553
- setSpanAttributes({
554
- "app.compaction_messages_covered": compactCount,
555
- "app.compaction.trigger_tokens": triggerTokens,
556
- "app.compaction.target_tokens": targetTokens,
557
- "app.context_tokens_estimated": estimatedTokens
558
- });
559
- if (estimatedTokens <= targetTokens) {
560
- break;
561
- }
562
- }
563
- }
564
- function createConversationMemoryService(deps) {
565
- return {
566
- compactConversationIfNeeded: async (conversation, context) => await compactConversationIfNeededWithDeps(conversation, context, deps),
567
- generateThreadTitle: async (sourceText) => await generateThreadTitleWithDeps(sourceText, deps)
568
- };
569
- }
570
- function isHumanConversationMessage(message) {
571
- return message.role === "user" && message.author?.isBot !== true;
572
- }
573
- function getConversationMessageSlackTs(message) {
574
- return parseSlackMessageTs(message.meta?.slackTs);
575
- }
576
-
577
184
  // src/chat/conversations/messages.ts
578
185
  import { createHash } from "crypto";
579
186
  import { isDeepStrictEqual as isDeepStrictEqual2 } from "util";
@@ -714,7 +321,6 @@ async function hydrateConversationMessages(args) {
714
321
  );
715
322
  args.conversation.compactions = history.compaction ? projectConversationMessageSummaries([history.compaction]) : [];
716
323
  args.conversation.messages = projectConversationMessages(history);
717
- updateConversationStats(args.conversation);
718
324
  }
719
325
  async function persistConversationMessages(args) {
720
326
  if (!args.conversationId || args.conversation.messages.length === 0) return;
@@ -1214,6 +820,59 @@ async function recordAuthenticationAccountChange(action, args) {
1214
820
  async function recordAuthenticationLinked(args) {
1215
821
  await recordAuthenticationAccountChange("linked", args);
1216
822
  }
823
+ async function loadAgentsEvent(conversationId) {
824
+ const event = await getConversationEventStore().loadLatestStructuredEvent(
825
+ conversationId,
826
+ JUNIOR_NATIVE_EVENT_NAMESPACE,
827
+ agentsInstructionsUpdatedEvent.eventName
828
+ );
829
+ if (!event || event.data.type !== "structured_event") return void 0;
830
+ return {
831
+ content: agentsInstructionsUpdatedEvent.parse(
832
+ event.data.content
833
+ ),
834
+ seq: event.seq
835
+ };
836
+ }
837
+ function agentsInstructionsIdempotencyKey(args) {
838
+ const previous = args.previousSeq ?? "none";
839
+ return `native:${JUNIOR_NATIVE_EVENT_NAMESPACE}:agents_instructions_updated:${args.turnId}:${args.action}:${previous}->${args.fingerprint}`;
840
+ }
841
+ async function recordAgentsInstructionsUpdated(args) {
842
+ const previous = await loadAgentsEvent(args.conversationId);
843
+ const instructions = args.instructions;
844
+ if (previous && (!instructions && previous.content.action === "cleared" || instructions && previous.content.action !== "cleared" && previous.content.fingerprint === instructions.fingerprint && previous.content.directory === instructions.directory)) {
845
+ return;
846
+ }
847
+ const action = !instructions ? "cleared" : previous && previous.content.action !== "cleared" ? "replaced" : "loaded";
848
+ const fingerprint = instructions?.fingerprint ?? "cleared";
849
+ const content = agentsInstructionsUpdatedEvent.parse({
850
+ action,
851
+ fingerprint,
852
+ sources: instructions?.sources ?? [],
853
+ ...instructions ? { directory: instructions.directory } : {},
854
+ ...instructions ? { textBytes: Buffer.byteLength(instructions.text, "utf8") } : {}
855
+ });
856
+ await getConversationEventStore().append(args.conversationId, [
857
+ {
858
+ createdAtMs: Date.now(),
859
+ idempotencyKey: agentsInstructionsIdempotencyKey({
860
+ action,
861
+ fingerprint,
862
+ previousSeq: previous?.seq,
863
+ turnId: args.turnId
864
+ }),
865
+ data: {
866
+ type: "structured_event",
867
+ namespace: JUNIOR_NATIVE_EVENT_NAMESPACE,
868
+ name: agentsInstructionsUpdatedEvent.eventName,
869
+ version: agentsInstructionsUpdatedEvent.version,
870
+ turnId: args.turnId,
871
+ content
872
+ }
873
+ }
874
+ ]);
875
+ }
1217
876
  async function loadTurnRoute(args) {
1218
877
  const event = await getConversationEventStore().loadByIdempotencyKey(
1219
878
  args.conversationId,
@@ -1334,7 +993,6 @@ function isTurnInputDeferredError(error) {
1334
993
  }
1335
994
  function startActiveTurn(args) {
1336
995
  args.conversation.processing.activeTurnId = args.nextTurnId;
1337
- args.updateConversationStats(args.conversation);
1338
996
  }
1339
997
  function clearActiveTurn(conversation, sessionId) {
1340
998
  if (!sessionId || conversation.processing.activeTurnId === sessionId) {
@@ -1344,12 +1002,10 @@ function clearActiveTurn(conversation, sessionId) {
1344
1002
  function markTurnClosed(args) {
1345
1003
  clearActiveTurn(args.conversation, args.sessionId);
1346
1004
  args.conversation.processing.lastCompletedAtMs = args.nowMs;
1347
- args.updateConversationStats(args.conversation);
1348
1005
  }
1349
1006
  function markTurnCompleted(args) {
1350
1007
  clearActiveTurn(args.conversation, args.sessionId);
1351
1008
  args.conversation.processing.lastCompletedAtMs = args.nowMs;
1352
- args.updateConversationStats(args.conversation);
1353
1009
  }
1354
1010
  function markTurnFailed(args) {
1355
1011
  clearActiveTurn(args.conversation, args.sessionId);
@@ -1358,7 +1014,6 @@ function markTurnFailed(args) {
1358
1014
  replied: false,
1359
1015
  skippedReason: "reply failed"
1360
1016
  });
1361
- args.updateConversationStats(args.conversation);
1362
1017
  }
1363
1018
 
1364
1019
  // src/chat/no-reply.ts
@@ -1402,9 +1057,6 @@ function getAssistantReplyText(message) {
1402
1057
  }
1403
1058
 
1404
1059
  // src/chat/state/turn-session.ts
1405
- import {
1406
- actorSchema
1407
- } from "@sentry/junior-plugin-api";
1408
1060
  import { z as z2 } from "zod";
1409
1061
 
1410
1062
  // src/chat/repository-instructions.ts
@@ -1420,7 +1072,7 @@ var RIPGREP_EXCLUDED_GLOBS = [
1420
1072
  ];
1421
1073
  var DEFAULT_SEARCH_COMMAND_TIMEOUT_MS = 3e4;
1422
1074
  var SKIPPED_DIRECTORIES = /* @__PURE__ */ new Set([".git", "node_modules"]);
1423
- function positiveInteger2(value) {
1075
+ function positiveInteger(value) {
1424
1076
  if (typeof value !== "number" || !Number.isFinite(value)) {
1425
1077
  return void 0;
1426
1078
  }
@@ -1767,6 +1419,9 @@ function agentTurnSessionConversationIndexKey(conversationId) {
1767
1419
  // src/chat/state/turn-session.ts
1768
1420
  var AGENT_TURN_SESSION_RESUME_TTL_MS = 24 * 60 * 60 * 1e3;
1769
1421
  var AGENT_TURN_SESSION_TERMINAL_TTL_MS = 60 * 60 * 1e3;
1422
+ function executionMetricsForSession(conversation, sessionId) {
1423
+ return conversation?.executionMetrics?.runId === sessionId ? conversation.executionMetrics : void 0;
1424
+ }
1770
1425
  function definedProps(values) {
1771
1426
  return Object.fromEntries(
1772
1427
  Object.entries(values).filter(([, value]) => value !== void 0)
@@ -1807,18 +1462,12 @@ var agentTurnSessionSummarySchema = z2.object({
1807
1462
  updatedAtMs: nonNegativeNumberSchema
1808
1463
  }).strip();
1809
1464
  var storedAgentTurnSessionRecordSchema = z2.object({
1810
- channelName: z2.string().min(1).optional(),
1811
1465
  version: z2.number().int().nonnegative(),
1812
1466
  conversationId: z2.string().min(1),
1813
- cumulativeDurationMs: nonNegativeNumberSchema,
1814
- cumulativeUsage: agentTurnUsageSchema.optional(),
1815
1467
  dispatchId: z2.string().min(1).optional(),
1816
1468
  dispatchOutcome: z2.enum(["blocked", "completed", "failed"]).optional(),
1817
1469
  resultMessageId: z2.string().min(1).optional(),
1818
1470
  lastProgressAtMs: nonNegativeNumberSchema,
1819
- loadedSkillNames: z2.array(z2.string()).optional(),
1820
- modelId: z2.string().min(1).optional(),
1821
- reasoningLevel: z2.string().min(1).optional(),
1822
1471
  resumeReason: agentTurnResumeReasonSchema.optional(),
1823
1472
  resumedFromSliceId: z2.number().int().nonnegative().optional(),
1824
1473
  sessionId: z2.string().min(1),
@@ -1828,7 +1477,6 @@ var storedAgentTurnSessionRecordSchema = z2.object({
1828
1477
  surface: agentTurnSurfaceSchema.optional(),
1829
1478
  traceId: z2.string().optional(),
1830
1479
  updatedAtMs: nonNegativeNumberSchema,
1831
- actors: z2.array(actorSchema).optional(),
1832
1480
  committedSeq: seqCursorSchema,
1833
1481
  historyVersion: z2.number().int().nonnegative().optional(),
1834
1482
  errorMessage: z2.string().optional(),
@@ -1908,7 +1556,7 @@ async function recordConversationActivityMetadata(args) {
1908
1556
  visibility: isChild ? void 0 : args.destinationVisibility
1909
1557
  });
1910
1558
  }
1911
- function materializeAgentTurnSessionRecord(stored, piProjection, turnStartMessageIndex, restoreVolatileContext = true) {
1559
+ function materializeAgentTurnSessionRecord(stored, piProjection, turnStartMessageIndex, restoreVolatileContext = true, runtimeMetadata) {
1912
1560
  const restoredProjection = restoreVolatileContext && (stored.state === "running" || stored.state === "awaiting_resume") ? restoreRuntimeContext(piProjection, stored.runtimeContext) : piProjection;
1913
1561
  return {
1914
1562
  version: stored.version,
@@ -1921,17 +1569,17 @@ function materializeAgentTurnSessionRecord(stored, piProjection, turnStartMessag
1921
1569
  updatedAtMs: stored.updatedAtMs,
1922
1570
  piMessages: restoredProjection.messages,
1923
1571
  piMessageProvenance: restoredProjection.provenance,
1924
- actors: stored.actors ?? instructionActors(piProjection.provenance),
1925
- cumulativeDurationMs: stored.cumulativeDurationMs,
1572
+ actors: instructionActors(piProjection.provenance),
1573
+ cumulativeDurationMs: runtimeMetadata?.cumulativeDurationMs ?? 0,
1926
1574
  ...definedProps({
1927
- channelName: stored.channelName,
1928
- cumulativeUsage: stored.cumulativeUsage,
1575
+ channelName: runtimeMetadata?.channelName,
1576
+ cumulativeUsage: runtimeMetadata?.cumulativeUsage,
1577
+ loadedSkillNames: runtimeMetadata?.loadedSkillNames,
1578
+ modelId: runtimeMetadata?.modelId,
1579
+ reasoningLevel: runtimeMetadata?.reasoningLevel,
1929
1580
  dispatchId: stored.dispatchId,
1930
1581
  dispatchOutcome: stored.dispatchOutcome,
1931
1582
  errorMessage: stored.errorMessage,
1932
- loadedSkillNames: stored.loadedSkillNames,
1933
- modelId: stored.modelId,
1934
- reasoningLevel: stored.reasoningLevel,
1935
1583
  resumeReason: stored.resumeReason,
1936
1584
  resultMessageId: stored.resultMessageId,
1937
1585
  resumedFromSliceId: stored.resumedFromSliceId,
@@ -2008,11 +1656,18 @@ async function materializeStoredAgentTurnSessionRecord(conversationId, sessionId
2008
1656
  const followsReplacement = followCurrentReplacement && (parsed.state === "running" || parsed.state === "awaiting_resume") && parsed.historyVersion !== void 0 && parsed.historyVersion !== currentHistoryVersion;
2009
1657
  const piProjection = followsReplacement ? projectConversationEvents(currentHistory) : pinnedProjection;
2010
1658
  const turnStartMessageIndex = followsReplacement ? 0 : parsed.turnStartSeq === void 0 ? void 0 : piProjection.seqs.filter((seq) => seq <= parsed.turnStartSeq).length;
1659
+ const conversation = await getConversationStore().get({ conversationId });
1660
+ const executionMetrics = executionMetricsForSession(conversation, sessionId);
2011
1661
  return materializeAgentTurnSessionRecord(
2012
1662
  parsed,
2013
1663
  piProjection,
2014
1664
  turnStartMessageIndex,
2015
- !followsReplacement && (parsed.historyVersion === void 0 || parsed.historyVersion === currentHistoryVersion)
1665
+ !followsReplacement && (parsed.historyVersion === void 0 || parsed.historyVersion === currentHistoryVersion),
1666
+ {
1667
+ channelName: conversation?.channelName,
1668
+ cumulativeDurationMs: executionMetrics?.durationMs,
1669
+ cumulativeUsage: executionMetrics?.usage
1670
+ }
2016
1671
  );
2017
1672
  }
2018
1673
  async function getAgentTurnSessionRecord(conversationId, sessionId) {
@@ -2041,18 +1696,11 @@ function buildStoredRecord(args) {
2041
1696
  lastProgressAtMs: args.lastProgressAtMs ?? nowMs,
2042
1697
  updatedAtMs: nowMs,
2043
1698
  committedSeq: args.committedSeq,
2044
- cumulativeDurationMs: args.cumulativeDurationMs,
2045
1699
  ...definedProps({
2046
- actors: args.actors,
2047
- channelName: args.channelName,
2048
- cumulativeUsage: args.cumulativeUsage,
2049
1700
  dispatchId: args.dispatchId,
2050
1701
  dispatchOutcome: args.dispatchOutcome,
2051
1702
  errorMessage: args.errorMessage,
2052
1703
  historyVersion: args.historyVersion,
2053
- loadedSkillNames: args.loadedSkillNames,
2054
- modelId: args.modelId,
2055
- reasoningLevel: args.reasoningLevel,
2056
1704
  resumeReason: args.resumeReason,
2057
1705
  resultMessageId: args.resultMessageId,
2058
1706
  resumedFromSliceId: args.resumedFromSliceId,
@@ -2074,7 +1722,7 @@ async function setStoredRecord(args) {
2074
1722
  destinationVisibility: args.destinationVisibility,
2075
1723
  nowMs: Date.now(),
2076
1724
  source: args.source,
2077
- summary: storedRecord
1725
+ summary: { ...storedRecord, ...args.runtimeMetadata }
2078
1726
  });
2079
1727
  await stateAdapter.set(
2080
1728
  agentTurnSessionKey(storedRecord.conversationId, storedRecord.sessionId),
@@ -2091,7 +1739,9 @@ async function setStoredRecord(args) {
2091
1739
  messages: [...args.piMessages],
2092
1740
  provenance: [...args.piMessageProvenance]
2093
1741
  },
2094
- args.turnStartMessageIndex
1742
+ args.turnStartMessageIndex,
1743
+ true,
1744
+ args.runtimeMetadata
2095
1745
  );
2096
1746
  }
2097
1747
  async function updateAgentTurnSessionState(args) {
@@ -2106,6 +1756,16 @@ async function updateAgentTurnSessionState(args) {
2106
1756
  piMessages: args.existing.piMessages,
2107
1757
  piMessageProvenance: args.existing.piMessageProvenance,
2108
1758
  ttlMs: agentTurnSessionRecordTtlMs(args.state),
1759
+ runtimeMetadata: {
1760
+ cumulativeDurationMs: args.existing.cumulativeDurationMs,
1761
+ ...definedProps({
1762
+ channelName: args.existing.channelName,
1763
+ cumulativeUsage: args.existing.cumulativeUsage,
1764
+ loadedSkillNames: args.existing.loadedSkillNames,
1765
+ modelId: args.existing.modelId,
1766
+ reasoningLevel: args.existing.reasoningLevel
1767
+ })
1768
+ },
2109
1769
  ...definedProps({
2110
1770
  turnStartMessageIndex: args.existing.turnStartMessageIndex
2111
1771
  }),
@@ -2118,18 +1778,11 @@ async function updateAgentTurnSessionState(args) {
2118
1778
  startedAtMs: parsed.startedAtMs,
2119
1779
  lastProgressAtMs: parsed.lastProgressAtMs,
2120
1780
  previousVersion: parsed.version,
2121
- cumulativeDurationMs: args.existing.cumulativeDurationMs,
2122
- actors: args.existing.actors,
2123
1781
  ...definedProps({
2124
- channelName: parsed.channelName,
2125
- cumulativeUsage: args.existing.cumulativeUsage,
2126
1782
  dispatchId: args.existing.dispatchId,
2127
1783
  dispatchOutcome: args.existing.dispatchOutcome,
2128
1784
  errorMessage: args.errorMessage ?? args.existing.errorMessage,
2129
1785
  historyVersion: parsed.historyVersion,
2130
- loadedSkillNames: args.existing.loadedSkillNames,
2131
- modelId: args.existing.modelId,
2132
- reasoningLevel: args.existing.reasoningLevel,
2133
1786
  resumeReason: args.existing.resumeReason,
2134
1787
  resultMessageId: args.existing.resultMessageId,
2135
1788
  resumedFromSliceId: args.existing.resumedFromSliceId,
@@ -2145,6 +1798,13 @@ async function upsertAgentTurnSessionRecord(args) {
2145
1798
  args.conversationId,
2146
1799
  args.sessionId
2147
1800
  );
1801
+ const conversation = await (args.conversationStore ?? getConversationStore()).get({
1802
+ conversationId: args.conversationId
1803
+ });
1804
+ const executionMetrics = executionMetricsForSession(
1805
+ conversation,
1806
+ args.sessionId
1807
+ );
2148
1808
  const existingDispatchId = existingRecord?.dispatchId ?? (await listAgentTurnSessionSummariesForConversation(args.conversationId)).find((summary) => summary.sessionId === args.sessionId)?.dispatchId;
2149
1809
  if (existingDispatchId && args.dispatchId && existingDispatchId !== args.dispatchId) {
2150
1810
  throw new Error(
@@ -2181,6 +1841,16 @@ async function upsertAgentTurnSessionRecord(args) {
2181
1841
  piMessages: commit.messages,
2182
1842
  piMessageProvenance: commit.provenance,
2183
1843
  ttlMs,
1844
+ // TODO(#1267): remove the temporary caller-to-SQL metadata write once the
1845
+ // SQL-only turn-session readers have completed rollout.
1846
+ runtimeMetadata: {
1847
+ channelName: args.channelName ?? conversation?.channelName,
1848
+ cumulativeDurationMs: args.cumulativeDurationMs ?? executionMetrics?.durationMs ?? 0,
1849
+ cumulativeUsage: args.cumulativeUsage ?? executionMetrics?.usage,
1850
+ loadedSkillNames: args.loadedSkillNames,
1851
+ modelId: args.modelId,
1852
+ reasoningLevel: args.reasoningLevel
1853
+ },
2184
1854
  // Pass through only an explicit override so indexes stay on the resume window
2185
1855
  // by default even when the record takes a short terminal TTL.
2186
1856
  ...definedProps({ indexTtlMs: args.ttlMs }),
@@ -2193,22 +1863,11 @@ async function upsertAgentTurnSessionRecord(args) {
2193
1863
  committedSeq: commit.committedSeq,
2194
1864
  historyVersion: commit.historyVersion,
2195
1865
  previousVersion: existingRecord?.version,
2196
- cumulativeDurationMs: args.cumulativeDurationMs ?? existingRecord?.cumulativeDurationMs ?? 0,
2197
- modelId: args.modelId,
2198
- actors: instructionActors([
2199
- ...(existingRecord?.actors ?? []).map(instructionProvenanceFor),
2200
- ...(args.actors ?? []).map(instructionProvenanceFor),
2201
- ...commit.provenance
2202
- ]),
2203
1866
  ...definedProps({
2204
- channelName: args.channelName ?? existingRecord?.channelName,
2205
- cumulativeUsage: args.cumulativeUsage,
2206
1867
  dispatchId: args.dispatchId ?? existingRecord?.dispatchId,
2207
1868
  dispatchOutcome: args.dispatchOutcome ?? existingRecord?.dispatchOutcome,
2208
1869
  errorMessage: args.errorMessage,
2209
1870
  lastProgressAtMs: args.lastProgressAtMs,
2210
- loadedSkillNames: args.loadedSkillNames,
2211
- reasoningLevel: args.reasoningLevel ?? existingRecord?.reasoningLevel,
2212
1871
  resumeReason: args.resumeReason,
2213
1872
  resultMessageId: args.resultMessageId ?? existingRecord?.resultMessageId,
2214
1873
  resumedFromSliceId: args.resumedFromSliceId,
@@ -2226,6 +1885,14 @@ async function recordAgentTurnSessionSummary(args) {
2226
1885
  args.conversationId,
2227
1886
  args.sessionId
2228
1887
  );
1888
+ const conversationStore = args.conversationStore ?? getConversationStore();
1889
+ const conversation = await conversationStore.get({
1890
+ conversationId: args.conversationId
1891
+ });
1892
+ const executionMetrics = executionMetricsForSession(
1893
+ conversation,
1894
+ args.sessionId
1895
+ );
2229
1896
  const priorSummary = (await listAgentTurnSessionSummariesForConversation(args.conversationId)).find((summary2) => summary2.sessionId === args.sessionId);
2230
1897
  const existing = stored ?? priorSummary;
2231
1898
  const existingDispatchId = existing?.dispatchId;
@@ -2255,16 +1922,16 @@ async function recordAgentTurnSessionSummary(args) {
2255
1922
  };
2256
1923
  await recordConversationActivityMetadata({
2257
1924
  actor: args.actor,
2258
- conversationStore: args.conversationStore,
1925
+ conversationStore,
2259
1926
  destination: args.destination,
2260
1927
  destinationVisibility: args.destinationVisibility,
2261
1928
  nowMs,
2262
1929
  source: args.source,
2263
1930
  summary: {
2264
1931
  ...summary,
2265
- channelName: args.channelName ?? stored?.channelName,
2266
- cumulativeDurationMs: args.cumulativeDurationMs ?? stored?.cumulativeDurationMs ?? 0,
2267
- cumulativeUsage: args.cumulativeUsage ?? stored?.cumulativeUsage,
1932
+ channelName: args.channelName ?? conversation?.channelName,
1933
+ cumulativeDurationMs: args.cumulativeDurationMs ?? executionMetrics?.durationMs ?? 0,
1934
+ cumulativeUsage: args.cumulativeUsage ?? executionMetrics?.usage,
2268
1935
  startedAtMs: stored?.startedAtMs ?? args.startedAtMs ?? nowMs
2269
1936
  }
2270
1937
  });
@@ -2493,9 +2160,9 @@ async function persistCompletedSessionRecord(args) {
2493
2160
  destination: args.destination,
2494
2161
  destinationVisibility: args.destinationVisibility,
2495
2162
  dispatchId: args.dispatchId,
2496
- loadedSkillNames: args.loadedSkillNames ?? latestSessionRecord?.loadedSkillNames,
2163
+ loadedSkillNames: args.loadedSkillNames,
2497
2164
  modelId: args.modelId,
2498
- reasoningLevel: args.reasoningLevel ?? latestSessionRecord?.reasoningLevel,
2165
+ reasoningLevel: args.reasoningLevel,
2499
2166
  sessionId: args.sessionId,
2500
2167
  source: args.source,
2501
2168
  surface: args.surface,
@@ -2574,7 +2241,7 @@ async function persistAuthPauseSessionRecord(args) {
2574
2241
  // Auth-pause keeps caller-owned skill names only.
2575
2242
  loadedSkillNames: args.loadedSkillNames,
2576
2243
  modelId: args.modelId,
2577
- reasoningLevel: args.reasoningLevel ?? latestSessionRecord?.reasoningLevel,
2244
+ reasoningLevel: args.reasoningLevel,
2578
2245
  sessionId: args.sessionId,
2579
2246
  source: args.source,
2580
2247
  surface: args.surface
@@ -2645,7 +2312,7 @@ async function persistContinuationSessionRecord(args) {
2645
2312
  // Continuation keeps caller-owned skill names only.
2646
2313
  loadedSkillNames: args.loadedSkillNames,
2647
2314
  modelId: args.modelId,
2648
- reasoningLevel: args.reasoningLevel ?? latestSessionRecord?.reasoningLevel,
2315
+ reasoningLevel: args.reasoningLevel,
2649
2316
  sessionId: args.sessionId,
2650
2317
  source: args.source,
2651
2318
  surface: args.surface
@@ -2713,7 +2380,7 @@ async function persistYieldSessionRecord(args) {
2713
2380
  // Yield keeps caller-owned skill names only.
2714
2381
  loadedSkillNames: args.loadedSkillNames,
2715
2382
  modelId: args.modelId,
2716
- reasoningLevel: args.reasoningLevel ?? latestSessionRecord?.reasoningLevel,
2383
+ reasoningLevel: args.reasoningLevel,
2717
2384
  sessionId: args.sessionId,
2718
2385
  source: args.source,
2719
2386
  surface: args.surface
@@ -2752,19 +2419,11 @@ async function persistYieldSessionRecord(args) {
2752
2419
 
2753
2420
  // src/chat/state/conversation.ts
2754
2421
  function defaultConversationState() {
2755
- const nowMs = Date.now();
2756
2422
  return {
2757
2423
  schemaVersion: 1,
2758
2424
  messages: [],
2759
2425
  compactions: [],
2760
- backfill: {},
2761
2426
  processing: {},
2762
- stats: {
2763
- estimatedContextTokens: 0,
2764
- totalMessageCount: 0,
2765
- compactedMessageCount: 0,
2766
- updatedAtMs: nowMs
2767
- },
2768
2427
  vision: {
2769
2428
  byFileId: {}
2770
2429
  }
@@ -2799,27 +2458,14 @@ function coerceThreadConversationState(value) {
2799
2458
  }
2800
2459
  const root = value;
2801
2460
  const rawConversation = isRecord(root.conversation) ? root.conversation : {};
2802
- const base = defaultConversationState();
2803
2461
  const messages = [];
2804
2462
  const compactions = [];
2805
- const rawBackfill = isRecord(rawConversation.backfill) ? rawConversation.backfill : {};
2806
- const backfill = {
2807
- completedAtMs: toOptionalNumber(rawBackfill.completedAtMs),
2808
- source: rawBackfill.source === "recent_messages" || rawBackfill.source === "thread_fetch" ? rawBackfill.source : void 0
2809
- };
2810
2463
  const rawProcessing = isRecord(rawConversation.processing) ? rawConversation.processing : {};
2811
2464
  const processing = {
2812
2465
  activeTurnId: toOptionalString(rawProcessing.activeTurnId),
2813
2466
  lastCompletedAtMs: toOptionalNumber(rawProcessing.lastCompletedAtMs),
2814
2467
  pendingAuth: coercePendingAuthState(rawProcessing.pendingAuth)
2815
2468
  };
2816
- const rawStats = isRecord(rawConversation.stats) ? rawConversation.stats : {};
2817
- const stats = {
2818
- estimatedContextTokens: toOptionalNumber(rawStats.estimatedContextTokens) ?? base.stats.estimatedContextTokens,
2819
- totalMessageCount: toOptionalNumber(rawStats.totalMessageCount) ?? messages.length,
2820
- compactedMessageCount: toOptionalNumber(rawStats.compactedMessageCount) ?? 0,
2821
- updatedAtMs: toOptionalNumber(rawStats.updatedAtMs) ?? base.stats.updatedAtMs
2822
- };
2823
2469
  const rawVision = isRecord(rawConversation.vision) ? rawConversation.vision : {};
2824
2470
  const rawVisionByFileId = isRecord(rawVision.byFileId) ? rawVision.byFileId : {};
2825
2471
  const byFileId = {};
@@ -2838,9 +2484,7 @@ function coerceThreadConversationState(value) {
2838
2484
  schemaVersion: 1,
2839
2485
  messages,
2840
2486
  compactions,
2841
- backfill,
2842
2487
  processing,
2843
- stats,
2844
2488
  vision: {
2845
2489
  backfillCompletedAtMs: toOptionalNumber(rawVision.backfillCompletedAtMs),
2846
2490
  byFileId
@@ -2848,19 +2492,13 @@ function coerceThreadConversationState(value) {
2848
2492
  };
2849
2493
  }
2850
2494
  function buildConversationStatePatch(conversation) {
2851
- const {
2852
- compactions: _compactions,
2853
- messages: _messages,
2854
- ...scratch
2855
- } = conversation;
2856
2495
  return {
2857
2496
  conversation: {
2858
- ...scratch,
2859
2497
  schemaVersion: 1,
2860
- stats: {
2861
- ...conversation.stats,
2862
- totalMessageCount: conversation.messages.length,
2863
- updatedAtMs: Date.now()
2498
+ processing: { ...conversation.processing },
2499
+ vision: {
2500
+ backfillCompletedAtMs: conversation.vision.backfillCompletedAtMs,
2501
+ byFileId: { ...conversation.vision.byFileId }
2864
2502
  }
2865
2503
  }
2866
2504
  };
@@ -3136,6 +2774,19 @@ function buildThreadStatePayload(patch) {
3136
2774
  }
3137
2775
  return payload;
3138
2776
  }
2777
+ async function mergePersistedState(key, payload) {
2778
+ if (Object.keys(payload).length === 0) {
2779
+ return;
2780
+ }
2781
+ const stateAdapter = getStateAdapter();
2782
+ await stateAdapter.connect();
2783
+ const existing = await stateAdapter.get(key) ?? {};
2784
+ await stateAdapter.set(
2785
+ key,
2786
+ { ...existing, ...payload },
2787
+ JUNIOR_THREAD_STATE_TTL_MS
2788
+ );
2789
+ }
3139
2790
  function mergeArtifactsState(artifacts, patch) {
3140
2791
  if (!patch) {
3141
2792
  return artifacts;
@@ -3172,11 +2823,14 @@ async function persistThreadState(thread, patch) {
3172
2823
  await persistThreadRuntimeState(thread, patch);
3173
2824
  }
3174
2825
  async function persistThreadRuntimeState(thread, patch) {
3175
- const payload = buildThreadStatePayload(patch);
3176
- if (Object.keys(payload).length === 0) {
3177
- return;
2826
+ const threadId = toOptionalString(thread.id) ?? toOptionalString(thread.runId);
2827
+ if (!threadId) {
2828
+ throw new Error("thread id is required to persist runtime scratch");
3178
2829
  }
3179
- await thread.setState(payload);
2830
+ await mergePersistedState(
2831
+ threadStateKey(threadId),
2832
+ buildThreadStatePayload(patch)
2833
+ );
3180
2834
  }
3181
2835
  async function getPersistedThreadState(threadId) {
3182
2836
  const stateAdapter = getStateAdapter();
@@ -3199,26 +2853,21 @@ async function persistThreadStateById(threadId, patch) {
3199
2853
  conversationId: threadId
3200
2854
  });
3201
2855
  }
3202
- const payload = buildThreadStatePayload(patch);
3203
- if (Object.keys(payload).length === 0) {
3204
- return;
3205
- }
3206
- const stateAdapter = getStateAdapter();
3207
- await stateAdapter.connect();
3208
- const key = threadStateKey(threadId);
3209
- const existing = await stateAdapter.get(key) ?? {};
3210
- await stateAdapter.set(
3211
- key,
3212
- { ...existing, ...payload },
3213
- JUNIOR_THREAD_STATE_TTL_MS
2856
+ await mergePersistedState(
2857
+ threadStateKey(threadId),
2858
+ buildThreadStatePayload(patch)
3214
2859
  );
3215
2860
  }
3216
2861
  function getChannelConfigurationService(thread) {
3217
2862
  const channel = thread.channel;
2863
+ const channelId = toOptionalString(thread.channelId) ?? toOptionalString(channel.id);
2864
+ if (!channelId) {
2865
+ throw new Error("channel id is required to load channel configuration");
2866
+ }
3218
2867
  return createChannelConfigurationService({
3219
- load: async () => channel.state,
2868
+ load: async () => await channel.state,
3220
2869
  save: async (state) => {
3221
- await channel.setState({
2870
+ await mergePersistedState(channelStateKey(channelId), {
3222
2871
  configuration: state
3223
2872
  });
3224
2873
  }
@@ -3228,15 +2877,9 @@ function getChannelConfigurationServiceById(channelId) {
3228
2877
  return createChannelConfigurationService({
3229
2878
  load: async () => await getPersistedChannelState(channelId),
3230
2879
  save: async (state) => {
3231
- const stateAdapter = getStateAdapter();
3232
- await stateAdapter.connect();
3233
- const key = channelStateKey(channelId);
3234
- const existing = await stateAdapter.get(key) ?? {};
3235
- await stateAdapter.set(
3236
- key,
3237
- { ...existing, configuration: state },
3238
- JUNIOR_THREAD_STATE_TTL_MS
3239
- );
2880
+ await mergePersistedState(channelStateKey(channelId), {
2881
+ configuration: state
2882
+ });
3240
2883
  }
3241
2884
  });
3242
2885
  }
@@ -3323,20 +2966,6 @@ export {
3323
2966
  historyItemFromPiMessage,
3324
2967
  piMessageFromHistoryItem,
3325
2968
  persistConversationMessageSummaries,
3326
- estimateTextTokens,
3327
- getAgentContextCompactionTriggerTokens,
3328
- getAgentContextInputLimitTokens,
3329
- normalizeConversationText,
3330
- updateConversationStats,
3331
- upsertConversationMessage,
3332
- recordDeliveredAssistantMessage,
3333
- turnHasReply,
3334
- markConversationMessage,
3335
- buildConversationContext,
3336
- getThreadTitleSourceMessage,
3337
- createConversationMemoryService,
3338
- isHumanConversationMessage,
3339
- getConversationMessageSlackTs,
3340
2969
  hydrateConversationMessages,
3341
2970
  persistConversationMessages,
3342
2971
  loadProjection,
@@ -3349,6 +2978,7 @@ export {
3349
2978
  recordAuthorizationRequested,
3350
2979
  recordAuthorizationCompleted,
3351
2980
  recordAuthenticationLinked,
2981
+ recordAgentsInstructionsUpdated,
3352
2982
  loadTurnRoute,
3353
2983
  recordTurnRoute,
3354
2984
  recordToolExecutionStarted,
@@ -3356,7 +2986,7 @@ export {
3356
2986
  MAX_TEXT_CHARS,
3357
2987
  RIPGREP_EXCLUDED_GLOBS,
3358
2988
  DEFAULT_SEARCH_COMMAND_TIMEOUT_MS,
3359
- positiveInteger2 as positiveInteger,
2989
+ positiveInteger,
3360
2990
  normalizeToLf,
3361
2991
  truncateText,
3362
2992
  isMissingPathError,