@sentry/junior 0.106.0 → 0.107.1

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 (132) hide show
  1. package/dist/{agent-hooks-KR5BCWOK.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 +3586 -3614
  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 +2 -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-result.d.ts +7 -12
  52. package/dist/chat/services/{turn-reasoning-level.d.ts → turn-router.d.ts} +12 -8
  53. package/dist/chat/services/turn-session-record.d.ts +15 -16
  54. package/dist/chat/slack/adapter.d.ts +2 -2
  55. package/dist/chat/slack/errors.d.ts +2 -0
  56. package/dist/chat/slack/outbound.d.ts +0 -1
  57. package/dist/chat/slack/reply.d.ts +2 -8
  58. package/dist/chat/slack/tools/channel-capabilities.d.ts +2 -2
  59. package/dist/chat/slack/tools/{send-message.d.ts → send-files.d.ts} +27 -19
  60. package/dist/chat/state/conversation.d.ts +4 -4
  61. package/dist/chat/state/turn-id.d.ts +2 -0
  62. package/dist/chat/state/turn-session-keys.d.ts +6 -0
  63. package/dist/chat/state/turn-session.d.ts +11 -9
  64. package/dist/chat/task-execution/state.d.ts +6 -1
  65. package/dist/chat/task-execution/store.d.ts +1 -1
  66. package/dist/chat/tool-support/pi-tool-adapter.d.ts +1 -1
  67. package/dist/chat/tool-support/tool-execution-report.d.ts +1 -0
  68. package/dist/chat/tools/handoff/tool.d.ts +2 -2
  69. package/dist/chat/tools/sandbox/file-uploads.d.ts +28 -6
  70. package/dist/chat/tools/types.d.ts +6 -9
  71. package/dist/chat/tools/web/image-generate.d.ts +4 -6
  72. package/dist/{chunk-DDRQ4U7I.js → chunk-4YF7Z6IA.js} +390 -270
  73. package/dist/{chunk-AZ5OMSN5.js → chunk-AUUOHQAT.js} +2 -2
  74. package/dist/chunk-B2Z2H66D.js +250 -0
  75. package/dist/{chunk-4W4O4DCV.js → chunk-CQ7KSO2B.js} +75 -45
  76. package/dist/{chunk-MJSJAHBW.js → chunk-DY7TRYB5.js} +1 -1
  77. package/dist/{chunk-GPPUTY5M.js → chunk-EDLNHZH3.js} +861 -891
  78. package/dist/{chunk-R5BFYK2O.js → chunk-IGHMVDWI.js} +134 -18
  79. package/dist/chunk-MU6HHZEN.js +99 -0
  80. package/dist/{chunk-XTKJPSMF.js → chunk-NVOTGWYX.js} +458 -684
  81. package/dist/{chunk-AEWZ7F65.js → chunk-O24R4DCK.js} +2 -179
  82. package/dist/{chunk-CCFROYAV.js → chunk-PUAMO4PI.js} +115 -125
  83. package/dist/{chunk-ZMRLBBD5.js → chunk-RMVOAJRL.js} +30 -13
  84. package/dist/{chunk-3NYUCBLE.js → chunk-SPUAJVVH.js} +6 -4
  85. package/dist/{chunk-4UYIRWDW.js → chunk-SS67LUOK.js} +41 -290
  86. package/dist/{chunk-FXFQNHRU.js → chunk-VFUK3X5B.js} +4 -2
  87. package/dist/{chunk-3MPPVPGZ.js → chunk-VH6KWKG2.js} +2 -2
  88. package/dist/chunk-VIXFG2VC.js +182 -0
  89. package/dist/{chunk-BKM4UXUD.js → chunk-YFQ7CQDE.js} +669 -18
  90. package/dist/{chunk-77LHNQK4.js → chunk-YIPD3L6L.js} +4 -2
  91. package/dist/{chunk-3OUSJPR7.js → chunk-YNP2ATQX.js} +6 -4
  92. package/dist/{chunk-WR7H7VLJ.js → chunk-YT72TOEK.js} +2 -2
  93. package/dist/cli/chat.js +21 -20
  94. package/dist/cli/check.js +4 -3
  95. package/dist/cli/plugins.js +11 -9
  96. package/dist/cli/snapshot-warmup.js +7 -5
  97. package/dist/cli/upgrade.js +2041 -105
  98. package/dist/db/schema/{agent-steps.d.ts → conversation-events.d.ts} +37 -21
  99. package/dist/db/schema.d.ts +28 -159
  100. package/dist/{db-S33LFWG2.js → db-DIGO4TGW.js} +6 -8
  101. package/dist/{runner-HA5XAL6K.js → runner-ACR2HAIC.js} +209 -101
  102. package/dist/{skills-722AXRV5.js → skills-ITNHBARB.js} +4 -3
  103. package/dist/{validation-EUI2UFRM.js → validation-AGQ46ZGV.js} +4 -3
  104. package/migrations/0005_conversation_events.sql +122 -0
  105. package/migrations/README.md +23 -10
  106. package/migrations/meta/0005_snapshot.json +948 -0
  107. package/migrations/meta/_journal.json +7 -0
  108. package/package.json +5 -5
  109. package/dist/api/conversations/activity.d.ts +0 -40
  110. package/dist/api/conversations/detail-projection.d.ts +0 -11
  111. package/dist/api/conversations/detail.query.d.ts +0 -3
  112. package/dist/api/conversations/subagent.d.ts +0 -10
  113. package/dist/api/conversations/transcript.d.ts +0 -28
  114. package/dist/chat/conversations/legacy-advisor-session.d.ts +0 -7
  115. package/dist/chat/conversations/legacy-import.d.ts +0 -38
  116. package/dist/chat/conversations/sql/legacy-history-import.d.ts +0 -55
  117. package/dist/chat/conversations/sql/messages.d.ts +0 -4
  118. package/dist/chat/conversations/visible-compactions.d.ts +0 -11
  119. package/dist/chat/conversations/visible-messages.d.ts +0 -47
  120. package/dist/chat/services/reply-delivery-plan.d.ts +0 -5
  121. package/dist/chat/state/session-log.d.ts +0 -384
  122. package/dist/chunk-P4P7O5WG.js +0 -1072
  123. package/dist/chunk-XIMUQ4IU.js +0 -147
  124. package/dist/db/schema/conversation-messages.d.ts +0 -153
  125. package/dist/handlers/github-webhook/check-suite.d.ts +0 -3
  126. package/dist/handlers/github-webhook/issue-comment.d.ts +0 -3
  127. package/dist/handlers/github-webhook/pull-request-review-comment.d.ts +0 -3
  128. package/dist/handlers/github-webhook/pull-request-review.d.ts +0 -3
  129. package/dist/handlers/github-webhook/pull-request.d.ts +0 -3
  130. package/dist/handlers/github-webhook/resource.d.ts +0 -10
  131. package/dist/handlers/github-webhook.d.ts +0 -15
  132. package/dist/legacy-import-TMJISRKO.js +0 -18
@@ -1,41 +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-DDRQ4U7I.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-P4P7O5WG.js";
16
+ getConversationEventStore
17
+ } from "./chunk-NVOTGWYX.js";
14
18
  import {
15
19
  sleep
16
20
  } from "./chunk-4ZNGQH7C.js";
