@sentry/junior 0.105.0 → 0.107.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 (133) hide show
  1. package/dist/{agent-hooks-EOFSY2PD.js → agent-hooks-ICPIJAFY.js} +9 -7
  2. package/dist/api/conversations/detail.d.ts +1 -1
  3. package/dist/api/conversations/events.d.ts +14 -0
  4. package/dist/api/conversations/schema.d.ts +196 -463
  5. package/dist/api/schema.d.ts +2 -7
  6. package/dist/api/schema.js +10 -8
  7. package/dist/api.js +377 -810
  8. package/dist/app.js +2284 -2288
  9. package/dist/{catalog-runtime-5CVM2DOF.js → catalog-runtime-FOFNPR3P.js} +3 -2
  10. package/dist/chat/agent/prompt.d.ts +1 -1
  11. package/dist/chat/agent/request.d.ts +28 -29
  12. package/dist/chat/agent/resume.d.ts +7 -7
  13. package/dist/chat/agent/session.d.ts +5 -6
  14. package/dist/chat/agent/tools.d.ts +3 -3
  15. package/dist/chat/agent-dispatch/runner.d.ts +2 -0
  16. package/dist/chat/config.d.ts +3 -2
  17. package/dist/chat/conversations/history.d.ts +582 -167
  18. package/dist/chat/conversations/message-projection.d.ts +6 -0
  19. package/dist/chat/conversations/message-summaries.d.ts +9 -0
  20. package/dist/chat/conversations/messages.d.ts +13 -27
  21. package/dist/chat/conversations/projection.d.ts +30 -53
  22. package/dist/chat/conversations/provenance.d.ts +42 -0
  23. package/dist/chat/conversations/search.d.ts +2 -2
  24. package/dist/chat/conversations/sql/conversation-row.d.ts +1 -1
  25. package/dist/chat/conversations/sql/event-lock.d.ts +3 -0
  26. package/dist/chat/conversations/sql/history.d.ts +3 -3
  27. package/dist/chat/conversations/sql/privacy.d.ts +27 -0
  28. package/dist/chat/conversations/sql/purge.d.ts +2 -10
  29. package/dist/chat/conversations/sql/store.d.ts +0 -5
  30. package/dist/chat/conversations/store.d.ts +8 -13
  31. package/dist/chat/conversations/turn-lifecycle.d.ts +41 -0
  32. package/dist/chat/db.d.ts +4 -10
  33. package/dist/chat/model-profile.d.ts +10 -0
  34. package/dist/chat/oauth-flow.d.ts +0 -5
  35. package/dist/chat/pi/conversation-events.d.ts +33 -0
  36. package/dist/chat/pi/sql-model-usage.d.ts +10 -0
  37. package/dist/chat/plugins/agent-hooks.d.ts +4 -2
  38. package/dist/chat/resource-events/ingest.d.ts +0 -13
  39. package/dist/chat/runtime/delivered-turn-state.d.ts +1 -1
  40. package/dist/chat/runtime/generated-artifacts.d.ts +1 -1
  41. package/dist/chat/runtime/reply-executor.d.ts +2 -0
  42. package/dist/chat/runtime/slack-resume.d.ts +23 -28
  43. package/dist/chat/runtime/slack-runtime.d.ts +2 -0
  44. package/dist/chat/runtime/turn.d.ts +14 -1
  45. package/dist/chat/services/context-compaction-marker.d.ts +0 -1
  46. package/dist/chat/services/context-compaction.d.ts +1 -0
  47. package/dist/chat/services/conversation-memory.d.ts +9 -0
  48. package/dist/chat/services/plugin-auth-orchestration.d.ts +1 -4
  49. package/dist/chat/services/subscribed-decision.d.ts +1 -1
  50. package/dist/chat/services/turn-failure-response.d.ts +10 -3
  51. package/dist/chat/services/turn-limit.d.ts +6 -0
  52. package/dist/chat/services/turn-result.d.ts +7 -12
  53. package/dist/chat/services/{turn-reasoning-level.d.ts → turn-router.d.ts} +12 -8
  54. package/dist/chat/services/turn-session-record.d.ts +15 -17
  55. package/dist/chat/slack/adapter.d.ts +2 -2
  56. package/dist/chat/slack/errors.d.ts +2 -0
  57. package/dist/chat/slack/outbound.d.ts +0 -1
  58. package/dist/chat/slack/reply.d.ts +2 -8
  59. package/dist/chat/slack/tools/channel-capabilities.d.ts +2 -2
  60. package/dist/chat/slack/tools/{send-message.d.ts → send-files.d.ts} +27 -19
  61. package/dist/chat/state/conversation.d.ts +4 -4
  62. package/dist/chat/state/turn-id.d.ts +2 -0
  63. package/dist/chat/state/turn-session-keys.d.ts +6 -0
  64. package/dist/chat/state/turn-session.d.ts +11 -9
  65. package/dist/chat/task-execution/state.d.ts +6 -1
  66. package/dist/chat/task-execution/store.d.ts +1 -1
  67. package/dist/chat/tool-support/pi-tool-adapter.d.ts +1 -1
  68. package/dist/chat/tool-support/tool-execution-report.d.ts +1 -0
  69. package/dist/chat/tools/handoff/tool.d.ts +2 -2
  70. package/dist/chat/tools/sandbox/file-uploads.d.ts +28 -6
  71. package/dist/chat/tools/types.d.ts +6 -9
  72. package/dist/chat/tools/web/image-generate.d.ts +4 -6
  73. package/dist/{chunk-QDKRE3LS.js → chunk-4YF7Z6IA.js} +390 -270
  74. package/dist/{chunk-4PDVOHXP.js → chunk-AUUOHQAT.js} +2 -2
  75. package/dist/chunk-B2Z2H66D.js +250 -0
  76. package/dist/{chunk-QVTVCJR4.js → chunk-CQ7KSO2B.js} +75 -45
  77. package/dist/{chunk-MJSJAHBW.js → chunk-DY7TRYB5.js} +1 -1
  78. package/dist/{chunk-NJJFU6CP.js → chunk-EDLNHZH3.js} +883 -898
  79. package/dist/{chunk-O7ILGCMJ.js → chunk-IGHMVDWI.js} +134 -18
  80. package/dist/chunk-MU6HHZEN.js +99 -0
  81. package/dist/{chunk-BCULHGZK.js → chunk-NVOTGWYX.js} +458 -684
  82. package/dist/{chunk-AEWZ7F65.js → chunk-O24R4DCK.js} +2 -179
  83. package/dist/{chunk-CCFROYAV.js → chunk-PUAMO4PI.js} +115 -125
  84. package/dist/{chunk-VDHMFCDY.js → chunk-RMVOAJRL.js} +30 -13
  85. package/dist/{chunk-7G4J3CR7.js → chunk-SPUAJVVH.js} +6 -4
  86. package/dist/{chunk-M3UAVLEN.js → chunk-SS67LUOK.js} +43 -290
  87. package/dist/{chunk-FXFQNHRU.js → chunk-VFUK3X5B.js} +4 -2
  88. package/dist/{chunk-CCTCIISK.js → chunk-VH6KWKG2.js} +2 -2
  89. package/dist/chunk-VIXFG2VC.js +182 -0
  90. package/dist/{chunk-RMZ65YAA.js → chunk-YFQ7CQDE.js} +687 -21
  91. package/dist/{chunk-77LHNQK4.js → chunk-YIPD3L6L.js} +4 -2
  92. package/dist/{chunk-U26ZWZCN.js → chunk-YNP2ATQX.js} +6 -4
  93. package/dist/{chunk-WR7H7VLJ.js → chunk-YT72TOEK.js} +2 -2
  94. package/dist/cli/chat.js +21 -20
  95. package/dist/cli/check.js +4 -3
  96. package/dist/cli/plugins.js +11 -9
  97. package/dist/cli/snapshot-warmup.js +7 -5
  98. package/dist/cli/upgrade.js +2046 -105
  99. package/dist/db/schema/{agent-steps.d.ts → conversation-events.d.ts} +37 -21
  100. package/dist/db/schema.d.ts +28 -159
  101. package/dist/{db-PDMC67T3.js → db-DIGO4TGW.js} +6 -8
  102. package/dist/{runner-6A7MQRUE.js → runner-ACR2HAIC.js} +209 -101
  103. package/dist/{skills-722AXRV5.js → skills-ITNHBARB.js} +4 -3
  104. package/dist/{validation-EUI2UFRM.js → validation-AGQ46ZGV.js} +4 -3
  105. package/migrations/0005_conversation_events.sql +122 -0
  106. package/migrations/README.md +23 -10
  107. package/migrations/meta/0005_snapshot.json +948 -0
  108. package/migrations/meta/_journal.json +7 -0
  109. package/package.json +6 -6
  110. package/dist/api/conversations/activity.d.ts +0 -40
  111. package/dist/api/conversations/detail-projection.d.ts +0 -11
  112. package/dist/api/conversations/detail.query.d.ts +0 -3
  113. package/dist/api/conversations/subagent.d.ts +0 -10
  114. package/dist/api/conversations/transcript.d.ts +0 -28
  115. package/dist/chat/conversations/legacy-advisor-session.d.ts +0 -7
  116. package/dist/chat/conversations/legacy-import.d.ts +0 -38
  117. package/dist/chat/conversations/sql/legacy-history-import.d.ts +0 -55
  118. package/dist/chat/conversations/sql/messages.d.ts +0 -4
  119. package/dist/chat/conversations/visible-compactions.d.ts +0 -11
  120. package/dist/chat/conversations/visible-messages.d.ts +0 -47
  121. package/dist/chat/services/reply-delivery-plan.d.ts +0 -5
  122. package/dist/chat/state/session-log.d.ts +0 -384
  123. package/dist/chunk-RYDF4GOX.js +0 -1072
  124. package/dist/chunk-XIMUQ4IU.js +0 -147
  125. package/dist/db/schema/conversation-messages.d.ts +0 -153
  126. package/dist/handlers/github-webhook/check-suite.d.ts +0 -3
  127. package/dist/handlers/github-webhook/issue-comment.d.ts +0 -3
  128. package/dist/handlers/github-webhook/pull-request-review-comment.d.ts +0 -3
  129. package/dist/handlers/github-webhook/pull-request-review.d.ts +0 -3
  130. package/dist/handlers/github-webhook/pull-request.d.ts +0 -3
  131. package/dist/handlers/github-webhook/resource.d.ts +0 -10
  132. package/dist/handlers/github-webhook.d.ts +0 -15
  133. package/dist/legacy-import-G333RHJZ.js +0 -18
