@sentry/junior 0.202.0 → 0.204.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 (174) hide show
  1. package/dist/agent-CMyTD4DD.js +17509 -0
  2. package/dist/agent-hooks-Bbcp9e33.js +4040 -0
  3. package/dist/agent-runner-DNb1A49M.js +678 -0
  4. package/dist/api/schema.d.ts +2728 -2639
  5. package/dist/api/schema.js +4 -169
  6. package/dist/api.d.ts +23 -17
  7. package/dist/api.js +3881 -5326
  8. package/dist/app-B1TOqBI8.d.ts +246 -0
  9. package/dist/app.d.ts +4 -520
  10. package/dist/app.js +9522 -11875
  11. package/dist/auth-pause-state-DosEalIG.js +168 -0
  12. package/dist/catalog-runtime-BReos-D4.js +7 -0
  13. package/dist/checkpoint-CNrPyRJN.js +2450 -0
  14. package/dist/cli/chat.d.ts +20 -0
  15. package/dist/cli/chat.js +229 -336
  16. package/dist/cli/check.d.ts +8 -0
  17. package/dist/cli/check.js +513 -806
  18. package/dist/cli/env.d.ts +13 -0
  19. package/dist/cli/env.js +63 -12
  20. package/dist/cli/init.d.ts +3 -0
  21. package/dist/cli/init.js +90 -159
  22. package/dist/cli/main.d.ts +6 -0
  23. package/dist/cli/main.js +43 -64
  24. package/dist/cli/plugins.d.ts +13 -0
  25. package/dist/cli/plugins.js +148 -227
  26. package/dist/cli/run.d.ts +16 -0
  27. package/dist/cli/run.js +52 -8
  28. package/dist/cli/snapshot-create.d.ts +4 -0
  29. package/dist/cli/snapshot-create.js +71 -125
  30. package/dist/cli/upgrade.d.ts +15 -0
  31. package/dist/cli/upgrade.js +232 -335
  32. package/dist/config-nGCW1OTG.js +941 -0
  33. package/dist/context-B45_WqVX.js +298 -0
  34. package/dist/conversation-privacy-B4voFRSm.js +182 -0
  35. package/dist/conversation-work-C6y3T5Hx.js +51 -0
  36. package/dist/credential-sync-kXr1hvO1.js +2660 -0
  37. package/dist/db-BeyXKJOI.d.ts +5 -0
  38. package/dist/destinations-BeBiJeEM.js +29 -0
  39. package/dist/execution-stats-DWfSihcH.js +373 -0
  40. package/dist/executor-DcMSlI5c.js +721 -0
  41. package/dist/experimental-CIXf7Uye.js +46 -0
  42. package/dist/history-ClE4toxe.d.ts +32 -0
  43. package/dist/history-CqTuSAdI.js +441 -0
  44. package/dist/instrumentation.d.ts +3 -3
  45. package/dist/instrumentation.js +130 -176
  46. package/dist/job-queue-Drn_LXlM.js +63 -0
  47. package/dist/logging-BzAk1S_s.js +1210 -0
  48. package/dist/model-profile-CZmVoGdX.js +49 -0
  49. package/dist/nitro.d.ts +31 -36
  50. package/dist/nitro.js +275 -456
  51. package/dist/oauth-callback-server-JiftokvA.js +130 -0
  52. package/dist/oauth-relay-CdCnu80W.js +62 -0
  53. package/dist/package-discovery-7a9YqLhE.js +455 -0
  54. package/dist/pending-auth-CWmSdZVg.js +185 -0
  55. package/dist/plugin-module-BKGOXca2.js +81 -0
  56. package/dist/plugins-BI9A1vKA.d.ts +95 -0
  57. package/dist/plugins-egKWjgue.js +99 -0
  58. package/dist/private-trace-result-BLvBjy_e.js +16 -0
  59. package/dist/profile-CrT5nXbw.js +861 -0
  60. package/dist/registry-DH1xUo2N.js +1419 -0
  61. package/dist/reporting-schema-DosTN__Q.js +105 -0
  62. package/dist/resolve-CZHnfxaS.js +546 -0
  63. package/dist/rolldown-runtime-B4FMCO8f.js +28 -0
  64. package/dist/runner-CZV2sC46.js +392 -0
  65. package/dist/sandbox-egress-signals-bHZfmhbd.js +46 -0
  66. package/dist/schema-Rn8-XEaN.js +1140 -0
  67. package/dist/sentry-CD3SdJl-.js +26 -0
  68. package/dist/skills-CIcDQSgM.js +316 -0
  69. package/dist/spawn-DdkUFRmq.js +29 -0
  70. package/dist/stats-DTUbAb88.js +2634 -0
  71. package/dist/store-C5qxF2ac.d.ts +208 -0
  72. package/dist/task-queue-CJIR4RF7.js +136 -0
  73. package/dist/task-runner-D-O3A-Cp.js +639 -0
  74. package/dist/thread-state-CRc9jTJS.js +431 -0
  75. package/dist/turn-execution-BkinIdJF.js +47 -0
  76. package/dist/turn-lifecycle-DQV19VTW.js +54 -0
  77. package/dist/usage-D3EbSO7w.js +296 -0
  78. package/dist/usage-schema-CKaDkH_j.js +20 -0
  79. package/dist/validation-C2CR-OX3.js +40 -0
  80. package/dist/vercel-queue-DIkVcfK4.js +167 -0
  81. package/dist/vercel.d.ts +6 -6
  82. package/dist/vercel.js +14 -6
  83. package/dist/version-CnXZ614E.d.ts +5 -0
  84. package/dist/version-DxcWdoKa.js +8 -0
  85. package/dist/version.d.ts +2 -4
  86. package/dist/version.js +2 -6
  87. package/dist/web-authorization-RMBxAR_t.js +76 -0
  88. package/dist/web-input-3EPfpf55.js +494 -0
  89. package/dist/work-fGofI7Op.js +509 -0
  90. package/dist/worker-CsOLq-mR.js +625 -0
  91. package/package.json +9 -9
  92. package/dist/agent-hooks-UXITLYOK.js +0 -51
  93. package/dist/catalog-runtime-BG3JLLK2.js +0 -14
  94. package/dist/chunk-2LYHWMR4.js +0 -46
  95. package/dist/chunk-2MK3QBGA.js +0 -321
  96. package/dist/chunk-3PGQSKJV.js +0 -692
  97. package/dist/chunk-3UI3WFTX.js +0 -519
  98. package/dist/chunk-3Y47VMHV.js +0 -6
  99. package/dist/chunk-4UO6FK4G.js +0 -64
  100. package/dist/chunk-5NOPO64K.js +0 -198
  101. package/dist/chunk-5PKWYPFT.js +0 -2943
  102. package/dist/chunk-63NKKFJL.js +0 -1228
  103. package/dist/chunk-6AYH6252.js +0 -787
  104. package/dist/chunk-6Y5B5PEX.js +0 -80
  105. package/dist/chunk-7DHZP5KJ.js +0 -333
  106. package/dist/chunk-7H2VZTWN.js +0 -103
  107. package/dist/chunk-7NL7T6VU.js +0 -406
  108. package/dist/chunk-A76OIMYN.js +0 -102
  109. package/dist/chunk-AWMS3SPP.js +0 -58
  110. package/dist/chunk-B3LY5RCL.js +0 -229
  111. package/dist/chunk-BD2KT3OB.js +0 -36
  112. package/dist/chunk-BXCX5QKA.js +0 -2086
  113. package/dist/chunk-C6VVF43U.js +0 -81
  114. package/dist/chunk-CEMMLV5L.js +0 -98
  115. package/dist/chunk-CM5HNV2W.js +0 -131
  116. package/dist/chunk-DA2KGZZI.js +0 -13
  117. package/dist/chunk-DBVY6HLX.js +0 -64
  118. package/dist/chunk-DIB4GX3G.js +0 -8
  119. package/dist/chunk-DKIJ2RP5.js +0 -68
  120. package/dist/chunk-DL2KLKVH.js +0 -931
  121. package/dist/chunk-DWPN6SCH.js +0 -172
  122. package/dist/chunk-ELWOYVOY.js +0 -206
  123. package/dist/chunk-EWT7H65X.js +0 -506
  124. package/dist/chunk-FA6POGLH.js +0 -70
  125. package/dist/chunk-HMREVIO2.js +0 -135
  126. package/dist/chunk-IIWUT4FO.js +0 -820
  127. package/dist/chunk-JAL3CH4G.js +0 -27
  128. package/dist/chunk-JSJQEWBJ.js +0 -3295
  129. package/dist/chunk-KHKKHDLD.js +0 -387
  130. package/dist/chunk-LA6R4A7Q.js +0 -706
  131. package/dist/chunk-LOOU2DUR.js +0 -20428
  132. package/dist/chunk-M6FHWXE3.js +0 -700
  133. package/dist/chunk-M76HMB7M.js +0 -1856
  134. package/dist/chunk-MAGNK5J4.js +0 -1520
  135. package/dist/chunk-MTM5IFCY.js +0 -1190
  136. package/dist/chunk-N7TMSOZH.js +0 -235
  137. package/dist/chunk-NHJU2ZAL.js +0 -56
  138. package/dist/chunk-NSXKRGRT.js +0 -717
  139. package/dist/chunk-OWMPFXJA.js +0 -1764
  140. package/dist/chunk-PDO5BLNM.js +0 -22
  141. package/dist/chunk-PEQXYSSE.js +0 -45
  142. package/dist/chunk-PW4P4JYF.js +0 -1135
  143. package/dist/chunk-QB54UQUS.js +0 -320
  144. package/dist/chunk-QP3OGJXF.js +0 -105
  145. package/dist/chunk-RC745GGZ.js +0 -122
  146. package/dist/chunk-REOIRYPR.js +0 -37
  147. package/dist/chunk-SCOZRLZ4.js +0 -164
  148. package/dist/chunk-TXJSXBV5.js +0 -1433
  149. package/dist/chunk-TXKUKMVO.js +0 -417
  150. package/dist/chunk-U2GPUYAL.js +0 -3656
  151. package/dist/chunk-UIE3R5XU.js +0 -17
  152. package/dist/chunk-UXPG6ZIN.js +0 -84
  153. package/dist/chunk-VFUK3X5B.js +0 -50
  154. package/dist/chunk-WQSHDTTI.js +0 -1163
  155. package/dist/chunk-X6GDDWQH.js +0 -60
  156. package/dist/chunk-Y2CM7HXH.js +0 -111
  157. package/dist/chunk-Y7X25LFY.js +0 -15
  158. package/dist/chunk-YDUR3ZYW.js +0 -10
  159. package/dist/conversation-work-VIOQ5ZF3.js +0 -54
  160. package/dist/db-3L2MCVRI.js +0 -26
  161. package/dist/discovery-DWVDM7UK.js +0 -44
  162. package/dist/experimental-P5FZM4CU.js +0 -12
  163. package/dist/oauth-callback-server-YVV6C6T2.js +0 -243
  164. package/dist/oauth-relay-5XPVRPYB.js +0 -8
  165. package/dist/plugins-YTYJMMJF.js +0 -14
  166. package/dist/runner-LQ7RETDP.js +0 -522
  167. package/dist/sandbox-egress-signals-FIL7EGPR.js +0 -73
  168. package/dist/sentry-MB3NL3IR.js +0 -42
  169. package/dist/skills-XRWYEDCD.js +0 -27
  170. package/dist/spawn-RC6SJUAI.js +0 -42
  171. package/dist/task-runner-7DY7ARYH.js +0 -36
  172. package/dist/validation-7MNQGCJA.js +0 -17
  173. package/dist/work-COALHUCG.js +0 -51
  174. package/dist/worker-6MURZIPD.js +0 -24