17
21
  import {
18
22
  botConfig,
23
+ escapeXml,
19
24
  getPiMessageRole,
20
- getStateAdapter,
25
+ resolveGatewayModel,
26
+ standardModelId,
21
27
  trimTrailingAssistantMessages
22
- } from "./chunk-4UYIRWDW.js";
28
+ } from "./chunk-SS67LUOK.js";
23
29
  import {
24
30
  getActiveTraceId,
25
- logException
26
- } from "./chunk-AEWZ7F65.js";
31
+ logException,
32
+ logWarn,
33
+ setSpanAttributes
34
+ } from "./chunk-O24R4DCK.js";
27
35
  import {
28
36
  isRecord,
29
37
  toOptionalString
30
38
  } from "./chunk-OB42YVAE.js";
39
+ import {
40
+ parseSlackMessageTs
41
+ } from "./chunk-7FBGKXPE.js";
31
42
 
32
43
  // src/chat/state/turn-id.ts
33
44
  function buildDeterministicTurnId(messageId) {
34
45
  const sanitized = messageId.replace(/[^a-zA-Z0-9_-]/g, "_");
35
46
  return `turn_${sanitized}`;
36
47
  }
48
+ function buildDeterministicAssistantMessageId(turnId) {
49
+ return `${turnId}:assistant:1`;
50
+ }
37
51
 