@@ -1,40 +1,73 @@
1
1
  import {
2
2
  JUNIOR_THREAD_STATE_TTL_MS,
3
- buildConversationStatePatch
4
- } from "./chunk-XIMUQ4IU.js";
5
- import {
6
3
  abandonAgentTurnSessionRecord,
7
- addAgentTurnUsage,
4
+ buildConversationStatePatch,
8
5
  getAgentTurnSessionRecord,
6
+ getAgentTurnSessionRecordForResume,
9
7
  upsertAgentTurnSessionRecord
10
- } from "./chunk-QDKRE3LS.js";
8
+ } from "./chunk-4YF7Z6IA.js";
9
+ import {
10
+ addAgentTurnUsage
11
+ } from "./chunk-MU6HHZEN.js";
12
+ import {
13
+ getStateAdapter
14
+ } from "./chunk-B2Z2H66D.js";
11
15
  import {
12
- persistConversationMessages
13
- } from "./chunk-RYDF4GOX.js";
16
+ getConversationEventStore
17
+ } from "./chunk-NVOTGWYX.js";
14
18
  import {
15
19
  sleep
16
20
  } from "./chunk-4ZNGQH7C.js";
17
21
  import {
22
+ botConfig,
23
+ escapeXml,
18
24
  getPiMessageRole,
19
- getStateAdapter,
25
+ resolveGatewayModel,
26
+ standardModelId,
20
27
  trimTrailingAssistantMessages
21
- } from "./chunk-M3UAVLEN.js";
28
+ } from "./chunk-SS67LUOK.js";
22
29
  import {
23
30
  getActiveTraceId,
24
- logException
25
- } from "./chunk-AEWZ7F65.js";
31
+ logException,
32
+ logWarn,
33
+ setSpanAttributes
34
+ } from "./chunk-O24R4DCK.js";
26
35
  import {
27
36
  isRecord,
28
37
  toOptionalString
29
38
  } from "./chunk-OB42YVAE.js";