@@ -1,2943 +0,0 @@
1
- import {
2
- JUNIOR_NATIVE_EVENT_NAMESPACE,
3
- ToolInputError,
4
- addAgentTurnUsage,
5
- agentsInstructionsUpdatedEvent,
6
- authenticationLinkedEvent,
7
- authenticationUnlinkedEvent,
8
- fallbackShortTitle,
9
- generateShortTitle
10
- } from "./chunk-7DHZP5KJ.js";
11
- import {
12
- bindProviderConversation,
13
- createSqlConversationEventStore,
14
- getConversationEventStore,
15
- getConversationStore,
16
- getSqlExecutor,
17
- sanitizePostgresJson,
18
- withConversationEventLock
19
- } from "./chunk-M76HMB7M.js";
20
- import {
21
- SANDBOX_REPOS_ROOT,
22
- SANDBOX_WORKSPACE_ROOT,
23
- sleep
24
- } from "./chunk-NSXKRGRT.js";
25
- import {
26
- MUTATION_LOCK_TTL_MS,
27
- fenceLock,
28
- getStateAdapter,
29
- withLock
30
- } from "./chunk-2MK3QBGA.js";
31
- import {
32
- agentHistoryItemSchema,
33
- contextProvenance,
34
- instructionActors,
35
- instructionProvenanceFor
36
- } from "./chunk-7NL7T6VU.js";
37
- import {
38
- botConfig,
39
- completeText,
40
- escapeXml,
41
- resolveGatewayModel,
42
- unwrapCurrentInstruction
43
- } from "./chunk-63NKKFJL.js";
44
- import {
45
- parseSlackMessageTs,
46
- toStoredSlackActor
47
- } from "./chunk-N7TMSOZH.js";
48
- import {
49
- buildTurnFailureResponse,
50
- getActiveTraceId,
51
- logException,
52
- logWarn,
53
- setSpanAttributes
54
- } from "./chunk-MAGNK5J4.js";
55
-
56
- // src/chat/turn-context-tag.ts
57
- var TURN_CONTEXT_TAG = "runtime-turn-context";
58
-
59
- // src/chat/pi/transcript.ts
60
- var RUNTIME_TURN_CONTEXT_START = `<${TURN_CONTEXT_TAG}>`;
61
- var AGENTS_INSTRUCTIONS_START = "# AGENTS.md instructions";
62
- function userMessageContent(message) {
63
- const record = message;
64
- return record.role === "user" && Array.isArray(record.content) ? record.content : void 0;
65
- }
66
- function isRuntimeTurnContextPart(part) {
67
- return part !== null && typeof part === "object" && part.type === "text" && typeof part.text === "string" && (part.text.startsWith(RUNTIME_TURN_CONTEXT_START) || part.text.startsWith(AGENTS_INSTRUCTIONS_START));
68
- }
69
- function hasCurrentInstruction(message) {
70
- return userMessageContent(message)?.some(
71
- (part) => part !== null && typeof part === "object" && part.type === "text" && typeof part.text === "string" && part.text.includes("<current-instruction")
72
- ) ?? false;
73
- }
74
- function messageTimestamp(message) {
75
- return message.timestamp;
76
- }
77
- function isStandaloneRuntimeContextMessage(messages, index) {
78
- const message = messages[index];
79
- const next = messages[index + 1];
80
- return Boolean(
81
- message && next && userMessageContent(message) && userMessageContent(next) && messageTimestamp(message) === messageTimestamp(next) && !hasCurrentInstruction(message) && hasCurrentInstruction(next)
82
- );
83
- }
84
- var EMBEDDED_THREAD_CONTEXT_TAGS = [
85
- "thread-context",
86
- "recent-thread-messages",
87
- "thread-compactions",
88
- "thread-transcript",
89
- "thread-background"
90
- ];
91
- var EMBEDDED_THREAD_CONTEXT_PATTERN = new RegExp(
92
- `<(${EMBEDDED_THREAD_CONTEXT_TAGS.join("|")})(?:\\s[^>]*)?>[\\s\\S]*</\\1>`,
93
- "g"
94
- );
95
- function isAssistantMessage(value) {
96
- return typeof value === "object" && value !== null && value.role === "assistant";
97
- }
98
- function isToolResultMessage(value) {
99
- return typeof value === "object" && value !== null && value.role === "toolResult";
100
- }
101
- function isToolResultError(result) {
102
- if (!result || typeof result !== "object") return false;
103
- return Boolean(result.isError);
104
- }
105
- function normalizeToolNameFromResult(result) {
106
- if (!result || typeof result !== "object") return void 0;
107
- const record = result;
108
- if (typeof record.toolName === "string" && record.toolName.length > 0) {
109
- return record.toolName;
110
- }
111
- if (typeof record.name === "string" && record.name.length > 0) {
112
- return record.name;
113
- }
114
- return void 0;
115
- }
116
- function getPiMessageRole(value) {
117
- if (!value || typeof value !== "object") {
118
- return void 0;
119
- }
120
- const role = value.role;
121
- return typeof role === "string" ? role : void 0;
122
- }
123
- function isContinuablePiBoundary(messages) {
124
- const lastRole = getPiMessageRole(messages.at(-1));
125
- return lastRole === "user" || lastRole === "toolResult";
126
- }
127
- function extractAssistantText(message) {
128
- const content = message.content ?? [];
129
- return content.filter(
130
- (part) => part.type === "text" && typeof part.text === "string"
131
- ).map((part) => part.text).join("\n");
132
- }
133
- function getTerminalAssistantMessages(messages) {
134
- let lastNonAssistantIndex = -1;
135
- for (let index = messages.length - 1; index >= 0; index -= 1) {
136
- if (!isAssistantMessage(messages[index])) {
137
- lastNonAssistantIndex = index;
138
- break;
139
- }
140
- }
141
- return messages.slice(lastNonAssistantIndex + 1).filter(isAssistantMessage);
142
- }
143
- function trimTrailingAssistantMessages(messages) {
144
- let end = messages.length;
145
- while (end > 0 && getPiMessageRole(messages[end - 1]) === "assistant") {
146
- end -= 1;
147
- }
148
- return end === messages.length ? [...messages] : messages.slice(0, end);
149
- }
150
- function hasRuntimeTurnContext(messages) {
151
- return messages.some(
152
- (message, index) => isStandaloneRuntimeContextMessage(messages, index) || userMessageContent(message)?.some(isRuntimeTurnContextPart)
153
- );
154
- }
155
- function retainRuntimeTurnContext(messages) {
156
- return messages.flatMap((message, index) => {
157
- if (isStandaloneRuntimeContextMessage(messages, index)) {
158
- return [message];
159
- }
160
- const runtimeContent = userMessageContent(message)?.filter(isRuntimeTurnContextPart) ?? [];
161
- return runtimeContent.length > 0 ? [{ ...message, content: runtimeContent }] : [];
162
- });
163
- }
164
- function instructionTextForProjection(text) {
165
- const withoutContext = text.replace(EMBEDDED_THREAD_CONTEXT_PATTERN, "").trim();
166
- return unwrapCurrentInstruction(withoutContext) ?? withoutContext;
167
- }
168
- function getUserMessageInstructionText(message) {
169
- if (getPiMessageRole(message) !== "user") {
170
- return "";
171
- }
172
- const content = message.content;
173
- const text = typeof content === "string" ? content : Array.isArray(content) ? content.flatMap(
174
- (part) => part && typeof part === "object" && part.type === "text" && typeof part.text === "string" ? [part.text] : []
175
- ).join("\n") : "";
176
- return instructionTextForProjection(text).trim();
177
- }
178
- function stripRuntimeTurnContext(messages) {
179
- return messages.flatMap((message, index) => {
180
- if (isStandaloneRuntimeContextMessage(messages, index)) {
181
- return [];
182
- }
183
- const content = userMessageContent(message);
184
- if (!content) return [message];
185
- const nextContent = content.filter(
186
- (part) => !isRuntimeTurnContextPart(part)
187
- );
188
- if (nextContent.length === content.length) return [message];
189
- if (nextContent.length === 0) return [];
190
- return [{ ...message, content: nextContent }];
191
- });
192
- }
193
-
194
- // src/chat/services/context-budget.ts
195
- var COMPACTION_TRIGGER_RATIO = 0.9;
196
- var CONTEXT_INPUT_LIMIT_RATIO = 0.95;
197
- var COMPACTION_TARGET_RATIO = 0.8;
198
- var FALLBACK_CONTEXT_WINDOW_TOKENS = 4e5;
199
- function positiveInteger(value, fallback) {
200
- return Number.isFinite(value) && value > 0 ? Math.floor(value) : fallback;
201
- }
202
- function estimateTextTokens(text) {
203
- return Math.ceil(text.length / 4);
204
- }
205
- function calculateContextCompactionTriggerTokens(model) {
206
- const contextWindow = positiveInteger(
207
- model.contextWindow,
208
- FALLBACK_CONTEXT_WINDOW_TOKENS
209
- );
210
- return Math.max(1, Math.floor(contextWindow * COMPACTION_TRIGGER_RATIO));
211
- }
212
- function calculateContextInputLimitTokens(model) {
213
- const contextWindow = positiveInteger(
214
- model.contextWindow,
215
- FALLBACK_CONTEXT_WINDOW_TOKENS
216
- );
217
- return Math.max(1, Math.floor(contextWindow * CONTEXT_INPUT_LIMIT_RATIO));
218
- }
219
- function calculateContextCompactionTargetTokens(triggerTokens) {
220
- return Math.max(1, Math.floor(triggerTokens * COMPACTION_TARGET_RATIO));
221
- }
222
- function getModelContextBudget(modelId) {
223
- const model = resolveGatewayModel(modelId);
224
- const advertisedContextWindow = positiveInteger(
225
- model.contextWindow,
226
- FALLBACK_CONTEXT_WINDOW_TOKENS
227
- );
228
- return {
229
- contextWindow: Math.min(
230
- botConfig.contextWindowTokens,
231
- advertisedContextWindow
232
- )
233
- };
234
- }
235
- function getAgentContextCompactionTriggerTokens(modelId) {
236
- return calculateContextCompactionTriggerTokens(
237
- getModelContextBudget(modelId)
238
- );
239
- }
240
- function getAgentContextInputLimitTokens(modelId) {
241
- return calculateContextInputLimitTokens(getModelContextBudget(modelId));
242
- }
243
- function getConversationContextCompactionTriggerTokens() {
244
- return calculateContextCompactionTriggerTokens(
245
- getModelContextBudget(botConfig.fastModelId)
246
- );
247
- }
248
-
249
- // src/chat/services/conversation-memory.ts
250
- var CONTEXT_MIN_LIVE_MESSAGES = 12;
251
- var CONTEXT_COMPACTION_BATCH_SIZE = 24;
252
- var CONTEXT_MAX_COMPACTIONS = 16;
253
- var CONTEXT_MAX_MESSAGE_CHARS = 3200;
254
- var SLACK_USER_ID_DISPLAY_PATTERN = /^[UW][A-Z0-9]{5,}$/;
255
- function generateConversationId(prefix) {
256
- return `${prefix}_${Date.now()}_${Math.random().toString(36).slice(2, 10)}`;
257
- }
258
- function normalizeConversationText(text) {
259
- return text.replace(/\r\n?/g, "\n").trim();
260
- }
261
- function buildImageContextSuffix(message, conversation) {
262
- const byFileId = conversation?.vision.byFileId;
263
- const imageFileIds = message.meta?.imageFileIds ?? [];
264
- if (!byFileId || imageFileIds.length === 0) {
265
- return "";
266
- }
267
- const summaries = imageFileIds.map((fileId) => byFileId[fileId]?.summary?.trim()).filter((summary) => Boolean(summary));
268
- if (summaries.length === 0) {
269
- return "";
270
- }
271
- return ` [image context: ${escapeXml(summaries.join(" | "))}]`;
272
- }
273
- function renderConversationMessageLine(message, conversation) {
274
- const displayName = escapeXml(conversationAuthorDisplayName(message));
275
- const text = escapeXml(
276
- message.text.replace(/\s+/g, " ").slice(0, CONTEXT_MAX_MESSAGE_CHARS)
277
- );
278
- const markers = [];
279
- if (message.meta?.replied === false) {
280
- markers.push(
281
- escapeXml(
282
- `assistant skipped: ${message.meta?.skippedReason ?? "no-reply route"}`
283
- )
284
- );
285
- }
286
- if (message.meta?.explicitMention) {
287
- markers.push("explicit mention");
288
- }
289
- const markerSuffix = markers.length > 0 ? ` (${markers.join("; ")})` : "";
290
- const imageContext = buildImageContextSuffix(message, conversation);
291
- return `[${message.role}] ${displayName}: ${text}${imageContext}${markerSuffix}`;
292
- }
293
- function conversationAuthorDisplayName(message) {
294
- const author = message.author;
295
- const fullName = authorDisplayField(author?.fullName, author?.userId);
296
- const userName = authorDisplayField(author?.userName, author?.userId);
297
- return fullName ?? userName ?? (message.role === "assistant" ? botConfig.userName : message.role);
298
- }
299
- function authorDisplayField(value, userId) {
300
- if (!value || value === userId || SLACK_USER_ID_DISPLAY_PATTERN.test(value)) {
301
- return void 0;
302
- }
303
- return value;
304
- }
305
- function estimateConversationContextTokens(conversation) {
306
- return estimateTextTokens(buildConversationContext(conversation) ?? "");
307
- }
308
- function upsertConversationMessage(conversation, message) {
309
- const existingIndex = conversation.messages.findIndex(
310
- (entry) => entry.id === message.id
311
- );
312
- if (existingIndex >= 0) {
313
- conversation.messages[existingIndex] = {
314
- ...conversation.messages[existingIndex],
315
- ...message,
316
- meta: {
317
- ...conversation.messages[existingIndex]?.meta,
318
- ...message.meta
319
- }
320
- };
321
- return message.id;
322
- }
323
- conversation.messages.push(message);
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
- ...args.source ? { source: args.source } : void 0
348
- }
349
- });
350
- return messageId;
351
- }
352
- function turnHasReply(conversation, turnId) {
353
- const assistantPrefix = `${turnId}:assistant:`;
354
- return conversation.messages.some(
355
- (message) => message.role === "assistant" && message.id.startsWith(assistantPrefix)
356
- );
357
- }
358
- function markConversationMessage(conversation, messageId, patch) {
359
- if (!messageId) return;
360
- const messageIndex = conversation.messages.findIndex(
361
- (entry) => entry.id === messageId
362
- );
363
- if (messageIndex < 0) return;
364
- const current = conversation.messages[messageIndex];
365
- conversation.messages[messageIndex] = {
366
- ...current,
367
- meta: {
368
- ...current.meta ?? {},
369
- ...patch
370
- }
371
- };
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-context authority="evidence-only">');
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-context>");
409
- }
410
- return lines.join("\n");
411
- }
412
- var THREAD_CONTEXT_OPEN = '<thread-context authority="evidence-only">';
413
- var THREAD_CONTEXT_CLOSE = "</thread-context>";
414
- function appendThreadContextMessages(baseContext, messageLines) {
415
- const base = baseContext?.trim();
416
- if (messageLines.length === 0) {
417
- return base || void 0;
418
- }
419
- const entryBlock = messageLines.join("\n");
420
- if (!base) {
421
- return [THREAD_CONTEXT_OPEN, entryBlock, THREAD_CONTEXT_CLOSE].join("\n");
422
- }
423
- const closeIdx = base.lastIndexOf(THREAD_CONTEXT_CLOSE);
424
- if (closeIdx >= 0) {
425
- const before = base.slice(0, closeIdx).replace(/\s*$/, "");
426
- const after = base.slice(closeIdx);
427
- return `${before}
428
- ${entryBlock}
429
- ${after}`;
430
- }
431
- return [base, "", THREAD_CONTEXT_OPEN, entryBlock, THREAD_CONTEXT_CLOSE].join(
432
- "\n"
433
- );
434
- }
435
- function pruneCompactions(compactions) {
436
- if (compactions.length <= CONTEXT_MAX_COMPACTIONS) {
437
- return compactions;
438
- }
439
- const overflowCount = compactions.length - CONTEXT_MAX_COMPACTIONS + 1;
440
- const merged = compactions.slice(0, overflowCount);
441
- const mergedSummary = merged.map((entry) => entry.summary).join("\n").slice(0, 3500);
442
- const coveredMessageCount = merged.reduce(
443
- (count, entry) => count + entry.coveredMessageCount,
444
- 0
445
- );
446
- const compacted = {
447
- id: generateConversationId("compaction"),
448
- createdAtMs: Date.now(),
449
- summary: mergedSummary,
450
- coveredMessageCount
451
- };
452
- return [compacted, ...compactions.slice(overflowCount)];
453
- }
454
- async function summarizeConversationChunk(messages, conversation, context, deps) {
455
- const transcript = messages.map((message) => renderConversationMessageLine(message, conversation)).join("\n");
456
- try {
457
- const result = await deps.completeText({
458
- modelId: botConfig.fastModelId,
459
- temperature: 0,
460
- promptName: "junior.thread_compaction",
461
- messages: [
462
- {
463
- role: "user",
464
- content: [
465
- "Summarize the following older Slack thread transcript segment for future assistant turns.",
466
- "Keep the summary factual and concise. Do not invent details.",
467
- "",
468
- "Output exactly three XML sections in this order:",
469
- "<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>",
470
- "<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>",
471
- "<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>",
472
- "",
473
- "Do not output any text outside the three sections.",
474
- "",
475
- transcript
476
- ].join("\n"),
477
- timestamp: Date.now()
478
- }
479
- ],
480
- metadata: {
481
- modelId: botConfig.fastModelId,
482
- threadId: context.threadId ?? "",
483
- channelId: context.channelId ?? "",
484
- actorId: context.actorId ?? "",
485
- runId: context.runId ?? ""
486
- }
487
- });
488
- const summary = result.text.trim();
489
- if (summary.length > 0) {
490
- return summary.slice(0, 3500);
491
- }
492
- } catch (error) {
493
- logWarn("conversation.compaction.summary.failed", {
494
- "exception.message": error instanceof Error ? error.message : String(error),
495
- "app.compaction_messages_covered": messages.length
496
- });
497
- }
498
- return transcript.slice(0, 2800);
499
- }
500
- function getThreadTitleSourceMessage(conversation) {
501
- let firstMessage;
502
- for (const message of conversation.messages) {
503
- if (!isHumanConversationMessage(message)) {
504
- continue;
505
- }
506
- if (!firstMessage) {
507
- firstMessage = message;
508
- continue;
509
- }
510
- if (message.createdAtMs < firstMessage.createdAtMs) {
511
- firstMessage = message;
512
- continue;
513
- }
514
- if (message.createdAtMs === firstMessage.createdAtMs && message.id < firstMessage.id) {
515
- firstMessage = message;
516
- }
517
- }
518
- return firstMessage;
519
- }
520
- async function compactConversationIfNeededWithDeps(conversation, context, deps) {
521
- let estimatedTokens = estimateConversationContextTokens(conversation);
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
- estimatedTokens = estimateConversationContextTokens(conversation);
551
- setSpanAttributes({
552
- "app.compaction_messages_covered": compactCount,
553
- "app.compaction.trigger_tokens": triggerTokens,
554
- "app.compaction.target_tokens": targetTokens,
555
- "app.context_tokens_estimated": estimatedTokens
556
- });
557
- if (estimatedTokens <= targetTokens) {
558
- break;
559
- }
560
- }
561
- }
562
- function createConversationMemoryService(deps) {
563
- return {
564
- compactConversationIfNeeded: async (conversation, context) => await compactConversationIfNeededWithDeps(conversation, context, deps)
565
- };
566
- }
567
- function isHumanConversationMessage(message) {
568
- return message.role === "user" && message.author?.isBot !== true;
569
- }
570
- function getConversationMessageSlackTs(message) {
571
- return parseSlackMessageTs(message.meta?.slackTs);
572
- }
573
-
574
- // src/chat/conversations/messages.ts
575
- import { createHash } from "crypto";
576
- import { isDeepStrictEqual as isDeepStrictEqual2 } from "util";
577
-
578
- // src/chat/services/conversation-title.ts
579
- var inFlightTitles = /* @__PURE__ */ new Map();
580
- async function ensureConversationTitle(args) {
581
- const pending = inFlightTitles.get(args.conversationId);
582
- if (pending) {
583
- return pending;
584
- }
585
- const work = ensureConversationTitleOnce(args).finally(() => {
586
- inFlightTitles.delete(args.conversationId);
587
- });
588
- inFlightTitles.set(args.conversationId, work);
589
- return work;
590
- }
591
- async function ensureConversationTitleOnce(args) {
592
- if (!args.conversation.messages.some(isHumanConversationMessage)) {
593
- return void 0;
594
- }
595
- const store = args.conversationStore ?? getConversationStore();
596
- try {
597
- const stored = await store.get({ conversationId: args.conversationId });
598
- if (stored?.title?.trim()) {
599
- return void 0;
600
- }
601
- if (stored?.parentConversationId) {
602
- return void 0;
603
- }
604
- const titleSourceMessage = getThreadTitleSourceMessage(args.conversation);
605
- const sourceText = titleSourceMessage?.text.trim() ?? "";
606
- if (!titleSourceMessage || !sourceText) {
607
- return void 0;
608
- }
609
- let title;
610
- try {
611
- const generated = await generateShortTitle({
612
- completeText,
613
- kind: "conversation",
614
- sourceText
615
- });
616
- title = generated ?? fallbackShortTitle(sourceText, "Conversation");
617
- } catch (error) {
618
- logWarn("conversation.title.generation.failed", {
619
- "exception.message": error instanceof Error ? error.message : String(error)
620
- });
621
- title = fallbackShortTitle(sourceText, "Conversation");
622
- }
623
- const normalized = title.trim();
624
- if (!normalized) {
625
- return void 0;
626
- }
627
- const activityAtMs = titleSourceMessage.createdAtMs;
628
- const nowMs = args.nowMs ?? Date.now();
629
- try {
630
- await store.recordActivity({
631
- activityAtMs,
632
- conversationId: args.conversationId,
633
- nowMs,
634
- title: normalized
635
- });
636
- } catch (error) {
637
- logException(error, "conversation.title.persist.failed");
638
- return void 0;
639
- }
640
- return normalized;
641
- } catch (error) {
642
- logException(error, "conversation.title.ensure.failed");
643
- return void 0;
644
- }
645
- }
646
- function scheduleConversationTitle(args) {
647
- if (!args.conversationId) {
648
- return;
649
- }
650
- void ensureConversationTitle({
651
- conversation: args.conversation,
652
- conversationId: args.conversationId
653
- }).catch((error) => {
654
- logException(error, "conversation.title.task.failed");
655
- });
656
- }
657
- async function resolveConversationTitle(args) {
658
- const pending = inFlightTitles.get(args.conversationId);
659
- if (pending) {
660
- const generated = await pending;
661
- if (generated?.trim()) {
662
- return generated.trim();
663
- }
664
- }
665
- const store = args.conversationStore ?? getConversationStore();
666
- const stored = await store.get({ conversationId: args.conversationId });
667
- return stored?.title?.trim() || void 0;
668
- }
669
-
670
- // src/chat/conversations/message-summaries.ts
671
- import { isDeepStrictEqual } from "util";
672
- function projectConversationMessageSummaries(events) {
673
- for (let index = events.length - 1; index >= 0; index -= 1) {
674
- const data = events[index]?.data;
675
- if (data?.type === "messages_summarized") {
676
- return data.compactions;
677
- }
678
- }
679
- return [];
680
- }
681
- async function persistConversationMessageSummaries(args) {
682
- const eventStore = getConversationEventStore();
683
- const history = await eventStore.loadMessageHistory(args.conversationId);
684
- const existing = history.compaction ? projectConversationMessageSummaries([history.compaction]) : [];
685
- if (isDeepStrictEqual(existing, args.conversation.compactions)) {
686
- return;
687
- }
688
- const liveMessageIds = new Set(
689
- args.conversation.messages.map((message) => message.id)
690
- );
691
- const historyFromSeq = history.events.find(
692
- (event) => event.data.type === "message" && liveMessageIds.has(event.data.messageId)
693
- )?.seq;
694
- const nextHistoryFromSeq = historyFromSeq ?? Math.max(history.historyFromSeq, (history.events.at(-1)?.seq ?? -1) + 1);
695
- await eventStore.append(args.conversationId, [
696
- {
697
- data: {
698
- type: "messages_summarized",
699
- historyFromSeq: nextHistoryFromSeq,
700
- compactions: args.conversation.compactions
701
- },
702
- createdAtMs: args.conversation.compactions.at(-1)?.createdAtMs ?? Date.now()
703
- }
704
- ]);
705
- }
706
-
707
- // src/chat/conversations/message-projection.ts
708
- function isMessageEvent(event) {
709
- return event.data.type === "message" || event.data.type === "message_updated" || event.data.type === "message_handled";
710
- }
711
- function splitMeta(meta) {
712
- const rest = { ...meta ?? {} };
713
- const author = rest.author;
714
- delete rest.author;
715
- return {
716
- ...author ? { author } : void 0,
717
- ...Object.keys(rest).length > 0 ? { meta: rest } : void 0
718
- };
719
- }
720
- function missingBaselineError(event) {
721
- return new Error(
722
- `Message event ${event.data.type} at seq ${event.seq} references ${event.data.messageId} before message`
723
- );
724
- }
725
- function projectConversationMessages(history) {
726
- const byId = /* @__PURE__ */ new Map();
727
- for (const event of history.events) {
728
- if (!isMessageEvent(event)) continue;
729
- const data = event.data;
730
- const current = byId.get(data.messageId);
731
- if (data.type === "message" || data.type === "message_updated") {
732
- const message = {
733
- id: data.messageId,
734
- role: data.role,
735
- text: data.text,
736
- createdAtMs: event.createdAtMs,
737
- ...splitMeta(data.meta)
738
- };
739
- if (data.type === "message" && current)
740
- throw new Error(
741
- `Duplicate message event for ${data.messageId} at seq ${event.seq}`
742
- );
743
- if (data.type === "message_updated" && !current) {
744
- if (history.historyFromSeq > 0) continue;
745
- throw missingBaselineError(event);
746
- }
747
- byId.set(data.messageId, {
748
- message: {
749
- ...message,
750
- createdAtMs: current?.message.createdAtMs ?? message.createdAtMs
751
- },
752
- ...current?.repliedAtMs === void 0 ? void 0 : { repliedAtMs: current.repliedAtMs }
753
- });
754
- continue;
755
- }
756
- if (!current) {
757
- if (history.historyFromSeq > 0) continue;
758
- throw missingBaselineError(event);
759
- }
760
- current.repliedAtMs ??= event.createdAtMs;
761
- }
762
- return [...byId.values()].map(({ message, repliedAtMs }) => {
763
- if (repliedAtMs === void 0) return message;
764
- return {
765
- ...message,
766
- meta: { ...message.meta ?? {}, replied: true }
767
- };
768
- });
769
- }
770
-
771
- // src/chat/conversations/messages.ts
772
- function toStoredConversationMessage(message) {
773
- const meta = {};
774
- if (message.author) meta.author = message.author;
775
- const { replied, ...restMeta } = message.meta ?? {};
776
- Object.assign(meta, restMeta);
777
- if (replied === false) meta.replied = false;
778
- return {
779
- messageId: message.id,
780
- role: message.role,
781
- text: message.text,
782
- ...Object.keys(meta).length > 0 ? { meta } : void 0,
783
- createdAtMs: message.createdAtMs
784
- };
785
- }
786
- function canonicalJson(value) {
787
- if (Array.isArray(value)) return value.map(canonicalJson);
788
- if (!value || typeof value !== "object") return value;
789
- return Object.fromEntries(
790
- Object.entries(value).filter(([, item]) => item !== void 0).sort(([left], [right]) => left.localeCompare(right)).map(([key, item]) => [key, canonicalJson(item)])
791
- );
792
- }
793
- function messageStateKey(message) {
794
- return createHash("sha256").update(JSON.stringify(canonicalJson(message))).digest("hex").slice(0, 32);
795
- }
796
- async function hydrateConversationMessages(args) {
797
- if (!args.conversationId) {
798
- args.conversation.messages = [];
799
- return;
800
- }
801
- const history = await getConversationEventStore().loadMessageHistory(
802
- args.conversationId
803
- );
804
- args.conversation.compactions = history.compaction ? projectConversationMessageSummaries([history.compaction]) : [];
805
- args.conversation.messages = projectConversationMessages(history);
806
- }
807
- async function persistConversationMessages(args) {
808
- if (!args.conversationId || args.conversation.messages.length === 0) return;
809
- await appendConversationMessages(getConversationEventStore(), {
810
- ...args,
811
- conversationId: args.conversationId
812
- });
813
- scheduleConversationTitle({
814
- conversation: args.conversation,
815
- conversationId: args.conversationId
816
- });
817
- }
818
- async function appendConversationMessages(eventStore, args) {
819
- if (args.conversation.messages.length === 0) return;
820
- const history = await eventStore.loadMessageHistory(args.conversationId);
821
- const existingMessages = new Map(
822
- projectConversationMessages(history).map((message) => [
823
- message.id,
824
- message
825
- ])
826
- );
827
- const handledAtMs = args.repliedAtMs ?? Date.now();
828
- const events = args.conversation.messages.flatMap((message) => {
829
- const stored = toStoredConversationMessage(message);
830
- const existing = existingMessages.get(stored.messageId);
831
- const changed = existing !== void 0 && !isDeepStrictEqual2(toStoredConversationMessage(existing), stored);
832
- const messageEvent = existing ? changed ? [
833
- {
834
- idempotencyKey: `message:${stored.messageId}:update:${messageStateKey(stored)}`,
835
- createdAtMs: handledAtMs,
836
- data: {
837
- type: "message_updated",
838
- messageId: stored.messageId,
839
- role: stored.role,
840
- text: stored.text,
841
- ...stored.authorIdentityId ? { authorIdentityId: stored.authorIdentityId } : void 0,
842
- ...stored.meta ? { meta: stored.meta } : void 0
843
- }
844
- }
845
- ] : [] : [
846
- {
847
- idempotencyKey: `message:${stored.messageId}`,
848
- createdAtMs: stored.createdAtMs,
849
- data: {
850
- type: "message",
851
- messageId: stored.messageId,
852
- role: stored.role,
853
- text: stored.text,
854
- ...stored.authorIdentityId ? { authorIdentityId: stored.authorIdentityId } : void 0,
855
- ...stored.meta ? { meta: stored.meta } : void 0
856
- }
857
- }
858
- ];
859
- return [
860
- ...messageEvent,
861
- ...message.meta?.replied === true ? [
862
- {
863
- idempotencyKey: `message:${stored.messageId}:handled`,
864
- createdAtMs: handledAtMs,
865
- data: {
866
- type: "message_handled",
867
- messageId: stored.messageId
868
- }
869
- }
870
- ] : []
871
- ];
872
- });
873
- await eventStore.append(args.conversationId, events);
874
- }
875
-
876
- // src/chat/conversations/projection.ts
877
- import { isDeepStrictEqual as isDeepStrictEqual3 } from "util";
878
-
879
- // src/chat/pi/messages.ts
880
- import { z } from "zod";
881
- var piMessageSchema = z.object({
882
- role: z.string()
883
- }).passthrough().transform((value) => value);
884
- var piContentMessageSchema = z.object({
885
- content: z.array(z.unknown()),
886
- role: z.string().min(1)
887
- }).passthrough().transform((value) => value);
888
-
889
- // src/chat/pi/conversation-events.ts
890
- function authorizationObservationMessage(data, createdAtMs) {
891
- const label = data.kind === "mcp" ? "MCP authorization" : "Authorization";
892
- return piMessageSchema.parse({
893
- role: "user",
894
- content: [
895
- {
896
- type: "text",
897
- text: `${label} completed for provider "${data.provider}". Continue the blocked request and retry the provider operation if needed.`
898
- }
899
- ],
900
- timestamp: createdAtMs
901
- });
902
- }
903
- function durableMessages(message) {
904
- return stripRuntimeTurnContext([message]);
905
- }
906
- function historyItemFromPiMessage(message, provenance) {
907
- const parsed = piMessageSchema.parse(message);
908
- const { role, ...fields } = parsed;
909
- if (role === "user") {
910
- return agentHistoryItemSchema.parse({
911
- ...fields,
912
- type: "user_message",
913
- provenance
914
- });
915
- }
916
- if (role === "assistant") {
917
- return agentHistoryItemSchema.parse({
918
- ...fields,
919
- type: "assistant_message"
920
- });
921
- }
922
- if (role === "toolResult") {
923
- return agentHistoryItemSchema.parse({
924
- ...fields,
925
- type: "tool_result"
926
- });
927
- }
928
- throw new Error(`Unsupported durable agent message role "${role}"`);
929
- }
930
- function piMessageFromHistoryItem(data) {
931
- if (data.type === "user_message") {
932
- const { type: _2, provenance: __, ...fields2 } = data;
933
- return piMessageSchema.parse({ ...fields2, role: "user" });
934
- }
935
- if (data.type === "assistant_message") {
936
- const { type: _2, ...fields2 } = data;
937
- return piMessageSchema.parse({ ...fields2, role: "assistant" });
938
- }
939
- const { type: _, ...fields } = data;
940
- return piMessageSchema.parse({ ...fields, role: "toolResult" });
941
- }
942
- function historyItemProvenance(data) {
943
- return data.type === "user_message" ? data.provenance : contextProvenance;
944
- }
945
- function projectConversationEvents(events, options) {
946
- const messages = [];
947
- const provenance = [];
948
- const seqs = [];
949
- let modelProfile = options.defaultProfile;
950
- let replacementSeq;
951
- for (const event of events) {
952
- if (options.maxSeq !== void 0 && event.seq > options.maxSeq) break;
953
- if (event.data.type === "unknown") {
954
- throw new Error(
955
- `Unsupported conversation event "${event.data.originalType}" at seq ${event.seq} (schema version ${event.schemaVersion})`
956
- );
957
- }
958
- if (event.data.type === "compaction" || event.data.type === "handoff") {
959
- modelProfile = event.data.modelProfile;
960
- replacementSeq = event.seq;
961
- for (const replacement of event.data.replacementHistory) {
962
- for (const message of durableMessages(
963
- piMessageFromHistoryItem(replacement.item)
964
- )) {
965
- messages.push(message);
966
- provenance.push(historyItemProvenance(replacement.item));
967
- seqs.push(replacement.sourceEventSeq ?? event.seq);
968
- }
969
- }
970
- continue;
971
- }
972
- if (event.data.type === "user_message" || event.data.type === "assistant_message" || event.data.type === "tool_result") {
973
- for (const message of durableMessages(
974
- piMessageFromHistoryItem(event.data)
975
- )) {
976
- messages.push(message);
977
- provenance.push(historyItemProvenance(event.data));
978
- seqs.push(event.seq);
979
- }
980
- continue;
981
- }
982
- if (event.data.type === "authorization_completed") {
983
- messages.push(
984
- authorizationObservationMessage(event.data, event.createdAtMs)
985
- );
986
- provenance.push(contextProvenance);
987
- seqs.push(event.seq);
988
- }
989
- }
990
- return {
991
- messages,
992
- provenance,
993
- seqs,
994
- modelProfile,
995
- replacementSeq
996
- };
997
- }
998
-
999
- // src/chat/conversations/projection.ts
1000
- function connectedMcpProvidersFromEvents(events, credentialSubjectId) {
1001
- const providers = /* @__PURE__ */ new Set();
1002
- for (const event of events) {
1003
- if (event.data.type === "mcp_provider_connected" && event.data.credentialSubjectId === credentialSubjectId) {
1004
- providers.add(event.data.provider);
1005
- }
1006
- }
1007
- return [...providers].sort((left, right) => left.localeCompare(right));
1008
- }
1009
- function isUserMessage(message) {
1010
- return message.role === "user";
1011
- }
1012
- var AgentHistoryBranchError = class extends Error {
1013
- constructor(conversationId) {
1014
- super(
1015
- `Agent history for ${conversationId} changed before its committed boundary`
1016
- );
1017
- this.name = "AgentHistoryBranchError";
1018
- }
1019
- };
1020
- function normalizeDurableMessage(message) {
1021
- return piMessageSchema.parse(
1022
- JSON.parse(JSON.stringify(sanitizePostgresJson(message)))
1023
- );
1024
- }
1025
- function durableMessageIdentity(message) {
1026
- const record = message;
1027
- if (record.role === "toolResult") {
1028
- return {
1029
- role: "toolResult",
1030
- timestamp: record.timestamp,
1031
- toolCallId: record.toolCallId,
1032
- toolName: record.toolName,
1033
- isError: record.isError,
1034
- content: record.content
1035
- };
1036
- }
1037
- return {
1038
- role: record.role,
1039
- timestamp: record.timestamp,
1040
- content: record.content
1041
- };
1042
- }
1043
- function countDurablePrefix(current, next) {
1044
- if (next.length < current.length) {
1045
- return countMatchingDeepPrefix(current, next);
1046
- }
1047
- for (let index = 0; index < current.length; index += 1) {
1048
- if (!isDeepStrictEqual3(
1049
- durableMessageIdentity(current[index]),
1050
- durableMessageIdentity(next[index])
1051
- )) {
1052
- return index;
1053
- }
1054
- }
1055
- return current.length;
1056
- }
1057
- function countMatchingDeepPrefix(left, right) {
1058
- const limit = Math.min(left.length, right.length);
1059
- for (let index = 0; index < limit; index += 1) {
1060
- if (!isDeepStrictEqual3(left[index], right[index])) {
1061
- return index;
1062
- }
1063
- }
1064
- return limit;
1065
- }
1066
- function resolveCommitProvenance(args) {
1067
- if (args.explicitProvenance) {
1068
- if (args.explicitProvenance.length !== args.nextMessages.length) {
1069
- throw new Error("commit provenance must align one-to-one with messages");
1070
- }
1071
- return args.explicitProvenance;
1072
- }
1073
- if (args.existing.provenance.length !== args.existing.messages.length) {
1074
- throw new Error("committed provenance must align one-to-one with messages");
1075
- }
1076
- const matchingPrefix = args.matchingPrefix;
1077
- const provenance = args.nextMessages.map(
1078
- (_, index) => index < matchingPrefix ? args.existing.provenance[index] : contextProvenance
1079
- );
1080
- if (args.newMessageProvenance) {
1081
- for (let index = args.nextMessages.length - 1; index >= matchingPrefix; index -= 1) {
1082
- if (isUserMessage(args.nextMessages[index])) {
1083
- provenance[index] = args.newMessageProvenance;
1084
- break;
1085
- }
1086
- }
1087
- }
1088
- if (args.trailingMessageProvenance) {
1089
- if (args.trailingMessageProvenance.length > provenance.length) {
1090
- throw new Error(
1091
- "trailing commit provenance cannot exceed committed messages"
1092
- );
1093
- }
1094
- const newMessageCount = args.nextMessages.length - matchingPrefix;
1095
- if (args.trailingMessageProvenance.length > newMessageCount) {
1096
- throw new Error(
1097
- "trailing commit provenance must align to newly committed messages"
1098
- );
1099
- }
1100
- const start = provenance.length - args.trailingMessageProvenance.length;
1101
- args.trailingMessageProvenance.forEach((entry, offset) => {
1102
- provenance[start + offset] = entry;
1103
- });
1104
- }
1105
- return provenance;
1106
- }
1107
- async function loadProjection(args) {
1108
- const events = await getConversationEventStore().loadCurrentHistory(
1109
- args.conversationId
1110
- );
1111
- return projectConversationEvents(events, {
1112
- defaultProfile: botConfig.defaultProfile
1113
- }).messages;
1114
- }
1115
- async function loadConversationProjection(args) {
1116
- const events = await getConversationEventStore().loadCurrentHistory(
1117
- args.conversationId
1118
- );
1119
- return projectConversationEvents(events, {
1120
- defaultProfile: botConfig.defaultProfile
1121
- });
1122
- }
1123
- async function openConversationProjection(args) {
1124
- const events = await getConversationEventStore().loadCurrentHistory(
1125
- args.conversationId
1126
- );
1127
- return projectConversationEvents(events, {
1128
- defaultProfile: botConfig.defaultProfile
1129
- });
1130
- }
1131
- async function loadTurnProjection(args) {
1132
- const eventStore = getConversationEventStore();
1133
- if (args.committedSeq < 0) {
1134
- return projectConversationEvents(
1135
- await eventStore.loadCurrentHistory(args.conversationId),
1136
- { defaultProfile: botConfig.defaultProfile }
1137
- );
1138
- }
1139
- const historyEvents = await eventStore.loadHistoryContaining(
1140
- args.conversationId,
1141
- args.committedSeq,
1142
- args.includeTail ? void 0 : args.committedSeq
1143
- );
1144
- if (!historyEvents) {
1145
- return void 0;
1146
- }
1147
- return projectConversationEvents(historyEvents, {
1148
- defaultProfile: botConfig.defaultProfile
1149
- });
1150
- }
1151
- async function loadConnectedMcpProviders(args) {
1152
- const events = await getConversationEventStore().loadHistory(
1153
- args.conversationId
1154
- );
1155
- return connectedMcpProvidersFromEvents(events, args.credentialSubjectId);
1156
- }
1157
- function messageTimestamp2(message) {
1158
- const timestamp = message.timestamp;
1159
- return typeof timestamp === "number" ? timestamp : Date.now();
1160
- }
1161
- async function commitMessages(args) {
1162
- const executor = args.executor ?? getSqlExecutor();
1163
- return await withConversationEventLock(
1164
- executor,
1165
- args.conversationId,
1166
- async () => executor.transaction(
1167
- async () => await commitMessagesLocked(args, executor)
1168
- )
1169
- );
1170
- }
1171
- async function commitAcceptedReply(args) {
1172
- const executor = getSqlExecutor();
1173
- await withConversationEventLock(
1174
- executor,
1175
- args.conversationId,
1176
- async () => executor.transaction(async () => {
1177
- if (args.agentMessage) {
1178
- const agentMessage = normalizeDurableMessage(args.agentMessage);
1179
- await createSqlConversationEventStore(executor).append(
1180
- args.conversationId,
1181
- [
1182
- {
1183
- idempotencyKey: `message:${args.conversationMessageId}:agent`,
1184
- data: historyItemFromPiMessage(agentMessage, contextProvenance),
1185
- createdAtMs: messageTimestamp2(agentMessage)
1186
- }
1187
- ]
1188
- );
1189
- }
1190
- await appendConversationMessages(
1191
- createSqlConversationEventStore(executor),
1192
- {
1193
- conversation: args.conversation,
1194
- conversationId: args.conversationId,
1195
- ...args.repliedAtMs === void 0 ? void 0 : { repliedAtMs: args.repliedAtMs }
1196
- }
1197
- );
1198
- for (const binding of args.providerConversationBindings ?? []) {
1199
- await bindProviderConversation(executor, {
1200
- conversationId: args.conversationId,
1201
- ...binding
1202
- });
1203
- }
1204
- })
1205
- );
1206
- }
1207
- async function commitMessagesLocked(args, executor) {
1208
- const eventStore = createSqlConversationEventStore(executor);
1209
- const currentEvents = await eventStore.loadCurrentHistory(
1210
- args.conversationId
1211
- );
1212
- const current = projectConversationEvents(currentEvents, {
1213
- defaultProfile: botConfig.defaultProfile
1214
- });
1215
- const nextLocalMessages = stripRuntimeTurnContext(args.messages).map(
1216
- normalizeDurableMessage
1217
- );
1218
- const matchingPrefix = countDurablePrefix(
1219
- current.messages,
1220
- nextLocalMessages
1221
- );
1222
- const nextLocalProvenance = resolveCommitProvenance({
1223
- existing: current,
1224
- nextMessages: nextLocalMessages,
1225
- matchingPrefix,
1226
- ...args.provenance ? { explicitProvenance: args.provenance } : void 0,
1227
- ...args.trailingMessageProvenance ? { trailingMessageProvenance: args.trailingMessageProvenance } : void 0,
1228
- ...args.newMessageProvenance ? { newMessageProvenance: args.newMessageProvenance } : void 0
1229
- });
1230
- if (matchingPrefix === current.messages.length) {
1231
- const newMessages = nextLocalMessages.slice(matchingPrefix);
1232
- const turnContext = args.turnContext;
1233
- const turnContextEvents = turnContext?.contexts.map((context, index) => ({
1234
- idempotencyKey: `turn:${turnContext.turnId}:context:${context.pluginName}:${index}`,
1235
- createdAtMs: context.loadedAtMs,
1236
- data: {
1237
- type: "turn_context",
1238
- turnId: turnContext.turnId,
1239
- pluginName: context.pluginName,
1240
- kind: context.kind,
1241
- version: context.version,
1242
- content: context.content
1243
- }
1244
- })) ?? [];
1245
- const appendedEvents = await eventStore.append(args.conversationId, [
1246
- ...newMessages.map((message, index) => ({
1247
- data: historyItemFromPiMessage(
1248
- message,
1249
- nextLocalProvenance[matchingPrefix + index]
1250
- ),
1251
- createdAtMs: messageTimestamp2(message)
1252
- })),
1253
- ...turnContextEvents
1254
- ]);
1255
- const cursor = appendedEvents.at(-1) ?? currentEvents.at(-1);
1256
- return {
1257
- committedSeq: cursor?.seq ?? -1,
1258
- historyVersion: cursor?.historyVersion ?? 0,
1259
- messageSeqs: [
1260
- ...current.seqs,
1261
- ...appendedEvents.slice(0, newMessages.length).map((event) => event.seq)
1262
- ],
1263
- messages: nextLocalMessages,
1264
- provenance: nextLocalProvenance
1265
- };
1266
- }
1267
- throw new AgentHistoryBranchError(args.conversationId);
1268
- }
1269
- async function recordMcpProviderConnected(args) {
1270
- const eventStore = getConversationEventStore();
1271
- const events = await eventStore.loadCurrentHistory(args.conversationId);
1272
- if (connectedMcpProvidersFromEvents(events, args.credentialSubjectId).includes(
1273
- args.provider
1274
- )) {
1275
- return;
1276
- }
1277
- await eventStore.append(args.conversationId, [
1278
- {
1279
- data: {
1280
- type: "mcp_provider_connected",
1281
- provider: args.provider,
1282
- credentialSubjectId: args.credentialSubjectId
1283
- },
1284
- createdAtMs: Date.now()
1285
- }
1286
- ]);
1287
- }
1288
- async function recordAuthorizationRequested(args) {
1289
- const eventStore = getConversationEventStore();
1290
- const events = await eventStore.loadCurrentHistory(args.conversationId);
1291
- if (events.some(
1292
- (event) => event.data.type === "authorization_requested" && event.data.authorizationId === args.authorizationId
1293
- )) {
1294
- return;
1295
- }
1296
- await eventStore.append(args.conversationId, [
1297
- {
1298
- data: {
1299
- type: "authorization_requested",
1300
- kind: args.kind,
1301
- provider: args.provider,
1302
- actorId: args.actorId,
1303
- authorizationId: args.authorizationId,
1304
- delivery: args.delivery
1305
- },
1306
- createdAtMs: Date.now()
1307
- }
1308
- ]);
1309
- }
1310
- async function recordAuthorizationCompleted(args) {
1311
- const eventStore = getConversationEventStore();
1312
- const events = await eventStore.loadCurrentHistory(args.conversationId);
1313
- if (events.some(
1314
- (event) => event.data.type === "authorization_completed" && event.data.authorizationId === args.authorizationId
1315
- )) {
1316
- return;
1317
- }
1318
- await eventStore.append(args.conversationId, [
1319
- {
1320
- data: {
1321
- type: "authorization_completed",
1322
- kind: args.kind,
1323
- provider: args.provider,
1324
- actorId: args.actorId,
1325
- authorizationId: args.authorizationId
1326
- },
1327
- createdAtMs: Date.now()
1328
- }
1329
- ]);
1330
- }
1331
- function authenticationEventIdempotencyKey(args) {
1332
- if (args.authorizationId) {
1333
- return `native:${JUNIOR_NATIVE_EVENT_NAMESPACE}:authentication_${args.action}:authorization:${args.authorizationId}`;
1334
- }
1335
- return `native:${JUNIOR_NATIVE_EVENT_NAMESPACE}:authentication_${args.action}:${args.provider}:actor:${args.actorId}`;
1336
- }
1337
- async function recordAuthenticationAccountChange(action, args) {
1338
- const definition = action === "linked" ? authenticationLinkedEvent : authenticationUnlinkedEvent;
1339
- const content = definition.parse({
1340
- actorId: args.actorId,
1341
- provider: args.provider,
1342
- ...args.accountLabel ? { accountLabel: args.accountLabel } : void 0,
1343
- ...args.authorizationId ? { authorizationId: args.authorizationId } : void 0,
1344
- ...args.providerLabel ? { providerLabel: args.providerLabel } : void 0
1345
- });
1346
- await getConversationEventStore().append(args.conversationId, [
1347
- {
1348
- createdAtMs: Date.now(),
1349
- idempotencyKey: authenticationEventIdempotencyKey({
1350
- action,
1351
- actorId: args.actorId,
1352
- authorizationId: args.authorizationId,
1353
- provider: args.provider
1354
- }),
1355
- data: {
1356
- type: "structured_event",
1357
- namespace: JUNIOR_NATIVE_EVENT_NAMESPACE,
1358
- name: definition.eventName,
1359
- version: definition.version,
1360
- ...args.turnId ? { turnId: args.turnId } : void 0,
1361
- content
1362
- }
1363
- }
1364
- ]);
1365
- }
1366
- async function recordAuthenticationLinked(args) {
1367
- await recordAuthenticationAccountChange("linked", args);
1368
- }
1369
- async function loadAgentsEvent(conversationId) {
1370
- const event = await getConversationEventStore().loadLatestStructuredEvent(
1371
- conversationId,
1372
- JUNIOR_NATIVE_EVENT_NAMESPACE,
1373
- agentsInstructionsUpdatedEvent.eventName
1374
- );
1375
- if (!event || event.data.type !== "structured_event") return void 0;
1376
- return {
1377
- content: agentsInstructionsUpdatedEvent.parse(
1378
- event.data.content
1379
- ),
1380
- seq: event.seq
1381
- };
1382
- }
1383
- function agentsInstructionsIdempotencyKey(args) {
1384
- const previous = args.previousSeq ?? "none";
1385
- return `native:${JUNIOR_NATIVE_EVENT_NAMESPACE}:agents_instructions_updated:${args.turnId}:${args.action}:${previous}->${args.fingerprint}`;
1386
- }
1387
- async function recordAgentsInstructionsUpdated(args) {
1388
- const previous = await loadAgentsEvent(args.conversationId);
1389
- const instructions = args.instructions;
1390
- if (previous && (!instructions && previous.content.action === "cleared" || instructions && previous.content.action !== "cleared" && previous.content.fingerprint === instructions.fingerprint && previous.content.directory === instructions.directory)) {
1391
- return;
1392
- }
1393
- const action = !instructions ? "cleared" : previous && previous.content.action !== "cleared" ? "replaced" : "loaded";
1394
- const fingerprint = instructions?.fingerprint ?? "cleared";
1395
- const content = agentsInstructionsUpdatedEvent.parse({
1396
- action,
1397
- fingerprint,
1398
- sources: instructions?.sources ?? [],
1399
- ...instructions ? { directory: instructions.directory } : void 0,
1400
- ...instructions ? { textBytes: Buffer.byteLength(instructions.text, "utf8") } : void 0
1401
- });
1402
- await getConversationEventStore().append(args.conversationId, [
1403
- {
1404
- createdAtMs: Date.now(),
1405
- idempotencyKey: agentsInstructionsIdempotencyKey({
1406
- action,
1407
- fingerprint,
1408
- previousSeq: previous?.seq,
1409
- turnId: args.turnId
1410
- }),
1411
- data: {
1412
- type: "structured_event",
1413
- namespace: JUNIOR_NATIVE_EVENT_NAMESPACE,
1414
- name: agentsInstructionsUpdatedEvent.eventName,
1415
- version: agentsInstructionsUpdatedEvent.version,
1416
- turnId: args.turnId,
1417
- content
1418
- }
1419
- }
1420
- ]);
1421
- }
1422
- async function recordSubscribedReplyRoute(args) {
1423
- await getConversationEventStore().append(args.conversationId, [
1424
- {
1425
- idempotencyKey: `native:${JUNIOR_NATIVE_EVENT_NAMESPACE}:passive_reply_routed:${args.runId}`,
1426
- createdAtMs: Date.now(),
1427
- data: {
1428
- type: "structured_event",
1429
- namespace: JUNIOR_NATIVE_EVENT_NAMESPACE,
1430
- name: "passive_reply_routed",
1431
- version: 1,
1432
- content: {
1433
- costUsd: args.costUsd,
1434
- reason: args.reason,
1435
- shouldReply: args.shouldReply,
1436
- ...args.shouldUnsubscribe !== void 0 ? { shouldUnsubscribe: args.shouldUnsubscribe } : void 0
1437
- }
1438
- }
1439
- }
1440
- ]);
1441
- }
1442
- async function loadTurnRoute(args) {
1443
- const event = await getConversationEventStore().loadByIdempotencyKey(
1444
- args.conversationId,
1445
- `turn:${args.turnId}:routed`
1446
- );
1447
- if (!event) {
1448
- return void 0;
1449
- }
1450
- if (event.data.type !== "turn_routed" || event.data.turnId !== args.turnId) {
1451
- throw new Error(`Turn route key for "${args.turnId}" has invalid data`);
1452
- }
1453
- return { ...event.data, seq: event.seq };
1454
- }
1455
- async function recordTurnRoute(args) {
1456
- await getConversationEventStore().append(args.conversationId, [
1457
- {
1458
- idempotencyKey: `turn:${args.turnId}:routed`,
1459
- createdAtMs: Date.now(),
1460
- data: {
1461
- type: "turn_routed",
1462
- turnId: args.turnId,
1463
- modelProfile: args.modelProfile,
1464
- modelId: args.modelId,
1465
- ...args.costUsd !== void 0 ? { costUsd: args.costUsd } : void 0,
1466
- reasoningLevel: args.reasoningLevel,
1467
- ...args.confidence !== void 0 ? { confidence: args.confidence } : void 0,
1468
- source: args.source
1469
- }
1470
- }
1471
- ]);
1472
- }
1473
- async function recordToolExecutionStarted(args) {
1474
- await getConversationEventStore().append(args.conversationId, [
1475
- {
1476
- data: {
1477
- type: "tool_execution_started",
1478
- toolCallId: args.toolCallId,
1479
- toolName: args.toolName
1480
- },
1481
- createdAtMs: args.createdAtMs ?? Date.now()
1482
- }
1483
- ]);
1484
- }
1485
- function attachmentsDeliveredIdempotencyKey(args) {
1486
- if (args.toolCallId) {
1487
- return `attachments_delivered:tool:${args.toolCallId}`;
1488
- }
1489
- if (args.turnId) {
1490
- return `attachments_delivered:turn:${args.turnId}:` + args.attachments.map((attachment) => attachment.id).sort().join(",");
1491
- }
1492
- return `attachments_delivered:${args.conversationId}:` + args.attachments.map((attachment) => attachment.id).sort().join(",");
1493
- }
1494
- async function recordAttachmentsDelivered(args) {
1495
- if (args.attachments.length === 0) return;
1496
- await getConversationEventStore().append(args.conversationId, [
1497
- {
1498
- // Retries after a successful Slack upload must not create duplicate rows.
1499
- idempotencyKey: attachmentsDeliveredIdempotencyKey({
1500
- attachments: args.attachments,
1501
- conversationId: args.conversationId,
1502
- ...args.toolCallId ? { toolCallId: args.toolCallId } : void 0,
1503
- ...args.turnId ? { turnId: args.turnId } : void 0
1504
- }),
1505
- data: {
1506
- type: "attachments_delivered",
1507
- attachments: args.attachments.map((attachment) => ({
1508
- id: attachment.id,
1509
- filename: attachment.filename,
1510
- contentType: attachment.contentType,
1511
- bytes: attachment.bytes
1512
- })),
1513
- ...args.toolCallId ? { toolCallId: args.toolCallId } : void 0,
1514
- ...args.turnId ? { turnId: args.turnId } : void 0
1515
- },
1516
- createdAtMs: args.createdAtMs ?? Date.now()
1517
- }
1518
- ]);
1519
- }
1520
- async function recordGuardianActionReviewed(args) {
1521
- await getConversationEventStore().append(args.conversationId, [
1522
- {
1523
- data: {
1524
- type: "guardian_action_reviewed",
1525
- turnId: args.turnId,
1526
- toolCallId: args.toolCallId,
1527
- toolName: args.toolName,
1528
- ...args.costUsd !== void 0 ? { costUsd: args.costUsd } : void 0,
1529
- decision: args.decision,
1530
- riskLevel: args.riskLevel,
1531
- userAuthorization: args.userAuthorization
1532
- },
1533
- createdAtMs: Date.now()
1534
- }
1535
- ]);
1536
- }
1537
-
1538
- // src/chat/services/persist-retry.ts
1539
- var PERSIST_ATTEMPTS = 3;
1540
- async function persistWithRetry(persist) {
1541
- let lastError;
1542
- for (let attempt = 1; attempt <= PERSIST_ATTEMPTS; attempt += 1) {
1543
- try {
1544
- await persist();
1545
- return;
1546
- } catch (error) {
1547
- lastError = error;
1548
- if (attempt < PERSIST_ATTEMPTS) {
1549
- await sleep(attempt * 100);
1550
- }
1551
- }
1552
- }
1553
- throw lastError;
1554
- }
1555
-
1556
- // src/chat/task-execution/turn-cursor.ts
1557
- import {
1558
- actorSchema,
1559
- sourceSchema
1560
- } from "@sentry/junior-plugin-api";
1561
- import { z as z2 } from "zod";
1562
-
1563
- // src/chat/repository-instructions.ts
1564
- import { createHash as createHash2 } from "crypto";
1565
- import path2 from "path";
1566
-
1567
- // src/chat/tools/sandbox/file-utils.ts
1568
- import path from "path";
1569
- var MAX_TEXT_CHARS = 6e4;
1570
- var RIPGREP_EXCLUDED_GLOBS = [
1571
- "!**/.git/**",
1572
- "!**/node_modules/**"
1573
- ];
1574
- var DEFAULT_SEARCH_COMMAND_TIMEOUT_MS = 3e4;
1575
- var SKIPPED_DIRECTORIES = /* @__PURE__ */ new Set([".git", "node_modules"]);
1576
- function positiveInteger2(value) {
1577
- if (typeof value !== "number" || !Number.isFinite(value)) {
1578
- return void 0;
1579
- }
1580
- const integer = Math.floor(value);
1581
- return integer > 0 ? integer : void 0;
1582
- }
1583
- function normalizeToLf(value) {
1584
- return value.replace(/\r\n/g, "\n").replace(/\r/g, "\n");
1585
- }
1586
- function truncateText(value, maxChars = MAX_TEXT_CHARS) {
1587
- if (value.length <= maxChars) {
1588
- return { content: value, truncated: false };
1589
- }
1590
- const removed = value.length - maxChars;
1591
- return {
1592
- content: `${value.slice(0, maxChars)}
1593
-
1594
- [output truncated: ${removed} characters removed]`,
1595
- truncated: true
1596
- };
1597
- }
1598
- function isMissingPathError(error) {
1599
- if (!error || typeof error !== "object") {
1600
- return false;
1601
- }
1602
- const candidate = error;
1603
- if (candidate.code === "ENOENT") {
1604
- return true;
1605
- }
1606
- return typeof candidate.message === "string" && candidate.message.startsWith("File not found:");
1607
- }
1608
- function missingPathSearchResult(params) {
1609
- const textPath = params.displayPath ?? params.missingPath ?? params.path;
1610
- throw new ToolInputError(`Path not found: ${textPath}`);
1611
- }
1612
- function escapeRegExp(value) {
1613
- return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
1614
- }
1615
- function globToRegExp(pattern) {
1616
- let source = "";
1617
- for (let index = 0; index < pattern.length; index += 1) {
1618
- const char = pattern[index];
1619
- const next = pattern[index + 1];
1620
- if (char === "*" && next === "*") {
1621
- if (pattern[index + 2] === "/") {
1622
- source += "(?:.*/)?";
1623
- index += 2;
1624
- continue;
1625
- }
1626
- source += ".*";
1627
- index += 1;
1628
- continue;
1629
- }
1630
- if (char === "*") {
1631
- source += "[^/]*";
1632
- continue;
1633
- }
1634
- if (char === "?") {
1635
- source += "[^/]";
1636
- continue;
1637
- }
1638
- source += escapeRegExp(char);
1639
- }
1640
- return new RegExp(`^${source}$`);
1641
- }
1642
- function matchesGlob(relativePath, pattern) {
1643
- const matcher = globToRegExp(pattern);
1644
- if (matcher.test(relativePath)) {
1645
- return true;
1646
- }
1647
- if (pattern.startsWith("**/") && matchesGlob(relativePath, pattern.slice(3))) {
1648
- return true;
1649
- }
1650
- return !pattern.includes("/") && matcher.test(path.posix.basename(relativePath));
1651
- }
1652
- function resolveWorkspacePath(input, fallback = ".") {
1653
- const requested = (input ?? "").trim() || fallback;
1654
- const absolute = requested.startsWith("/") ? requested : path.posix.join(SANDBOX_WORKSPACE_ROOT, requested);
1655
- const normalized = path.posix.normalize(absolute);
1656
- if (normalized !== SANDBOX_WORKSPACE_ROOT && !normalized.startsWith(`${SANDBOX_WORKSPACE_ROOT}/`)) {
1657
- throw new ToolInputError(
1658
- `Path must stay within ${SANDBOX_WORKSPACE_ROOT}: ${requested}`
1659
- );
1660
- }
1661
- return normalized;
1662
- }
1663
- function getRipgrepSearchLocation(root, rootIsDirectory) {
1664
- if (rootIsDirectory) {
1665
- return { cwd: root, target: "." };
1666
- }
1667
- return {
1668
- cwd: path.posix.dirname(root),
1669
- target: path.posix.basename(root)
1670
- };
1671
- }
1672
- async function collectFiles(params) {
1673
- const files = [];
1674
- let limitReached = false;
1675
- let missingPath;
1676
- const visit = async (dirPath) => {
1677
- if (missingPath) {
1678
- return;
1679
- }
1680
- let entries;
1681
- try {
1682
- entries = (await params.fs.readdir(dirPath)).sort(
1683
- (a, b) => a.toLowerCase().localeCompare(b.toLowerCase())
1684
- );
1685
- } catch (error) {
1686
- if (isMissingPathError(error)) {
1687
- missingPath = dirPath;
1688
- return;
1689
- }
1690
- throw error;
1691
- }
1692
- for (const entry of entries) {
1693
- const fullPath = path.posix.join(dirPath, entry);
1694
- let stat2;
1695
- try {
1696
- stat2 = await params.fs.stat(fullPath);
1697
- } catch (error) {
1698
- if (isMissingPathError(error)) {
1699
- missingPath = fullPath;
1700
- return;
1701
- }
1702
- throw error;
1703
- }
1704
- if (stat2.isDirectory()) {
1705
- if (!SKIPPED_DIRECTORIES.has(entry)) {
1706
- await visit(fullPath);
1707
- }
1708
- if (limitReached) return;
1709
- continue;
1710
- }
1711
- const relativePath = path.posix.relative(params.root, fullPath);
1712
- if (!params.pattern || matchesGlob(relativePath, params.pattern)) {
1713
- files.push(fullPath);
1714
- if (params.limit && files.length >= params.limit) {
1715
- limitReached = true;
1716
- return;
1717
- }
1718
- }
1719
- }
1720
- };
1721
- let stat;
1722
- try {
1723
- stat = await params.fs.stat(params.root);
1724
- } catch (error) {
1725
- if (isMissingPathError(error)) {
1726
- return {
1727
- files,
1728
- limitReached: false,
1729
- missingPath: params.root,
1730
- missingRoot: true
1731
- };
1732
- }
1733
- throw error;
1734
- }
1735
- if (!stat.isDirectory()) {
1736
- const relativePath = path.posix.basename(params.root);
1737
- return {
1738
- files: !params.pattern || matchesGlob(relativePath, params.pattern) ? [params.root] : [],
1739
- limitReached: false,
1740
- missingRoot: false
1741
- };
1742
- }
1743
- await visit(params.root);
1744
- return {
1745
- files,
1746
- limitReached,
1747
- missingPath,
1748
- missingRoot: missingPath === params.root
1749
- };
1750
- }
1751
-
1752
- // src/chat/repository-instructions.ts
1753
- var AGENTS_FILENAME = "AGENTS.md";
1754
- var MAX_REPOSITORY_INSTRUCTIONS_BYTES = 32 * 1024;
1755
- var AGENTS_REPLACEMENT_NOTICE = "These AGENTS.md instructions replace all previously provided AGENTS.md instructions.";
1756
- var AGENTS_REMOVAL_NOTICE = "The previously provided AGENTS.md instructions no longer apply.";
1757
- async function pathExists(fs, target) {
1758
- try {
1759
- await fs.stat(target);
1760
- return true;
1761
- } catch (error) {
1762
- if (isMissingPathError(error)) {
1763
- return false;
1764
- }
1765
- throw error;
1766
- }
1767
- }
1768
- async function directoryExists(fs, target) {
1769
- try {
1770
- return (await fs.stat(target)).isDirectory();
1771
- } catch (error) {
1772
- if (isMissingPathError(error)) {
1773
- return false;
1774
- }
1775
- throw error;
1776
- }
1777
- }
1778
- async function findGitRoot(fs, cwd) {
1779
- let current = cwd;
1780
- for (; ; ) {
1781
- if (await pathExists(fs, path2.posix.join(current, ".git"))) {
1782
- return current;
1783
- }
1784
- if (current === SANDBOX_WORKSPACE_ROOT) {
1785
- return void 0;
1786
- }
1787
- const parent = path2.posix.dirname(current);
1788
- if (parent === current || parent !== SANDBOX_WORKSPACE_ROOT && !parent.startsWith(`${SANDBOX_WORKSPACE_ROOT}/`)) {
1789
- return void 0;
1790
- }
1791
- current = parent;
1792
- }
1793
- }
1794
- async function listRepositoryDirectories(fs) {
1795
- if (!await directoryExists(fs, SANDBOX_REPOS_ROOT)) {
1796
- return [];
1797
- }
1798
- const entries = await fs.readdir(SANDBOX_REPOS_ROOT);
1799
- const repositories = [];
1800
- for (const entry of entries) {
1801
- const candidate = path2.posix.join(SANDBOX_REPOS_ROOT, entry);
1802
- if (await directoryExists(fs, candidate) && await pathExists(fs, path2.posix.join(candidate, ".git"))) {
1803
- repositories.push(candidate);
1804
- }
1805
- }
1806
- return repositories.sort(
1807
- (left, right) => left < right ? -1 : left > right ? 1 : 0
1808
- );
1809
- }
1810
- async function readInstructionFile(fs, filePath) {
1811
- try {
1812
- const content = await fs.readFile(filePath, { encoding: "utf8" });
1813
- return content.trim() ? content : void 0;
1814
- } catch (error) {
1815
- if (isMissingPathError(error)) {
1816
- return void 0;
1817
- }
1818
- throw error;
1819
- }
1820
- }
1821
- async function resolveRepositoryInstructions(args) {
1822
- const gitRoot = await findGitRoot(args.fs, args.cwd);
1823
- if (!gitRoot) {
1824
- return void 0;
1825
- }
1826
- const directories = [];
1827
- let current = args.cwd;
1828
- for (; ; ) {
1829
- directories.push(current);
1830
- if (current === gitRoot) {
1831
- break;
1832
- }
1833
- current = path2.posix.dirname(current);
1834
- }
1835
- directories.reverse();
1836
- const sources = [];
1837
- let remaining = Math.max(
1838
- 0,
1839
- args.maxBytes ?? MAX_REPOSITORY_INSTRUCTIONS_BYTES
1840
- );
1841
- for (const directory of directories) {
1842
- if (remaining <= 0) {
1843
- break;
1844
- }
1845
- const filePath = path2.posix.join(directory, AGENTS_FILENAME);
1846
- const content = await readInstructionFile(args.fs, filePath);
1847
- if (!content) {
1848
- continue;
1849
- }
1850
- const bytes = Buffer.from(content, "utf8");
1851
- const bounded = bytes.length <= remaining ? content : bytes.subarray(0, remaining).toString("utf8");
1852
- sources.push({ content: bounded, path: filePath });
1853
- remaining -= Buffer.byteLength(bounded, "utf8");
1854
- }
1855
- if (sources.length === 0) {
1856
- return void 0;
1857
- }
1858
- const text = formatInstructionSources(sources);
1859
- const fingerprint = createHash2("sha256").update(JSON.stringify({ directory: args.cwd, sources })).digest("hex");
1860
- return { directory: args.cwd, fingerprint, sources, text };
1861
- }
1862
- function formatInstructionSources(sources) {
1863
- if (sources.length === 1) {
1864
- return sources[0].content;
1865
- }
1866
- return sources.map(
1867
- (source) => `## ${path2.posix.dirname(source.path)}
1868
-
1869
- ${source.content}`
1870
- ).join("\n\n");
1871
- }
1872
- function mergeRepositoryInstructions(bundles) {
1873
- if (bundles.length === 0) {
1874
- return void 0;
1875
- }
1876
- if (bundles.length === 1) {
1877
- return bundles[0];
1878
- }
1879
- const sources = bundles.flatMap((bundle) => bundle.sources);
1880
- const text = formatInstructionSources(sources);
1881
- const fingerprint = createHash2("sha256").update(
1882
- JSON.stringify({
1883
- directories: bundles.map((bundle) => bundle.directory ?? null),
1884
- sources
1885
- })
1886
- ).digest("hex");
1887
- return { fingerprint, sources, text };
1888
- }
1889
- async function resolveRepositoryInstructionsForDirectories(args) {
1890
- const uniqueDirectories = [
1891
- ...new Set(
1892
- args.directories.filter(
1893
- (directory) => directory === SANDBOX_WORKSPACE_ROOT || directory.startsWith(`${SANDBOX_WORKSPACE_ROOT}/`)
1894
- )
1895
- )
1896
- ].sort((left, right) => left < right ? -1 : left > right ? 1 : 0);
1897
- let remaining = MAX_REPOSITORY_INSTRUCTIONS_BYTES;
1898
- const bundles = [];
1899
- for (const directory of uniqueDirectories) {
1900
- if (remaining <= 0) {
1901
- break;
1902
- }
1903
- const instructions = await resolveRepositoryInstructions({
1904
- cwd: directory,
1905
- fs: args.fs,
1906
- maxBytes: remaining
1907
- });
1908
- if (!instructions) {
1909
- continue;
1910
- }
1911
- bundles.push(instructions);
1912
- remaining -= instructions.sources.reduce(
1913
- (total, source) => total + Buffer.byteLength(source.content, "utf8"),
1914
- 0
1915
- );
1916
- }
1917
- return mergeRepositoryInstructions(bundles);
1918
- }
1919
- function renderAgentsInstructions(args) {
1920
- const directory = args.directory ? ` for ${args.directory}` : "";
1921
- return `# AGENTS.md instructions${directory}
1922
-
1923
- <INSTRUCTIONS>
1924
- ${args.text}
1925
- </INSTRUCTIONS>`;
1926
- }
1927
- function buildAgentsInstructionsMessage(args) {
1928
- return {
1929
- role: "user",
1930
- content: [{ type: "text", text: renderAgentsInstructions(args) }],
1931
- timestamp: args.timestamp ?? Date.now()
1932
- };
1933
- }
1934
- function findVisibleAgentsInstructions(messages) {
1935
- for (let messageIndex = messages.length - 1; messageIndex >= 0; messageIndex -= 1) {
1936
- const message = messages[messageIndex];
1937
- if (message.role !== "user" || !Array.isArray(message.content)) {
1938
- continue;
1939
- }
1940
- for (let partIndex = message.content.length - 1; partIndex >= 0; partIndex -= 1) {
1941
- const part = message.content[partIndex];
1942
- if (part.type !== "text" || typeof part.text !== "string" || !part.text.startsWith("# AGENTS.md instructions")) {
1943
- continue;
1944
- }
1945
- const match = part.text.match(
1946
- /^# AGENTS\.md instructions(?: for ([^\n]+))?\n\n<INSTRUCTIONS>\n([\s\S]*)\n<\/INSTRUCTIONS>$/
1947
- );
1948
- if (!match) {
1949
- continue;
1950
- }
1951
- let text = match[2];
1952
- if (text === AGENTS_REMOVAL_NOTICE) {
1953
- return { active: false };
1954
- }
1955
- if (text.startsWith(`${AGENTS_REPLACEMENT_NOTICE}
1956
-
1957
- `)) {
1958
- text = text.slice(AGENTS_REPLACEMENT_NOTICE.length + 2);
1959
- }
1960
- return {
1961
- active: true,
1962
- ...match[1] ? { directory: match[1] } : void 0,
1963
- text
1964
- };
1965
- }
1966
- }
1967
- return void 0;
1968
- }
1969
- function isAgentsInstructionsMessage(message) {
1970
- return Boolean(findVisibleAgentsInstructions([message]));
1971
- }
1972
-
1973
- // src/chat/task-execution/turn-cursor-keys.ts
1974
- var TURN_CURSOR_PREFIX = "junior:turn_cursor:v2";
1975
- function turnCursorKey(conversationId, turnId) {
1976
- return `${TURN_CURSOR_PREFIX}:${conversationId}:${turnId}`;
1977
- }
1978
- function turnCursorIndexKey(conversationId) {
1979
- return `${TURN_CURSOR_PREFIX}:conversation:${conversationId}:index`;
1980
- }
1981
- function turnCursorMutationKey(conversationId, turnId) {
1982
- return `${TURN_CURSOR_PREFIX}:mutation:${conversationId}:${turnId}`;
1983
- }
1984
-
1985
- // src/chat/task-execution/turn-cursor.ts
1986
- var TURN_CURSOR_SCHEMA_VERSION = 2;
1987
- var TURN_CURSOR_RESUME_TTL_MS = 24 * 60 * 60 * 1e3;
1988
- var TURN_CURSOR_TERMINAL_TTL_MS = 60 * 60 * 1e3;
1989
- var TURN_CURSOR_MUTATION_LOCK_WAIT_MS = 1e4;
1990
- async function withTurnCursorMutation(conversationId, turnId, run) {
1991
- const state = getStateAdapter();
1992
- await state.connect();
1993
- const result = await withLock(
1994
- state,
1995
- turnCursorMutationKey(conversationId, turnId),
1996
- async (lock) => await run(async () => await fenceLock(state, lock)),
1997
- {
1998
- keepAlive: true,
1999
- ttlMs: MUTATION_LOCK_TTL_MS,
2000
- waitMs: TURN_CURSOR_MUTATION_LOCK_WAIT_MS
2001
- }
2002
- );
2003
- if (!result.acquired) {
2004
- throw new Error(`Could not acquire turn cursor lock for ${turnId}`);
2005
- }
2006
- return result.value;
2007
- }
2008
- function executionMetricsForTurn(conversation, turnId) {
2009
- return conversation?.executionMetrics?.runId === turnId ? conversation.executionMetrics : void 0;
2010
- }
2011
- function definedProps(values) {
2012
- return Object.fromEntries(
2013
- Object.entries(values).filter(([, value]) => value !== void 0)
2014
- );
2015
- }
2016
- var turnStatusSchema = z2.enum([
2017
- "running",
2018
- "paused",
2019
- "completed",
2020
- "failed",
2021
- "abandoned"
2022
- ]);
2023
- var turnSurfaceSchema = z2.enum([
2024
- "slack",
2025
- "api",
2026
- "scheduler",
2027
- "internal"
2028
- ]);
2029
- var turnPauseReasonSchema = z2.enum([
2030
- "timeout",
2031
- "auth",
2032
- "yield",
2033
- "retry"
2034
- ]);
2035
- var nonNegativeNumberSchema = z2.number().finite().nonnegative();
2036
- var seqCursorSchema = z2.number().int().min(-1);
2037
- var storedTurnSummarySchema = z2.object({
2038
- schemaVersion: z2.literal(TURN_CURSOR_SCHEMA_VERSION),
2039
- version: z2.number().int().nonnegative(),
2040
- conversationId: z2.string().min(1),
2041
- dispatchId: z2.string().min(1).optional(),
2042
- dispatchOutcome: z2.enum(["blocked", "completed", "failed"]).optional(),
2043
- resultMessageId: z2.string().min(1).optional(),
2044
- resumeReason: turnPauseReasonSchema.optional(),
2045
- turnId: z2.string().min(1),
2046
- sliceId: z2.number().int().nonnegative(),
2047
- state: turnStatusSchema,
2048
- surface: turnSurfaceSchema.optional(),
2049
- updatedAtMs: nonNegativeNumberSchema
2050
- }).strip();
2051
- var storedTurnRecordSchema = z2.object({
2052
- // TODO(dcramer): Require Actor after no deployed Turn cursor can omit it.
2053
- actor: actorSchema.optional(),
2054
- schemaVersion: z2.literal(TURN_CURSOR_SCHEMA_VERSION),
2055
- version: z2.number().int().nonnegative(),
2056
- conversationId: z2.string().min(1),
2057
- dispatchId: z2.string().min(1).optional(),
2058
- dispatchOutcome: z2.enum(["blocked", "completed", "failed"]).optional(),
2059
- resultMessageId: z2.string().min(1).optional(),
2060
- lastProgressAtMs: nonNegativeNumberSchema,
2061
- resumeReason: turnPauseReasonSchema.optional(),
2062
- publishExternally: z2.boolean().optional(),
2063
- source: sourceSchema.optional(),
2064
- resumedFromSliceId: z2.number().int().nonnegative().optional(),
2065
- turnId: z2.string().min(1),
2066
- sliceId: z2.number().int().nonnegative(),
2067
- startedAtMs: nonNegativeNumberSchema,
2068
- state: turnStatusSchema,
2069
- surface: turnSurfaceSchema.optional(),
2070
- traceId: z2.string().optional(),
2071
- updatedAtMs: nonNegativeNumberSchema,
2072
- committedSeq: seqCursorSchema,
2073
- historyVersion: z2.number().int().nonnegative().optional(),
2074
- errorMessage: z2.string().optional(),
2075
- cumulativeToolCallCount: z2.number().int().nonnegative().optional(),
2076
- turnStartSeq: seqCursorSchema.optional(),
2077
- runtimeContext: z2.array(piMessageSchema).optional()
2078
- }).strip();
2079
- function conversationExecutionFromSummary(summary) {
2080
- const status = summary.state === "completed" || summary.state === "abandoned" ? "idle" : summary.state;
2081
- return {
2082
- status,
2083
- runId: summary.turnId,
2084
- updatedAtMs: summary.updatedAtMs
2085
- };
2086
- }
2087
- function sessionLogActor(actor) {
2088
- return actor?.platform === "slack" ? toStoredSlackActor(actor) : void 0;
2089
- }
2090
- function parseTurnRecord(value) {
2091
- return storedTurnRecordSchema.parse(value);
2092
- }
2093
- function parseTurnSummary(value) {
2094
- return storedTurnSummarySchema.parse(value);
2095
- }
2096
- function storeTurnSummary(summary) {
2097
- return storedTurnSummarySchema.parse(summary);
2098
- }
2099
- function turnCursorTtlMs(state, ttlMs) {
2100
- if (ttlMs !== void 0) {
2101
- return Math.max(1, ttlMs);
2102
- }
2103
- return state === "running" || state === "paused" ? TURN_CURSOR_RESUME_TTL_MS : TURN_CURSOR_TERMINAL_TTL_MS;
2104
- }
2105
- function turnCursorIndexTtlMs(ttlMs) {
2106
- return Math.max(1, ttlMs ?? TURN_CURSOR_RESUME_TTL_MS);
2107
- }
2108
- function projectTurnSummary(record) {
2109
- return parseTurnSummary(record);
2110
- }
2111
- async function appendTurnSummary(summary, ttlMs) {
2112
- const stateAdapter = getStateAdapter();
2113
- await stateAdapter.appendToList(
2114
- turnCursorIndexKey(summary.conversationId),
2115
- storeTurnSummary(summary),
2116
- { ttlMs }
2117
- );
2118
- }
2119
- async function recordConversationActivityMetadata(args) {
2120
- const conversationStore = args.conversationStore ?? getConversationStore();
2121
- const conversation = await conversationStore.get({
2122
- conversationId: args.summary.conversationId
2123
- });
2124
- const hasParent = Boolean(conversation?.parentConversationId);
2125
- const destination = hasParent ? void 0 : args.destination;
2126
- if (!hasParent && !destination && !conversation) {
2127
- return;
2128
- }
2129
- const activitySource = hasParent ? "internal" : args.source?.kind === "web" ? "web" : destination?.platform === "local" ? "local" : destination ? args.summary.surface : void 0;
2130
- await conversationStore.recordActivity({
2131
- activityAtMs: args.summary.updatedAtMs,
2132
- channelName: args.summary.channelName,
2133
- conversationId: args.summary.conversationId,
2134
- destination,
2135
- nowMs: args.nowMs,
2136
- actor: sessionLogActor(args.actor),
2137
- ...definedProps({ source: activitySource }),
2138
- ...args.source ? { sessionSource: args.source } : void 0,
2139
- visibility: hasParent ? void 0 : args.destinationVisibility
2140
- });
2141
- await conversationStore.recordExecution({
2142
- channelName: args.summary.channelName,
2143
- conversationId: args.summary.conversationId,
2144
- createdAtMs: args.summary.startedAtMs,
2145
- destination,
2146
- execution: conversationExecutionFromSummary(args.summary),
2147
- lastActivityAtMs: args.summary.updatedAtMs,
2148
- metrics: {
2149
- durationMs: args.summary.cumulativeDurationMs,
2150
- ...args.summary.cumulativeUsage ? { usage: args.summary.cumulativeUsage } : void 0
2151
- },
2152
- actor: sessionLogActor(args.actor),
2153
- ...definedProps({ source: activitySource }),
2154
- updatedAtMs: args.nowMs,
2155
- visibility: hasParent ? void 0 : args.destinationVisibility
2156
- });
2157
- }
2158
- function materializeTurnRecord(stored, piProjection, turnStartMessageIndex, restoreVolatileContext = true, runtimeMetrics) {
2159
- const restoredProjection = restoreVolatileContext && (stored.state === "running" || stored.state === "paused") ? restoreRuntimeContext(piProjection, stored.runtimeContext) : piProjection;
2160
- return {
2161
- ...definedProps({ actor: stored.actor }),
2162
- schemaVersion: stored.schemaVersion,
2163
- version: stored.version,
2164
- conversationId: stored.conversationId,
2165
- turnId: stored.turnId,
2166
- sliceId: stored.sliceId,
2167
- state: stored.state,
2168
- startedAtMs: stored.startedAtMs,
2169
- lastProgressAtMs: stored.lastProgressAtMs,
2170
- updatedAtMs: stored.updatedAtMs,
2171
- piMessages: restoredProjection.messages,
2172
- piMessageProvenance: restoredProjection.provenance,
2173
- actors: instructionActors(piProjection.provenance),
2174
- cumulativeDurationMs: runtimeMetrics?.cumulativeDurationMs ?? 0,
2175
- ...definedProps({
2176
- channelName: runtimeMetrics?.channelName,
2177
- cumulativeToolCallCount: stored.cumulativeToolCallCount,
2178
- cumulativeUsage: runtimeMetrics?.cumulativeUsage,
2179
- dispatchId: stored.dispatchId,
2180
- dispatchOutcome: stored.dispatchOutcome,
2181
- errorMessage: stored.errorMessage,
2182
- resumeReason: stored.resumeReason,
2183
- source: stored.source,
2184
- resultMessageId: stored.resultMessageId,
2185
- resumedFromSliceId: stored.resumedFromSliceId,
2186
- surface: stored.surface,
2187
- traceId: stored.traceId,
2188
- turnStartMessageIndex
2189
- })
2190
- };
2191
- }
2192
- function restoreRuntimeContext(projection, runtimeContext) {
2193
- if (!runtimeContext || runtimeContext.length === 0) return projection;
2194
- const restoredMessages = [...projection.messages];
2195
- const restoredProvenance = [...projection.provenance];
2196
- const unmatchedRuntimeContext = [];
2197
- for (const runtimeMessage of runtimeContext) {
2198
- const runtime = runtimeMessage;
2199
- const targetIndex = restoredMessages.findIndex((message) => {
2200
- const candidate = message;
2201
- return candidate.role === "user" && candidate.timestamp === runtime.timestamp;
2202
- });
2203
- if (targetIndex < 0) {
2204
- if (isAgentsInstructionsMessage(runtimeMessage)) {
2205
- const followingIndex = restoredMessages.findIndex((message) => {
2206
- const timestamp = message.timestamp;
2207
- return typeof runtime.timestamp === "number" && typeof timestamp === "number" && timestamp > runtime.timestamp;
2208
- });
2209
- const insertionIndex = followingIndex < 0 ? restoredMessages.length : followingIndex;
2210
- restoredMessages.splice(insertionIndex, 0, runtimeMessage);
2211
- restoredProvenance.splice(insertionIndex, 0, contextProvenance);
2212
- } else {
2213
- unmatchedRuntimeContext.push(runtimeMessage);
2214
- }
2215
- continue;
2216
- }
2217
- restoredMessages.splice(targetIndex, 0, runtimeMessage);
2218
- restoredProvenance.splice(targetIndex, 0, contextProvenance);
2219
- }
2220
- return {
2221
- messages: [...unmatchedRuntimeContext, ...restoredMessages],
2222
- provenance: [
2223
- ...unmatchedRuntimeContext.map(() => contextProvenance),
2224
- ...restoredProvenance
2225
- ]
2226
- };
2227
- }
2228
- async function getStoredTurnRecord(conversationId, turnId) {
2229
- const stateAdapter = getStateAdapter();
2230
- await stateAdapter.connect();
2231
- const value = await stateAdapter.get(turnCursorKey(conversationId, turnId));
2232
- return value == null ? void 0 : parseTurnRecord(value);
2233
- }
2234
- async function materializeStoredTurnRecord(conversationId, turnId, followCurrentReplacement) {
2235
- const parsed = await getStoredTurnRecord(conversationId, turnId);
2236
- if (!parsed) {
2237
- return void 0;
2238
- }
2239
- const pinnedProjection = await loadTurnProjection({
2240
- conversationId,
2241
- committedSeq: parsed.committedSeq,
2242
- // Unfinished records include the current-epoch tail so parked input
2243
- // appended after the last safe boundary stays model-visible on resume.
2244
- includeTail: parsed.state === "running" || parsed.state === "paused"
2245
- });
2246
- if (!pinnedProjection) {
2247
- return void 0;
2248
- }
2249
- const eventStore = getConversationEventStore();
2250
- const currentHistoryVersion = await eventStore.loadCurrentHistoryVersion(conversationId);
2251
- const followsReplacement = followCurrentReplacement && (parsed.state === "running" || parsed.state === "paused") && parsed.historyVersion !== void 0 && parsed.historyVersion !== currentHistoryVersion;
2252
- const piProjection = followsReplacement ? projectConversationEvents(
2253
- await eventStore.loadCurrentHistory(conversationId),
2254
- { defaultProfile: botConfig.defaultProfile }
2255
- ) : pinnedProjection;
2256
- const turnStartMessageIndex = followsReplacement ? 0 : parsed.turnStartSeq === void 0 ? void 0 : piProjection.seqs.filter((seq) => seq <= parsed.turnStartSeq).length;
2257
- const conversation = await getConversationStore().get({ conversationId });
2258
- const executionMetrics = executionMetricsForTurn(conversation, turnId);
2259
- return materializeTurnRecord(
2260
- parsed,
2261
- piProjection,
2262
- turnStartMessageIndex,
2263
- !followsReplacement && (parsed.historyVersion === void 0 || parsed.historyVersion === currentHistoryVersion),
2264
- {
2265
- channelName: conversation?.channelName,
2266
- cumulativeDurationMs: executionMetrics?.durationMs,
2267
- cumulativeUsage: executionMetrics?.usage
2268
- }
2269
- );
2270
- }
2271
- async function getTurnRecord(conversationId, turnId) {
2272
- return await materializeStoredTurnRecord(conversationId, turnId, false);
2273
- }
2274
- async function getTurnRecordForResume(conversationId, turnId) {
2275
- return await materializeStoredTurnRecord(conversationId, turnId, true);
2276
- }
2277
- function buildStoredRecord(args) {
2278
- const nowMs = Date.now();
2279
- return {
2280
- schemaVersion: TURN_CURSOR_SCHEMA_VERSION,
2281
- version: (args.previousVersion ?? 0) + 1,
2282
- conversationId: args.conversationId,
2283
- turnId: args.turnId,
2284
- sliceId: args.sliceId,
2285
- state: args.state,
2286
- startedAtMs: args.startedAtMs ?? nowMs,
2287
- lastProgressAtMs: args.lastProgressAtMs ?? nowMs,
2288
- updatedAtMs: nowMs,
2289
- committedSeq: args.committedSeq,
2290
- ...definedProps({
2291
- actor: args.actor,
2292
- cumulativeToolCallCount: args.cumulativeToolCallCount,
2293
- dispatchId: args.dispatchId,
2294
- dispatchOutcome: args.dispatchOutcome,
2295
- errorMessage: args.errorMessage,
2296
- historyVersion: args.historyVersion,
2297
- resumeReason: args.resumeReason,
2298
- publishExternally: args.publishExternally,
2299
- source: args.source,
2300
- resultMessageId: args.resultMessageId,
2301
- resumedFromSliceId: args.resumedFromSliceId,
2302
- runtimeContext: args.runtimeContext && args.runtimeContext.length > 0 ? args.runtimeContext : void 0,
2303
- surface: args.surface,
2304
- traceId: args.traceId,
2305
- turnStartSeq: args.turnStartSeq
2306
- })
2307
- };
2308
- }
2309
- async function setStoredRecord(args) {
2310
- const stateAdapter = getStateAdapter();
2311
- await stateAdapter.connect();
2312
- const storedRecord = storedTurnRecordSchema.parse(args.record);
2313
- await args.fence();
2314
- await recordConversationActivityMetadata({
2315
- actor: args.actor,
2316
- conversationStore: args.conversationStore,
2317
- destination: args.destination,
2318
- destinationVisibility: args.destinationVisibility,
2319
- nowMs: Date.now(),
2320
- source: args.source,
2321
- summary: {
2322
- ...projectTurnSummary(storedRecord),
2323
- startedAtMs: storedRecord.startedAtMs,
2324
- ...args.runtimeMetrics
2325
- }
2326
- });
2327
- await args.fence();
2328
- await stateAdapter.set(
2329
- turnCursorKey(storedRecord.conversationId, storedRecord.turnId),
2330
- storedRecord,
2331
- args.ttlMs
2332
- );
2333
- await args.fence();
2334
- await appendTurnSummary(
2335
- projectTurnSummary(storedRecord),
2336
- turnCursorIndexTtlMs(args.indexTtlMs)
2337
- );
2338
- return materializeTurnRecord(
2339
- storedRecord,
2340
- {
2341
- messages: [...args.piMessages],
2342
- provenance: [...args.piMessageProvenance]
2343
- },
2344
- args.turnStartMessageIndex,
2345
- true,
2346
- args.runtimeMetrics
2347
- );
2348
- }
2349
- async function updateTurnState(args) {
2350
- const parsed = await getStoredTurnRecord(
2351
- args.existing.conversationId,
2352
- args.existing.turnId
2353
- );
2354
- if (!parsed || parsed.version !== args.existing.version) {
2355
- return void 0;
2356
- }
2357
- return await setStoredRecord({
2358
- actor: args.existing.actor,
2359
- fence: args.fence,
2360
- piMessages: args.existing.piMessages,
2361
- piMessageProvenance: args.existing.piMessageProvenance,
2362
- ttlMs: turnCursorTtlMs(args.state),
2363
- runtimeMetrics: {
2364
- cumulativeDurationMs: args.existing.cumulativeDurationMs,
2365
- ...definedProps({
2366
- channelName: args.existing.channelName,
2367
- cumulativeUsage: args.existing.cumulativeUsage
2368
- })
2369
- },
2370
- ...definedProps({
2371
- turnStartMessageIndex: args.existing.turnStartMessageIndex
2372
- }),
2373
- record: buildStoredRecord({
2374
- actor: args.existing.actor,
2375
- conversationId: args.existing.conversationId,
2376
- turnId: args.existing.turnId,
2377
- sliceId: args.existing.sliceId,
2378
- state: args.state,
2379
- committedSeq: parsed.committedSeq,
2380
- startedAtMs: parsed.startedAtMs,
2381
- lastProgressAtMs: parsed.lastProgressAtMs,
2382
- previousVersion: parsed.version,
2383
- ...definedProps({
2384
- cumulativeToolCallCount: args.existing.cumulativeToolCallCount,
2385
- dispatchId: args.existing.dispatchId,
2386
- dispatchOutcome: args.existing.dispatchOutcome,
2387
- errorMessage: args.errorMessage ?? args.existing.errorMessage,
2388
- historyVersion: parsed.historyVersion,
2389
- resumeReason: args.existing.resumeReason,
2390
- publishExternally: parsed.publishExternally,
2391
- source: args.existing.source,
2392
- resultMessageId: args.resultMessageId ?? args.existing.resultMessageId,
2393
- resumedFromSliceId: args.existing.resumedFromSliceId,
2394
- surface: args.existing.surface,
2395
- traceId: args.existing.traceId,
2396
- turnStartSeq: parsed.turnStartSeq
2397
- })
2398
- })
2399
- });
2400
- }
2401
- async function upsertTurnRecord(args) {
2402
- return await withTurnCursorMutation(
2403
- args.conversationId,
2404
- args.turnId,
2405
- async (fence) => await upsertTurnRecordLocked(args, fence)
2406
- );
2407
- }
2408
- async function upsertTurnRecordLocked(args, fence) {
2409
- const existingRecord = await getStoredTurnRecord(
2410
- args.conversationId,
2411
- args.turnId
2412
- );
2413
- const conversation = await (args.conversationStore ?? getConversationStore()).get({
2414
- conversationId: args.conversationId
2415
- });
2416
- const executionMetrics = executionMetricsForTurn(conversation, args.turnId);
2417
- const existingDispatchId = existingRecord?.dispatchId ?? (await listTurnSummaries(args.conversationId)).find(
2418
- (summary) => summary.turnId === args.turnId
2419
- )?.dispatchId;
2420
- if (existingDispatchId && args.dispatchId && existingDispatchId !== args.dispatchId) {
2421
- throw new Error(`Turn ${args.turnId} dispatchId cannot be changed`);
2422
- }
2423
- const ttlMs = turnCursorTtlMs(args.state, args.ttlMs);
2424
- const instructionActor = args.actor;
2425
- const commit = await commitMessages({
2426
- conversationId: args.conversationId,
2427
- messages: args.piMessages,
2428
- ...instructionActor ? { newMessageProvenance: instructionProvenanceFor(instructionActor) } : void 0,
2429
- ...args.trailingMessageProvenance ? { trailingMessageProvenance: args.trailingMessageProvenance } : void 0,
2430
- ...args.turnContexts && args.turnContexts.length > 0 ? {
2431
- turnContext: {
2432
- contexts: args.turnContexts,
2433
- turnId: args.turnId
2434
- }
2435
- } : void 0
2436
- });
2437
- const durableTurnStartMessageIndex = args.turnStartMessageIndex === void 0 ? void 0 : stripRuntimeTurnContext(
2438
- args.piMessages.slice(0, args.turnStartMessageIndex)
2439
- ).length;
2440
- const runtimeContext = retainRuntimeTurnContext(args.piMessages);
2441
- const retainedRuntimeContext = runtimeContext.length > 0 ? runtimeContext : existingRecord?.historyVersion === commit.historyVersion ? existingRecord.runtimeContext : void 0;
2442
- const turnStartSeq = durableTurnStartMessageIndex === void 0 ? existingRecord?.turnStartSeq : durableTurnStartMessageIndex <= 0 ? -1 : commit.messageSeqs[durableTurnStartMessageIndex - 1] ?? commit.committedSeq;
2443
- const turnStartMessageIndex = durableTurnStartMessageIndex ?? (turnStartSeq === void 0 ? void 0 : commit.messageSeqs.filter((seq) => seq <= turnStartSeq).length);
2444
- const turnSource = args.source ?? existingRecord?.source;
2445
- const dispatchId = args.dispatchId ?? existingRecord?.dispatchId;
2446
- const hasProviderLocation = Boolean(conversation?.location) || args.destination?.platform === "slack";
2447
- const publishExternally = dispatchId || turnSource ? hasProviderLocation && (Boolean(dispatchId) || turnSource?.kind === "slack" || turnSource?.kind === "resource_event") : existingRecord?.publishExternally ?? false;
2448
- return await setStoredRecord({
2449
- actor: existingRecord?.actor ?? args.actor,
2450
- fence,
2451
- conversationStore: args.conversationStore,
2452
- destination: args.destination,
2453
- destinationVisibility: args.destinationVisibility,
2454
- source: args.source,
2455
- piMessages: commit.messages,
2456
- piMessageProvenance: commit.provenance,
2457
- ttlMs,
2458
- // Checkpoint commit also updates conversation execution metrics in SQL.
2459
- // Channel/duration/usage are not Redis resume-cursor fields.
2460
- runtimeMetrics: {
2461
- channelName: args.channelName ?? conversation?.channelName,
2462
- cumulativeDurationMs: args.cumulativeDurationMs ?? executionMetrics?.durationMs ?? 0,
2463
- cumulativeUsage: args.cumulativeUsage ?? executionMetrics?.usage
2464
- },
2465
- // Pass through only an explicit override so indexes stay on the resume window
2466
- // by default even when the record takes a short terminal TTL.
2467
- ...definedProps({ indexTtlMs: args.ttlMs }),
2468
- turnStartMessageIndex,
2469
- record: buildStoredRecord({
2470
- actor: existingRecord?.actor ?? args.actor,
2471
- conversationId: args.conversationId,
2472
- turnId: args.turnId,
2473
- sliceId: args.sliceId,
2474
- state: args.state,
2475
- committedSeq: commit.committedSeq,
2476
- historyVersion: commit.historyVersion,
2477
- previousVersion: existingRecord?.version,
2478
- ...definedProps({
2479
- cumulativeToolCallCount: args.cumulativeToolCallCount ?? existingRecord?.cumulativeToolCallCount,
2480
- dispatchId,
2481
- dispatchOutcome: args.dispatchOutcome ?? existingRecord?.dispatchOutcome,
2482
- errorMessage: args.errorMessage,
2483
- lastProgressAtMs: args.lastProgressAtMs,
2484
- resumeReason: args.resumeReason,
2485
- publishExternally,
2486
- source: args.source ?? existingRecord?.source,
2487
- resultMessageId: args.resultMessageId ?? existingRecord?.resultMessageId,
2488
- resumedFromSliceId: args.resumedFromSliceId,
2489
- runtimeContext: args.state === "running" || args.state === "paused" ? retainedRuntimeContext : void 0,
2490
- startedAtMs: existingRecord?.startedAtMs,
2491
- surface: args.surface ?? existingRecord?.surface,
2492
- traceId: args.traceId ?? existingRecord?.traceId,
2493
- turnStartSeq
2494
- })
2495
- })
2496
- });
2497
- }
2498
- async function recordTurnSummary(args) {
2499
- await withTurnCursorMutation(
2500
- args.conversationId,
2501
- args.turnId,
2502
- async (fence) => await recordTurnSummaryOwned(args, fence)
2503
- );
2504
- }
2505
- async function recordTurnSummaryOwned(args, fence) {
2506
- const stored = await getStoredTurnRecord(args.conversationId, args.turnId);
2507
- const conversationStore = args.conversationStore ?? getConversationStore();
2508
- const conversation = await conversationStore.get({
2509
- conversationId: args.conversationId
2510
- });
2511
- const executionMetrics = executionMetricsForTurn(conversation, args.turnId);
2512
- const priorSummary = (await listTurnSummaries(args.conversationId)).find(
2513
- (summary2) => summary2.turnId === args.turnId
2514
- );
2515
- const terminalSummary = priorSummary && (priorSummary.state === "completed" || priorSummary.state === "failed" || priorSummary.state === "abandoned") ? priorSummary : void 0;
2516
- const terminalRecord = stored && (stored.state === "completed" || stored.state === "failed" || stored.state === "abandoned") ? stored : void 0;
2517
- const existing = terminalSummary ?? terminalRecord ?? stored ?? priorSummary;
2518
- if ((terminalSummary || terminalRecord) && (args.state === "running" || args.state === "paused")) {
2519
- return;
2520
- }
2521
- const existingDispatchId = existing?.dispatchId;
2522
- const existingDispatchOutcome = priorSummary?.dispatchOutcome ?? stored?.dispatchOutcome;
2523
- const existingResultMessageId = priorSummary?.resultMessageId ?? stored?.resultMessageId;
2524
- if (existingDispatchId && args.dispatchId && existingDispatchId !== args.dispatchId) {
2525
- throw new Error(`Turn ${args.turnId} dispatchId cannot be changed`);
2526
- }
2527
- const nowMs = Date.now();
2528
- const ttlMs = turnCursorIndexTtlMs(args.ttlMs);
2529
- const summary = {
2530
- schemaVersion: TURN_CURSOR_SCHEMA_VERSION,
2531
- version: existing?.version ?? 0,
2532
- conversationId: args.conversationId,
2533
- turnId: args.turnId,
2534
- sliceId: args.sliceId,
2535
- state: args.state,
2536
- updatedAtMs: nowMs,
2537
- ...definedProps({
2538
- dispatchId: args.dispatchId ?? existingDispatchId,
2539
- dispatchOutcome: args.dispatchOutcome ?? existingDispatchOutcome,
2540
- resumeReason: args.resumeReason,
2541
- resultMessageId: args.resultMessageId ?? existingResultMessageId,
2542
- surface: args.surface ?? existing?.surface
2543
- })
2544
- };
2545
- await fence();
2546
- await recordConversationActivityMetadata({
2547
- actor: args.actor,
2548
- conversationStore,
2549
- destination: args.destination,
2550
- destinationVisibility: args.destinationVisibility,
2551
- nowMs,
2552
- source: args.source,
2553
- summary: {
2554
- ...summary,
2555
- channelName: args.channelName ?? conversation?.channelName,
2556
- cumulativeDurationMs: args.cumulativeDurationMs ?? executionMetrics?.durationMs ?? 0,
2557
- cumulativeUsage: args.cumulativeUsage ?? executionMetrics?.usage,
2558
- startedAtMs: stored?.startedAtMs ?? args.startedAtMs ?? nowMs
2559
- }
2560
- });
2561
- await fence();
2562
- await appendTurnSummary(summary, ttlMs);
2563
- }
2564
- async function readTurnSummaries(key) {
2565
- const stateAdapter = getStateAdapter();
2566
- await stateAdapter.connect();
2567
- const values = await stateAdapter.getList(key);
2568
- const summaries = /* @__PURE__ */ new Map();
2569
- for (const value of [...values].reverse()) {
2570
- let summary;
2571
- try {
2572
- summary = parseTurnSummary(value);
2573
- } catch {
2574
- continue;
2575
- }
2576
- const summaryKey = `${summary.conversationId}:${summary.turnId}`;
2577
- if (!summaries.has(summaryKey)) {
2578
- summaries.set(summaryKey, summary);
2579
- }
2580
- }
2581
- return [...summaries.values()].sort(
2582
- (left, right) => right.updatedAtMs - left.updatedAtMs
2583
- );
2584
- }
2585
- async function listTurnSummaries(conversationId) {
2586
- return readTurnSummaries(turnCursorIndexKey(conversationId));
2587
- }
2588
- async function abandonTurnRecord(args) {
2589
- return await withTurnCursorMutation(
2590
- args.conversationId,
2591
- args.turnId,
2592
- async (fence) => {
2593
- const existing = await getTurnRecord(args.conversationId, args.turnId);
2594
- if (!existing || existing.state === "completed" || existing.state === "failed" || existing.state === "abandoned") {
2595
- return void 0;
2596
- }
2597
- return await updateTurnState({
2598
- existing,
2599
- fence,
2600
- state: "abandoned",
2601
- errorMessage: args.errorMessage ?? existing.errorMessage
2602
- });
2603
- }
2604
- );
2605
- }
2606
- async function completeTurnRecord(args) {
2607
- return await withTurnCursorMutation(
2608
- args.conversationId,
2609
- args.turnId,
2610
- async (fence) => {
2611
- const existing = await getTurnRecord(args.conversationId, args.turnId);
2612
- if (!existing || existing.state === "completed" || existing.state === "failed" || existing.state === "abandoned" || existing.version !== args.expectedVersion) {
2613
- return void 0;
2614
- }
2615
- return await updateTurnState({
2616
- existing,
2617
- fence,
2618
- resultMessageId: args.resultMessageId,
2619
- state: "completed"
2620
- });
2621
- }
2622
- );
2623
- }
2624
- async function failTurnRecord(args) {
2625
- return await withTurnCursorMutation(
2626
- args.conversationId,
2627
- args.turnId,
2628
- async (fence) => {
2629
- const existing = await getTurnRecord(args.conversationId, args.turnId);
2630
- if (!existing || existing.state === "completed" || existing.state === "failed" || existing.state === "abandoned" || existing.version !== args.expectedVersion) {
2631
- return void 0;
2632
- }
2633
- return await updateTurnState({
2634
- existing,
2635
- fence,
2636
- state: "failed",
2637
- errorMessage: args.errorMessage ?? existing.errorMessage
2638
- });
2639
- }
2640
- );
2641
- }
2642
-
2643
- // src/chat/services/turn-limit.ts
2644
- var TurnSliceLimitExceededError = class extends Error {
2645
- constructor(maxSlices) {
2646
- super(`Agent turn exceeded execution limit (${maxSlices} slices)`);
2647
- this.name = "TurnSliceLimitExceededError";
2648
- }
2649
- };
2650
- var TurnToolCallLimitExceededError = class extends Error {
2651
- constructor(maxToolCalls) {
2652
- super(`Agent turn exceeded execution limit (${maxToolCalls} tool calls)`);
2653
- this.name = "TurnToolCallLimitExceededError";
2654
- }
2655
- };
2656
- function isDirectTurnExecutionLimitError(error) {
2657
- return error instanceof TurnSliceLimitExceededError || error instanceof TurnToolCallLimitExceededError;
2658
- }
2659
- function isTurnExecutionLimitExceededError(error) {
2660
- if (isDirectTurnExecutionLimitError(error)) {
2661
- return true;
2662
- }
2663
- return error instanceof Error && isDirectTurnExecutionLimitError(error.cause);
2664
- }
2665
- function assertTurnToolCallLimit(toolCallCount, maxToolCalls) {
2666
- if (toolCallCount <= maxToolCalls) {
2667
- return;
2668
- }
2669
- throw new TurnToolCallLimitExceededError(maxToolCalls);
2670
- }
2671
- function buildTurnLimitResponse(eventId) {
2672
- 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}\`.`;
2673
- }
2674
- function buildTurnErrorResponse(error, eventId) {
2675
- return isTurnExecutionLimitExceededError(error) ? buildTurnLimitResponse(eventId) : buildTurnFailureResponse(eventId);
2676
- }
2677
-
2678
- // src/chat/task-execution/checkpoint.ts
2679
- function definedProps2(values) {
2680
- return Object.fromEntries(
2681
- Object.entries(values).filter(([, value]) => value !== void 0)
2682
- );
2683
- }
2684
- function addDurationMs(prior, current) {
2685
- const total = [prior, current].reduce((sum, value) => {
2686
- if (typeof value !== "number" || !Number.isFinite(value)) {
2687
- return sum;
2688
- }
2689
- return (sum ?? 0) + Math.max(0, Math.floor(value));
2690
- }, void 0);
2691
- return total;
2692
- }
2693
- function continuableMessages(messages, fallback) {
2694
- const current = trimTrailingAssistantMessages(messages);
2695
- if (current.length > 0 && isContinuablePiBoundary(current)) {
2696
- return current;
2697
- }
2698
- return trimTrailingAssistantMessages(fallback ?? []);
2699
- }
2700
- async function loadTurnCheckpoint(args) {
2701
- const record = await getTurnRecordForResume(args.conversationId, args.turnId);
2702
- if (record?.state === "paused") {
2703
- return { resumed: true, sliceId: record.sliceId, record };
2704
- }
2705
- return { resumed: false, sliceId: 1, record };
2706
- }
2707
- async function saveTurnCheckpoint(args) {
2708
- if (args.mode === "running") {
2709
- return await saveRunning(args);
2710
- }
2711
- if (args.mode === "paused") {
2712
- return await savePaused(args);
2713
- }
2714
- await saveDone(args);
2715
- return void 0;
2716
- }
2717
- function sharedWrite(args, latest) {
2718
- return {
2719
- conversationId: args.conversationId,
2720
- turnId: args.turnId,
2721
- ...definedProps2({
2722
- actor: args.actor,
2723
- channelName: args.channelName ?? latest?.channelName,
2724
- destination: args.destination,
2725
- destinationVisibility: args.destinationVisibility,
2726
- dispatchId: args.dispatchId ?? latest?.dispatchId,
2727
- source: args.source,
2728
- surface: args.surface ?? latest?.surface,
2729
- traceId: getActiveTraceId() ?? latest?.traceId,
2730
- turnStartMessageIndex: args.turnStartMessageIndex ?? latest?.turnStartMessageIndex,
2731
- cumulativeToolCallCount: args.cumulativeToolCallCount ?? latest?.cumulativeToolCallCount
2732
- })
2733
- };
2734
- }
2735
- async function saveRunning(args) {
2736
- if (args.messages.length === 0 || !isContinuablePiBoundary(args.messages)) {
2737
- return void 0;
2738
- }
2739
- try {
2740
- const latest = await getTurnRecord(args.conversationId, args.turnId);
2741
- return await upsertTurnRecord({
2742
- ...sharedWrite(args, latest),
2743
- ...definedProps2({
2744
- trailingMessageProvenance: args.trailingMessageProvenance,
2745
- turnContexts: args.turnContexts
2746
- }),
2747
- cumulativeDurationMs: latest?.cumulativeDurationMs,
2748
- cumulativeUsage: latest?.cumulativeUsage,
2749
- piMessages: args.messages,
2750
- sliceId: args.sliceId,
2751
- state: "running"
2752
- });
2753
- } catch (error) {
2754
- if (!(error instanceof AgentHistoryBranchError)) {
2755
- logException(error, "agent.turn.checkpoint.running.failed", {
2756
- "app.ai.resume_conversation_id": args.conversationId,
2757
- "app.ai.resume_session_id": args.turnId,
2758
- "app.ai.resume_slice_id": args.sliceId
2759
- });
2760
- }
2761
- return void 0;
2762
- }
2763
- }
2764
- async function savePaused(args) {
2765
- const keepSlice = args.reason === "yield";
2766
- const nextSliceId = keepSlice ? args.sliceId : args.sliceId + 1;
2767
- try {
2768
- const latest = await getTurnRecord(args.conversationId, args.turnId);
2769
- const messages = args.reason === "yield" ? [...args.messages] : continuableMessages(args.messages, latest?.piMessages);
2770
- if (args.reason === "auth") {
2771
- if (messages.length > 0 && !isContinuablePiBoundary(messages)) {
2772
- return void 0;
2773
- }
2774
- } else if (messages.length === 0 || !isContinuablePiBoundary(messages)) {
2775
- return void 0;
2776
- }
2777
- const base = {
2778
- ...sharedWrite(args, latest),
2779
- ...definedProps2({
2780
- cumulativeDurationMs: addDurationMs(
2781
- latest?.cumulativeDurationMs,
2782
- args.durationMs
2783
- ),
2784
- cumulativeUsage: addAgentTurnUsage(latest?.cumulativeUsage, args.usage),
2785
- errorMessage: args.errorMessage
2786
- }),
2787
- piMessages: messages,
2788
- resumeReason: args.reason
2789
- };
2790
- if (!keepSlice && nextSliceId > botConfig.maxSlicesPerTurn) {
2791
- const error = new TurnSliceLimitExceededError(botConfig.maxSlicesPerTurn);
2792
- await upsertTurnRecord({
2793
- ...base,
2794
- ...definedProps2({ resumedFromSliceId: latest?.resumedFromSliceId }),
2795
- errorMessage: error.message,
2796
- sliceId: args.sliceId,
2797
- state: "failed"
2798
- });
2799
- throw error;
2800
- }
2801
- return await upsertTurnRecord({
2802
- ...base,
2803
- ...definedProps2({
2804
- resumedFromSliceId: keepSlice ? latest?.resumedFromSliceId : args.sliceId
2805
- }),
2806
- sliceId: nextSliceId,
2807
- // Stored name is historical; means "paused, may continue".
2808
- state: "paused"
2809
- });
2810
- } catch (error) {
2811
- if (error instanceof TurnSliceLimitExceededError) {
2812
- throw error;
2813
- }
2814
- logException(error, "agent.turn.checkpoint.paused.failed", {
2815
- "app.ai.resume_conversation_id": args.conversationId,
2816
- "app.ai.resume_session_id": args.turnId,
2817
- "app.ai.resume_from_slice_id": args.sliceId,
2818
- "app.ai.resume_next_slice_id": nextSliceId,
2819
- "app.ai.resume_reason": args.reason
2820
- });
2821
- return void 0;
2822
- }
2823
- }
2824
- async function saveDone(args) {
2825
- let latest;
2826
- await persistWithRetry(async () => {
2827
- latest = await getTurnRecord(args.conversationId, args.turnId);
2828
- });
2829
- const sliceId = args.sliceId ?? latest?.sliceId;
2830
- if (sliceId === void 0) {
2831
- throw new Error(
2832
- "Completed turn checkpoint requires a slice id from the caller or the latest stored record"
2833
- );
2834
- }
2835
- await persistWithRetry(async () => {
2836
- await upsertTurnRecord({
2837
- ...sharedWrite(args, latest),
2838
- ...definedProps2({
2839
- cumulativeDurationMs: addDurationMs(
2840
- latest?.cumulativeDurationMs,
2841
- args.durationMs
2842
- ),
2843
- cumulativeUsage: addAgentTurnUsage(latest?.cumulativeUsage, args.usage),
2844
- dispatchOutcome: args.dispatchOutcome ?? latest?.dispatchOutcome,
2845
- errorMessage: args.errorMessage,
2846
- resultMessageId: args.resultMessageId ?? latest?.resultMessageId
2847
- }),
2848
- piMessages: args.messages,
2849
- sliceId,
2850
- state: args.mode
2851
- });
2852
- });
2853
- }
2854
-
2855
- export {
2856
- TURN_CONTEXT_TAG,
2857
- isAssistantMessage,
2858
- isToolResultMessage,
2859
- isToolResultError,
2860
- normalizeToolNameFromResult,
2861
- getPiMessageRole,
2862
- isContinuablePiBoundary,
2863
- extractAssistantText,
2864
- getTerminalAssistantMessages,
2865
- trimTrailingAssistantMessages,
2866
- hasRuntimeTurnContext,
2867
- retainRuntimeTurnContext,
2868
- instructionTextForProjection,
2869
- getUserMessageInstructionText,
2870
- stripRuntimeTurnContext,
2871
- historyItemFromPiMessage,
2872
- piMessageFromHistoryItem,
2873
- estimateTextTokens,
2874
- getAgentContextCompactionTriggerTokens,
2875
- getAgentContextInputLimitTokens,
2876
- normalizeConversationText,
2877
- estimateConversationContextTokens,
2878
- upsertConversationMessage,
2879
- recordDeliveredAssistantMessage,
2880
- turnHasReply,
2881
- markConversationMessage,
2882
- buildConversationContext,
2883
- appendThreadContextMessages,
2884
- createConversationMemoryService,
2885
- isHumanConversationMessage,
2886
- getConversationMessageSlackTs,
2887
- resolveConversationTitle,
2888
- persistConversationMessageSummaries,
2889
- projectConversationMessages,
2890
- hydrateConversationMessages,
2891
- persistConversationMessages,
2892
- appendConversationMessages,
2893
- loadProjection,
2894
- loadConversationProjection,
2895
- openConversationProjection,
2896
- loadConnectedMcpProviders,
2897
- commitMessages,
2898
- commitAcceptedReply,
2899
- recordMcpProviderConnected,
2900
- recordAuthorizationRequested,
2901
- recordAuthorizationCompleted,
2902
- recordAuthenticationLinked,
2903
- recordAgentsInstructionsUpdated,
2904
- recordSubscribedReplyRoute,
2905
- loadTurnRoute,
2906
- recordTurnRoute,
2907
- recordToolExecutionStarted,
2908
- recordAttachmentsDelivered,
2909
- recordGuardianActionReviewed,
2910
- MAX_TEXT_CHARS,
2911
- RIPGREP_EXCLUDED_GLOBS,
2912
- DEFAULT_SEARCH_COMMAND_TIMEOUT_MS,
2913
- positiveInteger2 as positiveInteger,
2914
- normalizeToLf,
2915
- truncateText,
2916
- isMissingPathError,
2917
- missingPathSearchResult,
2918
- resolveWorkspacePath,
2919
- getRipgrepSearchLocation,
2920
- collectFiles,
2921
- AGENTS_REPLACEMENT_NOTICE,
2922
- AGENTS_REMOVAL_NOTICE,
2923
- listRepositoryDirectories,
2924
- resolveRepositoryInstructionsForDirectories,
2925
- renderAgentsInstructions,
2926
- buildAgentsInstructionsMessage,
2927
- findVisibleAgentsInstructions,
2928
- TurnSliceLimitExceededError,
2929
- isTurnExecutionLimitExceededError,
2930
- assertTurnToolCallLimit,
2931
- buildTurnErrorResponse,
2932
- persistWithRetry,
2933
- getTurnRecord,
2934
- getTurnRecordForResume,
2935
- recordTurnSummary,
2936
- listTurnSummaries,
2937
- abandonTurnRecord,
2938
- completeTurnRecord,
2939
- failTurnRecord,
2940
- continuableMessages,
2941
- loadTurnCheckpoint,
2942
- saveTurnCheckpoint
2943
- };