38
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
+ }
39
71
  var CooperativeTurnYieldError = class extends Error {
40
72
  code = "cooperative_turn_yield";
41
73
  constructor(message = "Agent turn yielded at a safe boundary") {
@@ -166,13 +198,14 @@ function resumableBoundary(messages, fallbackMessages) {
166
198
  return trimTrailingAssistantMessages(fallbackMessages ?? []);
167
199
  }
168
200
  async function loadTurnSessionRecord(ctx) {
169
- const canUseTurnSession = Boolean(ctx.conversationId && ctx.sessionId);
170
- 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
+ );
171
205
  const hasAwaitingResumeRecord = Boolean(
172
206
  existingSessionRecord && existingSessionRecord.state === "awaiting_resume"
173
207
  );
174
208
  return {
175
- canUseTurnSession,
176
209
  resumedFromSessionRecord: hasAwaitingResumeRecord,
177
210
  currentSliceId: hasAwaitingResumeRecord ? existingSessionRecord.sliceId : 1,
178
211
  existingSessionRecord
@@ -351,7 +384,7 @@ async function persistAuthPauseSessionRecord(args) {
351
384
  }
352
385
  return void 0;
353
386
  }
354
- async function persistTimeoutSessionRecord(args) {
387
+ async function persistContinuationSessionRecord(args) {
355
388
  const nextSliceId = args.currentSliceId + 1;
356
389
  try {
357
390
  const latestSessionRecord = await getAgentTurnSessionRecord(
@@ -395,7 +428,7 @@ async function persistTimeoutSessionRecord(args) {
395
428
  ...args.reasoningLevel ?? latestSessionRecord?.reasoningLevel ? {
396
429
  reasoningLevel: args.reasoningLevel ?? latestSessionRecord?.reasoningLevel
397
430
  } : {},
398
- resumeReason: "timeout",
431
+ resumeReason: args.resumeReason,
399
432
  resumedFromSliceId: latestSessionRecord?.resumedFromSliceId,
400
433
  errorMessage: new TurnSliceLimitExceededError(
401
434
  botConfig.maxSlicesPerTurn
@@ -421,7 +454,7 @@ async function persistTimeoutSessionRecord(args) {
421
454
  ...args.reasoningLevel ?? latestSessionRecord?.reasoningLevel ? {
422
455
  reasoningLevel: args.reasoningLevel ?? latestSessionRecord?.reasoningLevel
423
456
  } : {},
424
- resumeReason: "timeout",
457
+ resumeReason: args.resumeReason,
425
458
  resumedFromSliceId: args.currentSliceId,
426
459
  errorMessage: args.errorMessage,
427
460
  ...args.actor ?? latestSessionRecord?.actor ? { actor: args.actor ?? latestSessionRecord?.actor } : {},
@@ -497,6 +530,605 @@ async function persistYieldSessionRecord(args) {
497
530
  }
498
531
  }
499
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
+
500
1132
  // src/chat/state/artifacts.ts
501
1133
  function coerceThreadArtifactsState(value) {
502
1134
  if (!value || typeof value !== "object") {
@@ -940,6 +1572,9 @@ function isPendingAuthLatestRequest(conversation, pendingAuth) {
940
1572
 
941
1573
  export {
942
1574
  buildDeterministicTurnId,
1575
+ buildDeterministicAssistantMessageId,
1576
+ ConversationTurnBoundaryError,
1577
+ getConversationTurnBoundaryError,
943
1578
  CooperativeTurnYieldError,
944
1579
  isCooperativeTurnYieldError,
945
1580
  TurnInputCommitLostError,
@@ -958,7 +1593,7 @@ export {
958
1593
  persistCompletedSessionRecord,
959
1594
  completeDeliveredTurn,
960
1595
  persistAuthPauseSessionRecord,
961
- persistTimeoutSessionRecord,
1596
+ persistContinuationSessionRecord,
962
1597
  persistYieldSessionRecord,
963
1598
  getInterruptionMarker,
964
1599
  canReusePendingAuthLink,
@@ -966,6 +1601,22 @@ export {
966
1601
  clearPendingAuth,
967
1602
  abandonReplacedPendingAuth,
968
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,
969
1620
  coerceThreadArtifactsState,
970
1621
  mergeArtifactsState,
971
1622
  getPersistedSandboxState,