39
+ import {
40
+ parseSlackMessageTs
41
+ } from "./chunk-7FBGKXPE.js";
30
42
 
31
43
  // src/chat/state/turn-id.ts
32
44
  function buildDeterministicTurnId(messageId) {
33
45
  const sanitized = messageId.replace(/[^a-zA-Z0-9_-]/g, "_");
34
46
  return `turn_${sanitized}`;
35
47
  }
48
+ function buildDeterministicAssistantMessageId(turnId) {
49
+ return `${turnId}:assistant:1`;
50
+ }
36
51
 
37
52
  // src/chat/runtime/turn.ts
53
+ var ConversationTurnBoundaryError = class extends Error {
54
+ eventId;
55
+ failureCode;
56
+ constructor(args) {
57
+ super(
58
+ args.cause instanceof Error ? args.cause.message : String(args.cause),
59
+ {
60
+ cause: args.cause
61
+ }
62
+ );
63
+ this.name = "ConversationTurnBoundaryError";
64
+ this.eventId = args.eventId;
65
+ this.failureCode = args.failureCode;
66
+ }
67
+ };
68
+ function getConversationTurnBoundaryError(error) {
69
+ return error instanceof ConversationTurnBoundaryError ? error : void 0;
70
+ }
38
71
  var CooperativeTurnYieldError = class extends Error {
39
72
  code = "cooperative_turn_yield";
40
73
  constructor(message = "Agent turn yielded at a safe boundary") {
@@ -112,8 +145,18 @@ async function persistWithRetry(persist) {
112
145
  throw lastError;
113
146
  }
114
147
 
148
+ // src/chat/services/turn-limit.ts
149
+ var TurnSliceLimitExceededError = class extends Error {
150
+ constructor(maxSlices) {
151
+ super(`Agent turn exceeded execution limit (${maxSlices} slices)`);
152
+ this.name = "TurnSliceLimitExceededError";
153
+ }
154
+ };
155
+ function buildTurnLimitResponse(eventId) {
156
+ return `I couldn't finish this request because this turn reached its execution limit. Please try again with a smaller or more specific request. Reference: \`event_id=${eventId}\`.`;
157
+ }
158
+
115
159
  // src/chat/services/turn-session-record.ts
116
- var AGENT_CONTINUE_MAX_SLICES = 48;
117
160
  function logSessionRecordError(error, eventName, args, attributes, message) {
118
161
  logException(
119
162
  error,
@@ -155,13 +198,14 @@ function resumableBoundary(messages, fallbackMessages) {
155
198
  return trimTrailingAssistantMessages(fallbackMessages ?? []);
156
199
  }
157
200
  async function loadTurnSessionRecord(ctx) {
158
- const canUseTurnSession = Boolean(ctx.conversationId && ctx.sessionId);
159
- const existingSessionRecord = canUseTurnSession && ctx.conversationId && ctx.sessionId ? await getAgentTurnSessionRecord(ctx.conversationId, ctx.sessionId) : void 0;
201
+ const existingSessionRecord = await getAgentTurnSessionRecordForResume(
202
+ ctx.conversationId,
203
+ ctx.sessionId
204
+ );
160
205
  const hasAwaitingResumeRecord = Boolean(
161
206
  existingSessionRecord && existingSessionRecord.state === "awaiting_resume"
162
207
  );
163
208
  return {
164
- canUseTurnSession,
165
209
  resumedFromSessionRecord: hasAwaitingResumeRecord,
166
210
  currentSliceId: hasAwaitingResumeRecord ? existingSessionRecord.sliceId : 1,
167
211
  existingSessionRecord
@@ -340,7 +384,7 @@ async function persistAuthPauseSessionRecord(args) {
340
384
  }
341
385
  return void 0;
342
386
  }
343
- async function persistTimeoutSessionRecord(args) {
387
+ async function persistContinuationSessionRecord(args) {
344
388
  const nextSliceId = args.currentSliceId + 1;
345
389
  try {
346
390
  const latestSessionRecord = await getAgentTurnSessionRecord(
@@ -362,7 +406,7 @@ async function persistTimeoutSessionRecord(args) {
362
406
  latestSessionRecord?.cumulativeUsage,
363
407
  args.currentUsage
364
408
  );
365
- if (nextSliceId > AGENT_CONTINUE_MAX_SLICES) {
409
+ if (nextSliceId > botConfig.maxSlicesPerTurn) {
366
410
  return await upsertAgentTurnSessionRecord({
367
411
  ...args.channelName ?? latestSessionRecord?.channelName ? {
368
412
  channelName: args.channelName ?? latestSessionRecord?.channelName
@@ -384,9 +428,11 @@ async function persistTimeoutSessionRecord(args) {
384
428
  ...args.reasoningLevel ?? latestSessionRecord?.reasoningLevel ? {
385
429
  reasoningLevel: args.reasoningLevel ?? latestSessionRecord?.reasoningLevel
386
430
  } : {},
387
- resumeReason: "timeout",
431
+ resumeReason: args.resumeReason,
388
432
  resumedFromSliceId: latestSessionRecord?.resumedFromSliceId,
389
- errorMessage: `Agent continuation exceeded slice limit (${AGENT_CONTINUE_MAX_SLICES})`,
433
+ errorMessage: new TurnSliceLimitExceededError(
434
+ botConfig.maxSlicesPerTurn
435
+ ).message,
390
436
  ...args.actor ?? latestSessionRecord?.actor ? { actor: args.actor ?? latestSessionRecord?.actor } : {},
391
437
  ...getActiveTraceId() ?? latestSessionRecord?.traceId ? { traceId: getActiveTraceId() ?? latestSessionRecord?.traceId } : {}
392
438
  });
@@ -408,7 +454,7 @@ async function persistTimeoutSessionRecord(args) {
408
454
  ...args.reasoningLevel ?? latestSessionRecord?.reasoningLevel ? {
409
455
  reasoningLevel: args.reasoningLevel ?? latestSessionRecord?.reasoningLevel
410
456
  } : {},
411
- resumeReason: "timeout",
457
+ resumeReason: args.resumeReason,
412
458
  resumedFromSliceId: args.currentSliceId,
413
459
  errorMessage: args.errorMessage,
414
460
  ...args.actor ?? latestSessionRecord?.actor ? { actor: args.actor ?? latestSessionRecord?.actor } : {},
@@ -484,6 +530,605 @@ async function persistYieldSessionRecord(args) {
484
530
  }
485
531
  }
486
532
 
533
+ // src/chat/services/context-budget.ts
534
+ var COMPACTION_TRIGGER_INPUT_RATIO = 0.75;
535
+ var COMPACTION_OUTPUT_RESERVE_RATIO = 0.25;
536
+ var COMPACTION_TARGET_RATIO = 0.8;
537
+ var FALLBACK_CONTEXT_WINDOW_TOKENS = 4e5;
538
+ var FALLBACK_MAX_OUTPUT_TOKENS = 128e3;
539
+ function positiveInteger(value, fallback) {
540
+ return Number.isFinite(value) && value > 0 ? Math.floor(value) : fallback;
541
+ }
542
+ function estimateTextTokens(text) {
543
+ return Math.ceil(text.length / 4);
544
+ }
545
+ function calculateContextCompactionTriggerTokens(model) {
546
+ const contextWindow = positiveInteger(
547
+ model.contextWindow,
548
+ FALLBACK_CONTEXT_WINDOW_TOKENS
549
+ );
550
+ const maxTokens = positiveInteger(
551
+ model.maxTokens,
552
+ FALLBACK_MAX_OUTPUT_TOKENS
553
+ );
554
+ const outputReserve = Math.min(
555
+ maxTokens,
556
+ Math.floor(contextWindow * COMPACTION_OUTPUT_RESERVE_RATIO)
557
+ );
558
+ const usableInputTokens = Math.max(1, contextWindow - outputReserve);
559
+ return Math.max(
560
+ 1,
561
+ Math.floor(usableInputTokens * COMPACTION_TRIGGER_INPUT_RATIO)
562
+ );
563
+ }
564
+ function calculateContextCompactionTargetTokens(triggerTokens) {
565
+ return Math.max(1, Math.floor(triggerTokens * COMPACTION_TARGET_RATIO));
566
+ }
567
+ function getAgentContextCompactionTriggerTokens() {
568
+ const model = resolveGatewayModel(standardModelId(botConfig));
569
+ return calculateContextCompactionTriggerTokens({
570
+ contextWindow: botConfig.modelContextWindowTokens ?? model.contextWindow,
571
+ maxTokens: model.maxTokens
572
+ });
573
+ }
574
+ function getConversationContextCompactionTriggerTokens() {
575
+ const model = resolveGatewayModel(botConfig.fastModelId);
576
+ return calculateContextCompactionTriggerTokens({
577
+ contextWindow: model.contextWindow,
578
+ maxTokens: model.maxTokens
579
+ });
580
+ }
581
+
582
+ // src/chat/services/conversation-memory.ts
583
+ var CONTEXT_MIN_LIVE_MESSAGES = 12;
584
+ var CONTEXT_COMPACTION_BATCH_SIZE = 24;
585
+ var CONTEXT_MAX_COMPACTIONS = 16;
586
+ var CONTEXT_MAX_MESSAGE_CHARS = 3200;
587
+ var SLACK_USER_ID_DISPLAY_PATTERN = /^[UW][A-Z0-9]{5,}$/;
588
+ function generateConversationId(prefix) {
589
+ return `${prefix}_${Date.now()}_${Math.random().toString(36).slice(2, 10)}`;
590
+ }
591
+ function normalizeConversationText(text) {
592
+ return text.trim().replace(/\s+/g, " ").slice(0, CONTEXT_MAX_MESSAGE_CHARS);
593
+ }
594
+ function buildImageContextSuffix(message, conversation) {
595
+ const byFileId = conversation?.vision.byFileId;
596
+ const imageFileIds = message.meta?.imageFileIds ?? [];
597
+ if (!byFileId || imageFileIds.length === 0) {
598
+ return "";
599
+ }
600
+ const summaries = imageFileIds.map((fileId) => byFileId[fileId]?.summary?.trim()).filter((summary) => Boolean(summary));
601
+ if (summaries.length === 0) {
602
+ return "";
603
+ }
604
+ return ` [image context: ${summaries.join(" | ")}]`;
605
+ }
606
+ function renderConversationMessageLine(message, conversation) {
607
+ const displayName = conversationAuthorDisplayName(message);
608
+ const markers = [];
609
+ if (message.meta?.replied === false) {
610
+ markers.push(
611
+ `assistant skipped: ${message.meta?.skippedReason ?? "no-reply route"}`
612
+ );
613
+ }
614
+ if (message.meta?.explicitMention) {
615
+ markers.push("explicit mention");
616
+ }
617
+ const markerSuffix = markers.length > 0 ? ` (${markers.join("; ")})` : "";
618
+ const imageContext = buildImageContextSuffix(message, conversation);
619
+ return `[${message.role}] ${displayName}: ${message.text}${imageContext}${markerSuffix}`;
620
+ }
621
+ function conversationAuthorDisplayName(message) {
622
+ const author = message.author;
623
+ const fullName = authorDisplayField(author?.fullName, author?.userId);
624
+ const userName = authorDisplayField(author?.userName, author?.userId);
625
+ return fullName ?? userName ?? (message.role === "assistant" ? botConfig.userName : message.role);
626
+ }
627
+ function authorDisplayField(value, userId) {
628
+ if (!value || value === userId || SLACK_USER_ID_DISPLAY_PATTERN.test(value)) {
629
+ return void 0;
630
+ }
631
+ return value;
632
+ }
633
+ function updateConversationStats(conversation) {
634
+ const contextText = buildConversationContext(conversation);
635
+ conversation.stats.estimatedContextTokens = estimateTextTokens(
636
+ contextText ?? ""
637
+ );
638
+ conversation.stats.totalMessageCount = conversation.messages.length;
639
+ conversation.stats.updatedAtMs = Date.now();
640
+ }
641
+ function upsertConversationMessage(conversation, message) {
642
+ const existingIndex = conversation.messages.findIndex(
643
+ (entry) => entry.id === message.id
644
+ );
645
+ if (existingIndex >= 0) {
646
+ conversation.messages[existingIndex] = {
647
+ ...conversation.messages[existingIndex],
648
+ ...message,
649
+ meta: {
650
+ ...conversation.messages[existingIndex]?.meta,
651
+ ...message.meta
652
+ }
653
+ };
654
+ updateConversationStats(conversation);
655
+ return message.id;
656
+ }
657
+ conversation.messages.push(message);
658
+ updateConversationStats(conversation);
659
+ return message.id;
660
+ }
661
+ function recordDeliveredAssistantMessage(args) {
662
+ const prefix = `${args.sessionId}:assistant:`;
663
+ const ordinal = args.conversation.messages.filter(
664
+ (message) => message.id.startsWith(prefix)
665
+ ).length + 1;
666
+ const messageId = `${prefix}${ordinal}`;
667
+ markConversationMessage(args.conversation, args.userMessageId, {
668
+ replied: true,
669
+ skippedReason: void 0
670
+ });
671
+ upsertConversationMessage(args.conversation, {
672
+ id: messageId,
673
+ role: "assistant",
674
+ text: normalizeConversationText(args.text),
675
+ createdAtMs: Date.now(),
676
+ author: {
677
+ userName: botConfig.userName,
678
+ isBot: true
679
+ },
680
+ meta: {
681
+ replied: true
682
+ }
683
+ });
684
+ return messageId;
685
+ }
686
+ function turnHasReply(conversation, turnId) {
687
+ const assistantPrefix = `${turnId}:assistant:`;
688
+ return conversation.messages.some(
689
+ (message) => message.role === "assistant" && message.id.startsWith(assistantPrefix)
690
+ );
691
+ }
692
+ function markConversationMessage(conversation, messageId, patch) {
693
+ if (!messageId) return;
694
+ const messageIndex = conversation.messages.findIndex(
695
+ (entry) => entry.id === messageId
696
+ );
697
+ if (messageIndex < 0) return;
698
+ const current = conversation.messages[messageIndex];
699
+ conversation.messages[messageIndex] = {
700
+ ...current,
701
+ meta: {
702
+ ...current.meta ?? {},
703
+ ...patch
704
+ }
705
+ };
706
+ updateConversationStats(conversation);
707
+ }
708
+ function buildConversationContext(conversation, options = {}) {
709
+ const messages = conversation.messages.filter(
710
+ (entry) => entry.id !== options.excludeMessageId
711
+ );
712
+ if (messages.length === 0 && conversation.compactions.length === 0) {
713
+ return void 0;
714
+ }
715
+ const lines = [];
716
+ if (conversation.compactions.length > 0) {
717
+ lines.push("<thread-compactions>");
718
+ for (const [index, compaction] of conversation.compactions.entries()) {
719
+ lines.push(
720
+ ` <compaction index="${index + 1}" covered_messages="${compaction.coveredMessageCount}" created_at="${new Date(compaction.createdAtMs).toISOString()}">`,
721
+ compaction.summary,
722
+ " </compaction>"
723
+ );
724
+ }
725
+ lines.push("</thread-compactions>");
726
+ }
727
+ if (messages.length > 0) {
728
+ if (lines.length > 0) {
729
+ lines.push("");
730
+ }
731
+ lines.push("<thread-transcript>");
732
+ for (const [index, message] of messages.entries()) {
733
+ const author = escapeXml(conversationAuthorDisplayName(message));
734
+ const actorIdAttr = message.author?.userId ? ` actor_id="${escapeXml(message.author.userId)}"` : "";
735
+ const ts = new Date(message.createdAtMs).toISOString();
736
+ const slackTsAttr = message.meta?.slackTs ? ` slack_ts="${escapeXml(message.meta.slackTs)}"` : "";
737
+ lines.push(
738
+ ` <message index="${index + 1}" ts="${ts}" role="${message.role}" author="${author}"${actorIdAttr}${slackTsAttr}>`,
739
+ renderConversationMessageLine(message, conversation),
740
+ " </message>"
741
+ );
742
+ }
743
+ lines.push("</thread-transcript>");
744
+ }
745
+ return lines.join("\n");
746
+ }
747
+ function pruneCompactions(compactions) {
748
+ if (compactions.length <= CONTEXT_MAX_COMPACTIONS) {
749
+ return compactions;
750
+ }
751
+ const overflowCount = compactions.length - CONTEXT_MAX_COMPACTIONS + 1;
752
+ const merged = compactions.slice(0, overflowCount);
753
+ const mergedSummary = merged.map((entry) => entry.summary).join("\n").slice(0, 3500);
754
+ const coveredMessageCount = merged.reduce(
755
+ (count, entry) => count + entry.coveredMessageCount,
756
+ 0
757
+ );
758
+ const compacted = {
759
+ id: generateConversationId("compaction"),
760
+ createdAtMs: Date.now(),
761
+ summary: mergedSummary,
762
+ coveredMessageCount
763
+ };
764
+ return [compacted, ...compactions.slice(overflowCount)];
765
+ }
766
+ async function summarizeConversationChunk(messages, conversation, context, deps) {
767
+ const transcript = messages.map((message) => renderConversationMessageLine(message, conversation)).join("\n");
768
+ try {
769
+ const result = await deps.completeText({
770
+ modelId: botConfig.fastModelId,
771
+ temperature: 0,
772
+ messages: [
773
+ {
774
+ role: "user",
775
+ content: [
776
+ "Summarize the following older Slack thread transcript segment for future assistant turns.",
777
+ "Keep the summary factual and concise. Do not invent details.",
778
+ "",
779
+ "Output exactly three XML sections in this order:",
780
+ "<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>",
781
+ "<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>",
782
+ "<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>",
783
+ "",
784
+ "Do not output any text outside the three sections.",
785
+ "",
786
+ transcript
787
+ ].join("\n"),
788
+ timestamp: Date.now()
789
+ }
790
+ ],
791
+ metadata: {
792
+ modelId: botConfig.fastModelId,
793
+ threadId: context.threadId ?? "",
794
+ channelId: context.channelId ?? "",
795
+ actorId: context.actorId ?? "",
796
+ runId: context.runId ?? ""
797
+ }
798
+ });
799
+ const summary = result.text.trim();
800
+ if (summary.length > 0) {
801
+ return summary.slice(0, 3500);
802
+ }
803
+ } catch (error) {
804
+ logWarn(
805
+ "conversation_compaction_summary_failed",
806
+ {
807
+ slackThreadId: context.threadId,
808
+ slackUserId: context.actorId,
809
+ slackChannelId: context.channelId,
810
+ runId: context.runId,
811
+ assistantUserName: botConfig.userName,
812
+ modelId: botConfig.fastModelId
813
+ },
814
+ {
815
+ "exception.message": error instanceof Error ? error.message : String(error),
816
+ "app.compaction_messages_covered": messages.length
817
+ },
818
+ "Compaction summarization failed; using fallback summary"
819
+ );
820
+ }
821
+ return transcript.slice(0, 2800);
822
+ }
823
+ async function generateThreadTitleWithDeps(sourceText, deps) {
824
+ const result = await deps.completeText({
825
+ modelId: botConfig.fastModelId,
826
+ temperature: 0,
827
+ messages: [
828
+ {
829
+ role: "user",
830
+ content: [
831
+ "Generate a concise 5-8 word Slack conversation title from the first user message below.",
832
+ "Capture the user's main request.",
833
+ "Reply with ONLY the title, with no quotes or trailing punctuation.",
834
+ "",
835
+ `First user message: ${sourceText.slice(0, 500)}`
836
+ ].join("\n"),
837
+ timestamp: Date.now()
838
+ }
839
+ ],
840
+ metadata: {
841
+ modelId: botConfig.fastModelId
842
+ }
843
+ });
844
+ return result.text.trim().slice(0, 60);
845
+ }
846
+ function getThreadTitleSourceMessage(conversation) {
847
+ let firstMessage;
848
+ for (const message of conversation.messages) {
849
+ if (!isHumanConversationMessage(message)) {
850
+ continue;
851
+ }
852
+ if (!firstMessage) {
853
+ firstMessage = message;
854
+ continue;
855
+ }
856
+ if (message.createdAtMs < firstMessage.createdAtMs) {
857
+ firstMessage = message;
858
+ continue;
859
+ }
860
+ if (message.createdAtMs === firstMessage.createdAtMs && message.id < firstMessage.id) {
861
+ firstMessage = message;
862
+ }
863
+ }
864
+ return firstMessage;
865
+ }
866
+ async function compactConversationIfNeededWithDeps(conversation, context, deps) {
867
+ updateConversationStats(conversation);
868
+ let estimatedTokens = conversation.stats.estimatedContextTokens;
869
+ setSpanAttributes({
870
+ "app.context_tokens_estimated": estimatedTokens
871
+ });
872
+ const triggerTokens = getConversationContextCompactionTriggerTokens();
873
+ const targetTokens = calculateContextCompactionTargetTokens(triggerTokens);
874
+ while (estimatedTokens > triggerTokens && conversation.messages.length > CONTEXT_MIN_LIVE_MESSAGES) {
875
+ const compactCount = Math.min(
876
+ CONTEXT_COMPACTION_BATCH_SIZE,
877
+ conversation.messages.length - CONTEXT_MIN_LIVE_MESSAGES
878
+ );
879
+ if (compactCount <= 0) {
880
+ break;
881
+ }
882
+ const compactedChunk = conversation.messages.slice(0, compactCount);
883
+ const summary = await summarizeConversationChunk(
884
+ compactedChunk,
885
+ conversation,
886
+ context,
887
+ deps
888
+ );
889
+ conversation.compactions.push({
890
+ id: generateConversationId("compaction"),
891
+ createdAtMs: Date.now(),
892
+ summary,
893
+ coveredMessageCount: compactedChunk.length
894
+ });
895
+ conversation.compactions = pruneCompactions(conversation.compactions);
896
+ conversation.messages = conversation.messages.slice(compactCount);
897
+ conversation.stats.compactedMessageCount += compactCount;
898
+ updateConversationStats(conversation);
899
+ estimatedTokens = conversation.stats.estimatedContextTokens;
900
+ setSpanAttributes({
901
+ "app.compaction_messages_covered": compactCount,
902
+ "app.compaction.trigger_tokens": triggerTokens,
903
+ "app.compaction.target_tokens": targetTokens,
904
+ "app.context_tokens_estimated": estimatedTokens
905
+ });
906
+ if (estimatedTokens <= targetTokens) {
907
+ break;
908
+ }
909
+ }
910
+ }
911
+ function createConversationMemoryService(deps) {
912
+ return {
913
+ compactConversationIfNeeded: async (conversation, context) => await compactConversationIfNeededWithDeps(conversation, context, deps),
914
+ generateThreadTitle: async (sourceText) => await generateThreadTitleWithDeps(sourceText, deps)
915
+ };
916
+ }
917
+ function isHumanConversationMessage(message) {
918
+ return message.role === "user" && message.author?.isBot !== true;
919
+ }
920
+ function getConversationMessageSlackTs(message) {
921
+ return parseSlackMessageTs(message.meta?.slackTs);
922
+ }
923
+
924
+ // src/chat/conversations/messages.ts
925
+ import { createHash } from "crypto";
926
+ import { isDeepStrictEqual as isDeepStrictEqual2 } from "util";
927
+
928
+ // src/chat/conversations/message-summaries.ts
929
+ import { isDeepStrictEqual } from "util";
930
+ function projectConversationMessageSummaries(events) {
931
+ for (let index = events.length - 1; index >= 0; index -= 1) {
932
+ const data = events[index]?.data;
933
+ if (data?.type === "messages_summarized") {
934
+ return data.compactions;
935
+ }
936
+ }
937
+ return [];
938
+ }
939
+ async function persistConversationMessageSummaries(args) {
940
+ const eventStore = getConversationEventStore();
941
+ const history = await eventStore.loadMessageHistory(args.conversationId);
942
+ const existing = history.compaction ? projectConversationMessageSummaries([history.compaction]) : [];
943
+ if (isDeepStrictEqual(existing, args.conversation.compactions)) {
944
+ return;
945
+ }
946
+ const liveMessageIds = new Set(
947
+ args.conversation.messages.map((message) => message.id)
948
+ );
949
+ const historyFromSeq = history.events.find(
950
+ (event) => event.data.type === "message" && liveMessageIds.has(event.data.messageId)
951
+ )?.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
+ );
956
+ await eventStore.append(args.conversationId, [
957
+ {
958
+ data: {
959
+ type: "messages_summarized",
960
+ historyFromSeq: nextHistoryFromSeq,
961
+ compactions: args.conversation.compactions
962
+ },
963
+ createdAtMs: args.conversation.compactions.at(-1)?.createdAtMs ?? Date.now()
964
+ }
965
+ ]);
966
+ }
967
+
968
+ // src/chat/conversations/message-projection.ts
969
+ function isMessageEvent(event) {
970
+ return event.data.type === "message" || event.data.type === "message_updated" || event.data.type === "message_handled";
971
+ }
972
+ function splitMeta(meta) {
973
+ const rest = { ...meta ?? {} };
974
+ const author = rest.author;
975
+ delete rest.author;
976
+ return {
977
+ ...author ? { author } : {},
978
+ ...Object.keys(rest).length > 0 ? { meta: rest } : {}
979
+ };
980
+ }
981
+ function missingBaselineError(event) {
982
+ return new Error(
983
+ `Message event ${event.data.type} at seq ${event.seq} references ${event.data.messageId} before message`
984
+ );
985
+ }
986
+ function projectConversationMessages(events, options = {}) {
987
+ const byId = /* @__PURE__ */ new Map();
988
+ for (const event of events) {
989
+ if (!isMessageEvent(event)) continue;
990
+ const data = event.data;
991
+ const current = byId.get(data.messageId);
992
+ if (data.type === "message" || data.type === "message_updated") {
993
+ const message = {
994
+ id: data.messageId,
995
+ role: data.role,
996
+ text: data.text,
997
+ createdAtMs: event.createdAtMs,
998
+ ...splitMeta(data.meta)
999
+ };
1000
+ if (data.type === "message" && current)
1001
+ throw new Error(
1002
+ `Duplicate message event for ${data.messageId} at seq ${event.seq}`
1003
+ );
1004
+ if (data.type === "message_updated" && !current) {
1005
+ if ((options.historyFromSeq ?? 0) > 0) continue;
1006
+ throw missingBaselineError(event);
1007
+ }
1008
+ byId.set(data.messageId, {
1009
+ message: {
1010
+ ...message,
1011
+ createdAtMs: current?.message.createdAtMs ?? message.createdAtMs
1012
+ },
1013
+ ...current?.repliedAtMs === void 0 ? {} : { repliedAtMs: current.repliedAtMs }
1014
+ });
1015
+ continue;
1016
+ }
1017
+ if (!current) {
1018
+ if ((options.historyFromSeq ?? 0) > 0) continue;
1019
+ throw missingBaselineError(event);
1020
+ }
1021
+ current.repliedAtMs ??= event.createdAtMs;
1022
+ }
1023
+ return [...byId.values()].map(({ message, repliedAtMs }) => {
1024
+ if (repliedAtMs === void 0) return message;
1025
+ return {
1026
+ ...message,
1027
+ meta: { ...message.meta ?? {}, replied: true }
1028
+ };
1029
+ });
1030
+ }
1031
+
1032
+ // src/chat/conversations/messages.ts
1033
+ function toStoredConversationMessage(message) {
1034
+ const meta = {};
1035
+ if (message.author) meta.author = message.author;
1036
+ const { replied, ...restMeta } = message.meta ?? {};
1037
+ Object.assign(meta, restMeta);
1038
+ if (replied === false) meta.replied = false;
1039
+ return {
1040
+ messageId: message.id,
1041
+ role: message.role,
1042
+ text: message.text,
1043
+ ...Object.keys(meta).length > 0 ? { meta } : {},
1044
+ createdAtMs: message.createdAtMs
1045
+ };
1046
+ }
1047
+ function canonicalJson(value) {
1048
+ if (Array.isArray(value)) return value.map(canonicalJson);
1049
+ if (!value || typeof value !== "object") return value;
1050
+ return Object.fromEntries(
1051
+ Object.entries(value).filter(([, item]) => item !== void 0).sort(([left], [right]) => left.localeCompare(right)).map(([key, item]) => [key, canonicalJson(item)])
1052
+ );
1053
+ }
1054
+ function messageStateKey(message) {
1055
+ return createHash("sha256").update(JSON.stringify(canonicalJson(message))).digest("hex").slice(0, 32);
1056
+ }
1057
+ async function hydrateConversationMessages(args) {
1058
+ if (!args.conversationId) {
1059
+ args.conversation.messages = [];
1060
+ return;
1061
+ }
1062
+ const history = await getConversationEventStore().loadMessageHistory(
1063
+ args.conversationId
1064
+ );
1065
+ 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
+ });
1070
+ updateConversationStats(args.conversation);
1071
+ }
1072
+ async function persistConversationMessages(args) {
1073
+ if (!args.conversationId || args.conversation.messages.length === 0) return;
1074
+ const history = await getConversationEventStore().loadMessageHistory(
1075
+ args.conversationId
1076
+ );
1077
+ const existingMessages = new Map(
1078
+ projectConversationMessages(history.events).map((message) => [
1079
+ message.id,
1080
+ message
1081
+ ])
1082
+ );
1083
+ const handledAtMs = args.repliedAtMs ?? Date.now();
1084
+ const events = args.conversation.messages.flatMap((message) => {
1085
+ const stored = toStoredConversationMessage(message);
1086
+ const existing = existingMessages.get(stored.messageId);
1087
+ const changed = existing !== void 0 && !isDeepStrictEqual2(toStoredConversationMessage(existing), stored);
1088
+ const messageEvent = existing ? changed ? [
1089
+ {
1090
+ idempotencyKey: `message:${stored.messageId}:update:${messageStateKey(stored)}`,
1091
+ createdAtMs: handledAtMs,
1092
+ data: {
1093
+ type: "message_updated",
1094
+ messageId: stored.messageId,
1095
+ role: stored.role,
1096
+ text: stored.text,
1097
+ ...stored.authorIdentityId ? { authorIdentityId: stored.authorIdentityId } : {},
1098
+ ...stored.meta ? { meta: stored.meta } : {}
1099
+ }
1100
+ }
1101
+ ] : [] : [
1102
+ {
1103
+ idempotencyKey: `message:${stored.messageId}`,
1104
+ createdAtMs: stored.createdAtMs,
1105
+ data: {
1106
+ type: "message",
1107
+ messageId: stored.messageId,
1108
+ role: stored.role,
1109
+ text: stored.text,
1110
+ ...stored.authorIdentityId ? { authorIdentityId: stored.authorIdentityId } : {},
1111
+ ...stored.meta ? { meta: stored.meta } : {}
1112
+ }
1113
+ }
1114
+ ];
1115
+ return [
1116
+ ...messageEvent,
1117
+ ...message.meta?.replied === true ? [
1118
+ {
1119
+ idempotencyKey: `message:${stored.messageId}:handled`,
1120
+ createdAtMs: handledAtMs,
1121
+ data: {
1122
+ type: "message_handled",
1123
+ messageId: stored.messageId
1124
+ }
1125
+ }
1126
+ ] : []
1127
+ ];
1128
+ });
1129
+ await getConversationEventStore().append(args.conversationId, events);
1130
+ }
1131
+
487
1132
  // src/chat/state/artifacts.ts
488
1133
  function coerceThreadArtifactsState(value) {
489
1134
  if (!value || typeof value !== "object") {
@@ -927,6 +1572,9 @@ function isPendingAuthLatestRequest(conversation, pendingAuth) {
927
1572
 
928
1573
  export {
929
1574
  buildDeterministicTurnId,
1575
+ buildDeterministicAssistantMessageId,
1576
+ ConversationTurnBoundaryError,
1577
+ getConversationTurnBoundaryError,
930
1578
  CooperativeTurnYieldError,
931
1579
  isCooperativeTurnYieldError,
932
1580
  TurnInputCommitLostError,
@@ -938,12 +1586,14 @@ export {
938
1586
  markTurnCompleted,
939
1587
  markTurnFailed,
940
1588
  persistWithRetry,
1589
+ TurnSliceLimitExceededError,
1590
+ buildTurnLimitResponse,
941
1591
  loadTurnSessionRecord,
942
1592
  persistRunningSessionRecord,
943
1593
  persistCompletedSessionRecord,
944
1594
  completeDeliveredTurn,
945
1595
  persistAuthPauseSessionRecord,
946
- persistTimeoutSessionRecord,
1596
+ persistContinuationSessionRecord,
947
1597
  persistYieldSessionRecord,
948
1598
  getInterruptionMarker,
949
1599
  canReusePendingAuthLink,
@@ -951,6 +1601,22 @@ export {
951
1601
  clearPendingAuth,
952
1602
  abandonReplacedPendingAuth,
953
1603
  isPendingAuthLatestRequest,
1604
+ persistConversationMessageSummaries,
1605
+ estimateTextTokens,
1606
+ getAgentContextCompactionTriggerTokens,
1607
+ normalizeConversationText,
1608
+ updateConversationStats,
1609
+ upsertConversationMessage,
1610
+ recordDeliveredAssistantMessage,
1611
+ turnHasReply,
1612
+ markConversationMessage,
1613
+ buildConversationContext,
1614
+ getThreadTitleSourceMessage,
1615
+ createConversationMemoryService,
1616
+ isHumanConversationMessage,
1617
+ getConversationMessageSlackTs,
1618
+ hydrateConversationMessages,
1619
+ persistConversationMessages,
954
1620
  coerceThreadArtifactsState,
955
1621
  mergeArtifactsState,
956
1622
  getPersistedSandboxState,