@sentry/junior 0.147.0 → 0.148.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 (155) hide show
  1. package/dist/{agent-hooks-HS6VGUPM.js → agent-hooks-VAUYT6PD.js} +10 -10
  2. package/dist/api/conversations/access.d.ts +2 -1
  3. package/dist/api/conversations/archive.d.ts +3 -5
  4. package/dist/api/conversations/create.d.ts +6 -0
  5. package/dist/api/conversations/detail.d.ts +2 -296
  6. package/dist/api/conversations/event-list.d.ts +2 -193
  7. package/dist/api/conversations/list.d.ts +4 -66
  8. package/dist/api/conversations/routes.d.ts +1 -1
  9. package/dist/api/conversations/stats.d.ts +0 -54
  10. package/dist/api/http.d.ts +2 -6
  11. package/dist/api/locations/detail.d.ts +3 -111
  12. package/dist/api/locations/list.d.ts +0 -45
  13. package/dist/api/locations/query.d.ts +2 -1
  14. package/dist/api/locations/routes.d.ts +1 -1
  15. package/dist/api/people/list.d.ts +0 -29
  16. package/dist/api/people/profile.d.ts +2 -106
  17. package/dist/api/people/profile.query.d.ts +2 -1
  18. package/dist/api/people/routes.d.ts +1 -1
  19. package/dist/api/people/shared.d.ts +1 -1
  20. package/dist/api/people/spend.d.ts +3 -4
  21. package/dist/api/personal-tokens/routes.d.ts +2 -2
  22. package/dist/api/route.d.ts +8 -14
  23. package/dist/api/schema/conversation.d.ts +60 -29
  24. package/dist/api/schema/location.d.ts +4 -0
  25. package/dist/api/schema/person.d.ts +7 -3
  26. package/dist/api/schema.d.ts +2 -2
  27. package/dist/api/schema.js +7 -1
  28. package/dist/api/stats.d.ts +0 -13
  29. package/dist/api/validation.d.ts +7 -0
  30. package/dist/api/viewer.d.ts +3 -0
  31. package/dist/api.d.ts +2 -1
  32. package/dist/api.js +624 -562
  33. package/dist/app.js +183 -105
  34. package/dist/{catalog-runtime-M6ZTPYRV.js → catalog-runtime-WADOZVTB.js} +5 -5
  35. package/dist/chat/actor.d.ts +4 -1
  36. package/dist/chat/agent/request.d.ts +2 -0
  37. package/dist/chat/agent/resume.d.ts +1 -0
  38. package/dist/chat/agent-dispatch/context.d.ts +2 -2
  39. package/dist/chat/agent-invocations/types.d.ts +52 -0
  40. package/dist/chat/api-turns/work.d.ts +103 -0
  41. package/dist/chat/conversations/history.d.ts +60 -0
  42. package/dist/chat/conversations/provenance.d.ts +6 -0
  43. package/dist/chat/conversations/sql/actor-identity.d.ts +19 -0
  44. package/dist/chat/conversations/store.d.ts +1 -1
  45. package/dist/chat/resource-events/store.d.ts +1 -1
  46. package/dist/chat/runtime/reply-executor.d.ts +1 -1
  47. package/dist/chat/runtime/slack-runtime.d.ts +2 -1
  48. package/dist/chat/runtime/turn-preparation.d.ts +9 -0
  49. package/dist/chat/scheduled-tasks/tool-support.d.ts +5 -5
  50. package/dist/chat/scheduled-tasks/tools/create.d.ts +2 -2
  51. package/dist/chat/scheduled-tasks/tools/delete.d.ts +2 -2
  52. package/dist/chat/scheduled-tasks/tools/list.d.ts +2 -2
  53. package/dist/chat/scheduled-tasks/tools/run-now.d.ts +2 -2
  54. package/dist/chat/scheduled-tasks/tools/update.d.ts +2 -2
  55. package/dist/chat/slack/channel.d.ts +26 -0
  56. package/dist/chat/slack/dispatch-turn.d.ts +1 -0
  57. package/dist/chat/slack/timestamp-param.d.ts +0 -2
  58. package/dist/chat/slack/{tools → tool-support}/canvas/api.d.ts +1 -1
  59. package/dist/chat/slack/{tools → tool-support}/canvas/context.d.ts +3 -0
  60. package/dist/chat/slack/tool-support/channel-access.d.ts +42 -0
  61. package/dist/chat/slack/tool-support/channel-target.d.ts +27 -0
  62. package/dist/chat/slack/tool-support/list/params.d.ts +5 -0
  63. package/dist/chat/slack/tools/canvas/create.d.ts +1 -1
  64. package/dist/chat/slack/tools/channel-join.d.ts +28 -0
  65. package/dist/chat/slack/tools/channel-list-messages.d.ts +3 -2
  66. package/dist/chat/slack/tools/conversation-message-search.d.ts +2 -8
  67. package/dist/chat/slack/tools/message-add-reaction.d.ts +1 -1
  68. package/dist/chat/slack/tools/public-search.d.ts +63 -2
  69. package/dist/chat/slack/tools/send-files.d.ts +1 -1
  70. package/dist/chat/slack/tools/thread-read.d.ts +2 -5
  71. package/dist/chat/source.d.ts +2 -0
  72. package/dist/chat/task-execution/checkpoint.d.ts +1 -0
  73. package/dist/chat/task-execution/state.d.ts +9 -4
  74. package/dist/chat/task-execution/store.d.ts +3 -2
  75. package/dist/chat/task-execution/turn-cursor.d.ts +2 -0
  76. package/dist/chat/task-execution/worker.d.ts +2 -0
  77. package/dist/chat/tasks/read.d.ts +1 -1
  78. package/dist/chat/tools/search-conversation-events.d.ts +1 -1
  79. package/dist/chat/tools/types.d.ts +11 -4
  80. package/dist/chat/tools/watch-resource-events.d.ts +2 -2
  81. package/dist/{chunk-ORO3PWKQ.js → chunk-2B3PT3V2.js} +1 -1
  82. package/dist/{chunk-SVHUEHUL.js → chunk-45LOR4PU.js} +21 -4
  83. package/dist/{chunk-YBVXBNSV.js → chunk-4AQZYNHM.js} +2 -2
  84. package/dist/{chunk-Z4YU7Z4L.js → chunk-4EA2SFK2.js} +65 -4
  85. package/dist/{chunk-7NVJVJID.js → chunk-4GXJUYJV.js} +2 -2
  86. package/dist/{chunk-IIOOO4FQ.js → chunk-55F4QU2V.js} +2 -1
  87. package/dist/{chunk-745QJ3ZA.js → chunk-7OEIXFC3.js} +2 -2
  88. package/dist/{chunk-DU74WPIC.js → chunk-D3WPMGEX.js} +1 -1
  89. package/dist/{chunk-PVNG6UMU.js → chunk-D6YX4NNN.js} +22 -10
  90. package/dist/chunk-DUY5CDAM.js +36 -0
  91. package/dist/{chunk-4LJO56OJ.js → chunk-EAI67NTM.js} +1 -1
  92. package/dist/{chunk-X5UY34KT.js → chunk-EINWQFXI.js} +8 -8
  93. package/dist/chunk-GDFF3BQG.js +136 -0
  94. package/dist/{chunk-EFLJ2WJH.js → chunk-GXYDVZKM.js} +5 -5
  95. package/dist/{chunk-CWP5RHVX.js → chunk-HLPLT3NY.js} +8 -5
  96. package/dist/{chunk-JUUKWRBE.js → chunk-HWQGBIH7.js} +1 -1
  97. package/dist/{chunk-AZ7EAOBE.js → chunk-IL4EDBMV.js} +5 -5
  98. package/dist/{chunk-DLDRQHG5.js → chunk-LAZGHEYU.js} +1 -1
  99. package/dist/{chunk-RXBVB2JM.js → chunk-LE5H7LMT.js} +1 -1
  100. package/dist/chunk-LNVEIJ2L.js +48 -0
  101. package/dist/{chunk-YEO3VX72.js → chunk-OJCP3C3J.js} +8 -6
  102. package/dist/{chunk-GGB4WL5A.js → chunk-POSZWRGL.js} +1 -1
  103. package/dist/{chunk-YNTTROJT.js → chunk-QOWHHM3F.js} +1 -1
  104. package/dist/{chunk-6H5SZR24.js → chunk-RRJV5UR5.js} +302 -59
  105. package/dist/{chunk-AYU72WO7.js → chunk-ULAU5ZGU.js} +41 -64
  106. package/dist/{chunk-SGZO7UWH.js → chunk-V2FXMOEA.js} +2 -2
  107. package/dist/{chunk-AIOK6LAG.js → chunk-VCYSOXON.js} +272 -41
  108. package/dist/{chunk-7A4M7PCV.js → chunk-VMNRXKAH.js} +1166 -540
  109. package/dist/{chunk-YSB2N563.js → chunk-VRHTNBA6.js} +121 -87
  110. package/dist/{chunk-AVZDSUFB.js → chunk-VY6W3T43.js} +4 -4
  111. package/dist/{chunk-7UQIVT6G.js → chunk-WCN4YSH7.js} +6 -6
  112. package/dist/{chunk-CSZINQJ2.js → chunk-WOONT5ZP.js} +1 -1
  113. package/dist/{chunk-Q4R73I43.js → chunk-XJ42NBP5.js} +58 -28
  114. package/dist/{chunk-YHDERDRO.js → chunk-ZCHIC2GM.js} +91 -75
  115. package/dist/chunk-ZPL2RR7F.js +798 -0
  116. package/dist/cli/chat.js +33 -35
  117. package/dist/cli/check.js +6 -6
  118. package/dist/cli/plugins.js +13 -13
  119. package/dist/cli/snapshot-create.js +9 -9
  120. package/dist/cli/upgrade.js +7 -7
  121. package/dist/db/schema/agent-invocations.d.ts +20 -0
  122. package/dist/db/schema/conversation-annotations.d.ts +2 -2
  123. package/dist/db/schema.d.ts +22 -2
  124. package/dist/{db-CSEW4RII.js → db-XUKZRFFK.js} +7 -7
  125. package/dist/instrumentation.js +4 -4
  126. package/dist/nitro.js +2 -2
  127. package/dist/{oauth-callback-server-FUEV5HNG.js → oauth-callback-server-LPZMMGFI.js} +25 -26
  128. package/dist/{runner-U3XHSKSH.js → runner-DGKUTMPX.js} +17 -18
  129. package/dist/{skills-AKE6W3WI.js → skills-P7YUFSXM.js} +6 -6
  130. package/dist/spawn-DSK3ZEVD.js +38 -0
  131. package/dist/{validation-WDTAULUF.js → validation-S7U5M5NR.js} +6 -6
  132. package/dist/version.js +1 -1
  133. package/dist/{work-C3HAO2BG.js → work-GAA7TVAD.js} +18 -19
  134. package/dist/{worker-LNP4YKBM.js → worker-JXPJH3QB.js} +11 -10
  135. package/package.json +5 -4
  136. package/dist/api/health.d.ts +0 -7
  137. package/dist/api/personal-tokens/create.d.ts +0 -11
  138. package/dist/api/personal-tokens/list.d.ts +0 -12
  139. package/dist/api/personal-tokens/revoke.d.ts +0 -5
  140. package/dist/api/plugin-reports.d.ts +0 -63
  141. package/dist/api/plugins.d.ts +0 -8
  142. package/dist/api/runtime.d.ts +0 -24
  143. package/dist/api/skills.d.ts +0 -6
  144. package/dist/chat/slack/tools/channel-access.d.ts +0 -30
  145. package/dist/chunk-34GS7B6L.js +0 -115
  146. package/dist/chunk-AVOCH2Z5.js +0 -285
  147. package/dist/chunk-HLXXQI5Q.js +0 -238
  148. package/dist/chunk-K4SZAP3K.js +0 -67
  149. package/dist/chunk-R776HKPK.js +0 -81
  150. package/dist/spawn-B6GDTWJ4.js +0 -39
  151. /package/dist/chat/slack/{tools → tool-support}/canvas/markdown.d.ts +0 -0
  152. /package/dist/chat/slack/{tools → tool-support}/channel-capabilities.d.ts +0 -0
  153. /package/dist/chat/slack/{tools → tool-support}/context.d.ts +0 -0
  154. /package/dist/chat/slack/{tools → tool-support}/list/api.d.ts +0 -0
  155. /package/dist/chat/slack/{tools → tool-support}/slack-message-url.d.ts +0 -0
@@ -0,0 +1,798 @@
1
+ import {
2
+ appendAndEnqueueInboundMessage
3
+ } from "./chunk-HLPLT3NY.js";
4
+ import {
5
+ buildConversationContext,
6
+ buildDeliveredTurnStatePatch,
7
+ finalizeFailedTurnReplyWithEvent,
8
+ getTurnUserMessage,
9
+ markConversationMessage,
10
+ normalizeConversationText,
11
+ processPluginTask,
12
+ recordDeliveredAssistantMessage,
13
+ scheduleSessionCompletedPluginTasks,
14
+ upsertConversationMessage
15
+ } from "./chunk-RRJV5UR5.js";
16
+ import {
17
+ ConversationTurnLifecycleService,
18
+ coerceThreadConversationState,
19
+ getPersistedSandboxState,
20
+ getPersistedThreadState,
21
+ persistThreadStateById
22
+ } from "./chunk-IL4EDBMV.js";
23
+ import {
24
+ healthReportSchema
25
+ } from "./chunk-UTL3MHPC.js";
26
+ import {
27
+ parseSlackThreadId
28
+ } from "./chunk-WOONT5ZP.js";
29
+ import {
30
+ TurnInputCommitLostError,
31
+ buildDeterministicTurnId,
32
+ getAssistantReplyText,
33
+ getConversationTurnBoundaryError,
34
+ getTurnRecord,
35
+ hydrateConversationMessages,
36
+ isTurnInputCommitLostError,
37
+ listTurnSummaries,
38
+ loadProjection,
39
+ markTurnFailed,
40
+ persistConversationMessages,
41
+ persistWithRetry,
42
+ saveTurnCheckpoint,
43
+ startActiveTurn
44
+ } from "./chunk-VCYSOXON.js";
45
+ import {
46
+ normalizeSlackConversationId
47
+ } from "./chunk-V2FXMOEA.js";
48
+ import {
49
+ getConversationEventStore,
50
+ getConversationStore
51
+ } from "./chunk-VRHTNBA6.js";
52
+ import {
53
+ credentialContextForActor
54
+ } from "./chunk-RUIXF3YQ.js";
55
+ import {
56
+ logException,
57
+ setTags,
58
+ withLogContext
59
+ } from "./chunk-EAI67NTM.js";
60
+
61
+ // src/handlers/health.ts
62
+ function readHealthReport() {
63
+ return healthReportSchema.parse({
64
+ status: "ok",
65
+ service: "junior",
66
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
67
+ });
68
+ }
69
+ function GET() {
70
+ return Response.json(readHealthReport());
71
+ }
72
+
73
+ // src/chat/api-turns/work.ts
74
+ import { createHash } from "crypto";
75
+ import { z } from "zod";
76
+ import {
77
+ createWebSource,
78
+ localDestinationSchema
79
+ } from "@sentry/junior-plugin-api";
80
+ var apiTurnMailboxMetadataSchema = z.object({
81
+ authorEmail: z.string().email(),
82
+ authorFullName: z.string().min(1).optional(),
83
+ authorUserId: z.string().min(1),
84
+ authorUserName: z.string().min(1).optional(),
85
+ kind: z.literal("api_turn"),
86
+ messageId: z.string().min(1)
87
+ }).strict();
88
+ function normalizeEmail(email) {
89
+ return email.trim().toLowerCase();
90
+ }
91
+ function stableHex(...parts) {
92
+ return createHash("sha256").update(parts.join("\0")).digest("hex").slice(0, 24);
93
+ }
94
+ function createApiConversationId(args) {
95
+ return `local:web:${stableHex(
96
+ normalizeEmail(args.actorEmail),
97
+ args.idempotencyKey
98
+ )}`;
99
+ }
100
+ function apiMessageId(args) {
101
+ return `api-msg:${stableHex(args.conversationId, args.idempotencyKey)}`;
102
+ }
103
+ function apiTurnIdForMessage(messageId) {
104
+ return buildDeterministicTurnId(messageId);
105
+ }
106
+ function requireLocalDestination(conversationId) {
107
+ const parsed = localDestinationSchema.safeParse({
108
+ platform: "local",
109
+ conversationId
110
+ });
111
+ if (!parsed.success) {
112
+ throw new Error(`Invalid local conversation id: ${conversationId}`);
113
+ }
114
+ return parsed.data;
115
+ }
116
+ function resolveApiTurnDestination(args) {
117
+ if (args.existing) {
118
+ return args.existing;
119
+ }
120
+ return requireLocalDestination(args.conversationId);
121
+ }
122
+ function webSourceForConversation(args) {
123
+ return createWebSource(
124
+ args.conversationId,
125
+ args.visibility === "private" ? "private" : "public"
126
+ );
127
+ }
128
+ function actorFromMetadata(metadata) {
129
+ return {
130
+ platform: "web",
131
+ userId: metadata.authorUserId,
132
+ email: normalizeEmail(metadata.authorEmail),
133
+ ...metadata.authorFullName ? { fullName: metadata.authorFullName } : {},
134
+ ...metadata.authorUserName ? { userName: metadata.authorUserName } : {}
135
+ };
136
+ }
137
+ function storedActorFromApi(actor) {
138
+ return {
139
+ ...actor.email ? { email: normalizeEmail(actor.email) } : {},
140
+ ...actor.fullName ? { fullName: actor.fullName } : {}
141
+ };
142
+ }
143
+ function webActorFromEmail(email, profile) {
144
+ const normalized = normalizeEmail(email);
145
+ return {
146
+ platform: "web",
147
+ userId: `dashboard:${stableHex(normalized)}`,
148
+ email: normalized,
149
+ ...profile?.fullName ? { fullName: profile.fullName } : {},
150
+ ...profile?.userName ? { userName: profile.userName } : {}
151
+ };
152
+ }
153
+ function actorFromStoredConversation(stored) {
154
+ const email = stored?.email?.trim().toLowerCase();
155
+ if (!email) {
156
+ return void 0;
157
+ }
158
+ return webActorFromEmail(
159
+ email,
160
+ stored?.fullName ? { fullName: stored.fullName } : void 0
161
+ );
162
+ }
163
+ function buildApiTurnInboundMessage(args) {
164
+ const text = args.message.trim();
165
+ if (!text) {
166
+ throw new Error("API conversation message must not be empty");
167
+ }
168
+ if (!args.actor.email) {
169
+ throw new Error("API conversation actor requires a verified email");
170
+ }
171
+ const destination = resolveApiTurnDestination({
172
+ conversationId: args.conversationId,
173
+ existing: args.destination
174
+ });
175
+ const nowMs = args.nowMs ?? Date.now();
176
+ return {
177
+ conversationId: args.conversationId,
178
+ createdAtMs: args.createdAtMs ?? nowMs,
179
+ delivery: "defer",
180
+ destination,
181
+ inboundMessageId: args.messageId,
182
+ input: {
183
+ authorId: args.actor.userId,
184
+ text,
185
+ metadata: {
186
+ authorEmail: normalizeEmail(args.actor.email),
187
+ ...args.actor.fullName ? { authorFullName: args.actor.fullName } : {},
188
+ authorUserId: args.actor.userId,
189
+ ...args.actor.userName ? { authorUserName: args.actor.userName } : {},
190
+ kind: "api_turn",
191
+ messageId: args.messageId
192
+ }
193
+ },
194
+ receivedAtMs: nowMs,
195
+ publishExternally: false,
196
+ source: "web"
197
+ };
198
+ }
199
+ async function recordApiConversationActivity(args) {
200
+ const store = args.conversationStore ?? getConversationStore();
201
+ const existing = await store.get({ conversationId: args.conversationId });
202
+ const destination = resolveApiTurnDestination({
203
+ conversationId: args.conversationId,
204
+ existing: existing?.destination
205
+ });
206
+ const isNewRoot = !existing;
207
+ const source = isNewRoot ? webSourceForConversation({ conversationId: args.conversationId }) : void 0;
208
+ await store.recordActivity({
209
+ conversationId: args.conversationId,
210
+ destination,
211
+ nowMs: args.nowMs,
212
+ actor: storedActorFromApi(args.actor),
213
+ // Do not rewrite a Slack root's origin source when a dashboard participant
214
+ // continues it. Mailbox entries still carry source "web" per turn.
215
+ ...isNewRoot ? { source: "web" } : {},
216
+ ...source ? { sessionSource: source } : {},
217
+ ...isNewRoot ? { visibility: "public" } : {}
218
+ });
219
+ return destination;
220
+ }
221
+ async function createAndEnqueueApiConversation(input, options) {
222
+ if (!input.actor.email) {
223
+ throw new Error("API conversation actor requires a verified email");
224
+ }
225
+ const conversationId = createApiConversationId({
226
+ actorEmail: input.actor.email,
227
+ idempotencyKey: input.idempotencyKey
228
+ });
229
+ return await appendAndEnqueueApiConversationMessage(
230
+ {
231
+ actor: input.actor,
232
+ conversationId,
233
+ idempotencyKey: input.idempotencyKey,
234
+ message: input.message
235
+ },
236
+ options
237
+ );
238
+ }
239
+ async function appendAndEnqueueApiConversationMessage(input, options) {
240
+ const text = input.message.trim();
241
+ if (!text) {
242
+ throw new Error("API conversation message must not be empty");
243
+ }
244
+ if (!input.actor.email) {
245
+ throw new Error("API conversation actor requires a verified email");
246
+ }
247
+ const nowMs = options.nowMs ?? Date.now();
248
+ const messageId = apiMessageId({
249
+ conversationId: input.conversationId,
250
+ idempotencyKey: input.idempotencyKey
251
+ });
252
+ const destination = await recordApiConversationActivity({
253
+ actor: input.actor,
254
+ conversationId: input.conversationId,
255
+ conversationStore: options.conversationStore,
256
+ nowMs
257
+ });
258
+ const result = await appendAndEnqueueInboundMessage({
259
+ message: buildApiTurnInboundMessage({
260
+ actor: input.actor,
261
+ conversationId: input.conversationId,
262
+ destination,
263
+ message: text,
264
+ messageId,
265
+ nowMs
266
+ }),
267
+ conversationStore: options.conversationStore,
268
+ nowMs,
269
+ queue: options.queue,
270
+ state: options.state
271
+ });
272
+ return {
273
+ conversationId: input.conversationId,
274
+ messageId,
275
+ status: result.status === "duplicate" ? "duplicate" : "accepted"
276
+ };
277
+ }
278
+ function parseApiTurnMessages(messages) {
279
+ if (messages.length === 0) {
280
+ return [];
281
+ }
282
+ const parsed = messages.map((message) => ({
283
+ message,
284
+ metadata: apiTurnMailboxMetadataSchema.safeParse(message.input.metadata)
285
+ }));
286
+ if (parsed.every((entry) => !entry.metadata.success)) {
287
+ return [];
288
+ }
289
+ if (parsed.some((entry) => !entry.metadata.success)) {
290
+ throw new Error("Conversation mailbox mixes web turns and other input");
291
+ }
292
+ return parsed.map((entry) => {
293
+ if (!entry.metadata.success) {
294
+ throw new Error("API turn mailbox metadata failed validation");
295
+ }
296
+ return { message: entry.message, metadata: entry.metadata.data };
297
+ });
298
+ }
299
+ async function resolveApiTurnWork(context) {
300
+ const batch = parseApiTurnMessages(context.attempt.messages);
301
+ if (batch.length > 0) {
302
+ return { kind: "mailbox", batch };
303
+ }
304
+ if (context.attempt.messages.length > 0) {
305
+ return void 0;
306
+ }
307
+ const summaries = await listTurnSummaries(context.conversationId);
308
+ const active = summaries.filter(
309
+ (summary) => summary.surface === "api" && !summary.dispatchId && (summary.state === "paused" || summary.state === "running")
310
+ );
311
+ if (active.length > 1) {
312
+ throw new Error(
313
+ `Conversation ${context.conversationId} has multiple active web turns`
314
+ );
315
+ }
316
+ const turnId = active[0]?.turnId;
317
+ if (!turnId) {
318
+ return void 0;
319
+ }
320
+ const record = await getTurnRecord(context.conversationId, turnId);
321
+ if (!record || record.surface !== "api" || Boolean(record.dispatchId) || record.state !== "paused" && record.state !== "running") {
322
+ return void 0;
323
+ }
324
+ return { kind: "resume", turnId };
325
+ }
326
+ async function isApiTurnWork(context) {
327
+ return await resolveApiTurnWork(context) !== void 0;
328
+ }
329
+ function captureApiBoundaryFailure(args) {
330
+ const eventId = logException(args.error, `api.turn.${args.failureCode}`, {
331
+ conversationId: args.conversationId,
332
+ ...args.runId ? { runId: args.runId } : {},
333
+ turnId: args.turnId
334
+ });
335
+ return typeof eventId === "string" ? eventId : void 0;
336
+ }
337
+ function createApiTurnWorker(options) {
338
+ return async (context) => {
339
+ const resolved = await resolveApiTurnWork(context);
340
+ if (!resolved) {
341
+ throw new Error(
342
+ `API turn worker received non-API work for ${context.conversationId}`
343
+ );
344
+ }
345
+ if (context.publishExternally) {
346
+ throw new Error(
347
+ `API turn work must not publish externally for ${context.conversationId}`
348
+ );
349
+ }
350
+ const lifecycle = options.turnLifecycle ?? new ConversationTurnLifecycleService(getConversationEventStore());
351
+ const isResume = resolved.kind === "resume";
352
+ let actor;
353
+ let text;
354
+ let turnId;
355
+ let userMessageId;
356
+ let startedAtMs;
357
+ let inputMessageIds;
358
+ const storedConversation = await getConversationStore().get({
359
+ conversationId: context.conversationId
360
+ });
361
+ if (resolved.kind === "mailbox") {
362
+ const first = resolved.batch[0];
363
+ text = resolved.batch.map((entry) => entry.message.input.text.trim()).filter(Boolean).join("\n\n");
364
+ actor = actorFromMetadata(first.metadata);
365
+ turnId = apiTurnIdForMessage(first.metadata.messageId);
366
+ userMessageId = first.metadata.messageId;
367
+ startedAtMs = first.message.createdAtMs;
368
+ inputMessageIds = resolved.batch.map((entry) => entry.metadata.messageId);
369
+ } else {
370
+ turnId = resolved.turnId;
371
+ const resumedActor = actorFromStoredConversation(
372
+ storedConversation?.actor
373
+ );
374
+ if (!resumedActor) {
375
+ throw new Error(
376
+ `API turn resume missing actor for ${context.conversationId}`
377
+ );
378
+ }
379
+ actor = resumedActor;
380
+ text = "";
381
+ userMessageId = "";
382
+ startedAtMs = Date.now();
383
+ inputMessageIds = [];
384
+ }
385
+ const destination = resolveApiTurnDestination({
386
+ conversationId: context.conversationId,
387
+ existing: context.destination ?? (resolved.kind === "mailbox" ? resolved.batch[0]?.message.destination : void 0) ?? storedConversation?.destination
388
+ });
389
+ const source = webSourceForConversation({
390
+ conversationId: context.conversationId,
391
+ visibility: storedConversation?.visibility
392
+ });
393
+ return await withLogContext(
394
+ {
395
+ conversationId: context.conversationId,
396
+ platform: "web",
397
+ userId: actor.userId,
398
+ ...actor.userName ? { userName: actor.userName } : {}
399
+ },
400
+ async () => {
401
+ let acknowledged = isResume || context.attempt.messages.length === 0;
402
+ const acknowledge = async () => {
403
+ if (acknowledged) {
404
+ return;
405
+ }
406
+ try {
407
+ await context.attempt.ack();
408
+ } catch {
409
+ throw new TurnInputCommitLostError(
410
+ `Conversation work lease lost before API turn inbox ack for ${context.conversationId}`
411
+ );
412
+ }
413
+ acknowledged = true;
414
+ };
415
+ const persisted = await getPersistedThreadState(context.conversationId);
416
+ const conversation = coerceThreadConversationState(persisted);
417
+ await hydrateConversationMessages({
418
+ conversation,
419
+ conversationId: context.conversationId
420
+ });
421
+ let sandboxRef = getPersistedSandboxState(persisted);
422
+ const initialSandboxRef = sandboxRef;
423
+ if (isResume) {
424
+ const userMessage = getTurnUserMessage(conversation, turnId);
425
+ if (!userMessage) {
426
+ throw new Error(
427
+ `Unable to locate the persisted user message for API turn "${turnId}"`
428
+ );
429
+ }
430
+ userMessageId = userMessage.id;
431
+ text = userMessage.text;
432
+ startedAtMs = userMessage.createdAtMs;
433
+ inputMessageIds = [userMessageId];
434
+ } else {
435
+ upsertConversationMessage(conversation, {
436
+ id: userMessageId,
437
+ role: "user",
438
+ text: normalizeConversationText(text),
439
+ createdAtMs: startedAtMs,
440
+ author: {
441
+ ...actor.fullName ? { fullName: actor.fullName } : {},
442
+ userId: actor.userId,
443
+ ...actor.userName ? { userName: actor.userName } : {}
444
+ },
445
+ meta: {
446
+ explicitMention: true,
447
+ replied: false
448
+ }
449
+ });
450
+ await persistConversationMessages({
451
+ conversation,
452
+ conversationId: context.conversationId
453
+ });
454
+ await lifecycle.start({
455
+ conversationId: context.conversationId,
456
+ createdAtMs: Date.now(),
457
+ inputMessageIds,
458
+ surface: "api",
459
+ turnId
460
+ });
461
+ startActiveTurn({
462
+ conversation,
463
+ nextTurnId: turnId
464
+ });
465
+ }
466
+ let currentRunId;
467
+ let assistantMessageDelivered = false;
468
+ let failureCode = "persistence_failed";
469
+ let modelFailureEventId;
470
+ let modelFailureCaptureAttempted = false;
471
+ let reply;
472
+ const deliverAssistantMessage = async (value) => {
473
+ const replyText = typeof value === "string" ? value : getAssistantReplyText(value);
474
+ if (!replyText?.trim()) {
475
+ return;
476
+ }
477
+ failureCode = "delivery_failed";
478
+ assistantMessageDelivered = true;
479
+ recordDeliveredAssistantMessage({
480
+ conversation,
481
+ sessionId: turnId,
482
+ text: replyText,
483
+ userMessageId
484
+ });
485
+ try {
486
+ await persistWithRetry(
487
+ () => persistConversationMessages({
488
+ conversation,
489
+ conversationId: context.conversationId
490
+ })
491
+ );
492
+ } catch (error) {
493
+ logException(
494
+ new Error("Accepted assistant message persistence failed"),
495
+ "api.assistant.message_post_delivery_persist.failed",
496
+ {
497
+ "error.type": error instanceof Error ? error.name : typeof error
498
+ }
499
+ );
500
+ }
501
+ failureCode = "agent_run_failed";
502
+ };
503
+ try {
504
+ await persistThreadStateById(context.conversationId, {
505
+ conversation
506
+ });
507
+ const piMessages = await loadProjection({
508
+ conversationId: context.conversationId
509
+ });
510
+ failureCode = "agent_run_failed";
511
+ currentRunId = `api-run:${stableHex(turnId, String(startedAtMs))}`;
512
+ setTags({ runId: currentRunId });
513
+ const outcome = await options.agentRunner.run({
514
+ conversationId: context.conversationId,
515
+ turnId,
516
+ runId: currentRunId,
517
+ input: {
518
+ messageText: text,
519
+ conversationContext: buildConversationContext(conversation, {
520
+ excludeMessageId: userMessageId
521
+ }),
522
+ piMessages
523
+ },
524
+ routing: {
525
+ actor,
526
+ credentialContext: credentialContextForActor(actor),
527
+ destination,
528
+ publishExternally: false,
529
+ source,
530
+ surface: "api"
531
+ },
532
+ policy: {
533
+ disabledFeatures: ["interactive-auth"]
534
+ },
535
+ state: {
536
+ pendingAuth: conversation.processing.pendingAuth,
537
+ sandboxRef
538
+ },
539
+ delivery: deliverAssistantMessage,
540
+ durability: {
541
+ onInputCommitted: acknowledge,
542
+ shouldYield: context.shouldYield,
543
+ onSandboxRefChanged: async (nextSandboxRef) => {
544
+ sandboxRef = nextSandboxRef;
545
+ await persistThreadStateById(context.conversationId, {
546
+ conversation,
547
+ sandboxRef
548
+ });
549
+ }
550
+ }
551
+ });
552
+ if (outcome.status === "suspended") {
553
+ return { status: "yielded" };
554
+ }
555
+ if (outcome.status !== "completed") {
556
+ throw new Error(`API agent run ended with ${outcome.status}`);
557
+ }
558
+ reply = outcome.result;
559
+ modelFailureCaptureAttempted = reply.diagnostics.outcome !== "success";
560
+ const finalized = finalizeFailedTurnReplyWithEvent({
561
+ reply,
562
+ logException
563
+ });
564
+ reply = finalized.reply;
565
+ modelFailureEventId = finalized.eventId;
566
+ if (reply.diagnostics.outcome !== "success") {
567
+ await deliverAssistantMessage(reply.text);
568
+ }
569
+ const completedState = buildDeliveredTurnStatePatch({
570
+ conversation,
571
+ reply,
572
+ sessionId: turnId,
573
+ userMessageId
574
+ });
575
+ await persistThreadStateById(context.conversationId, {
576
+ conversation: completedState.conversation,
577
+ sandboxRef: reply.sandboxRef ?? sandboxRef
578
+ });
579
+ if (reply.piMessages?.length) {
580
+ const latest = await getTurnRecord(context.conversationId, turnId);
581
+ await saveTurnCheckpoint({
582
+ mode: "completed",
583
+ conversationId: context.conversationId,
584
+ turnId,
585
+ sliceId: latest?.sliceId ?? 1,
586
+ messages: reply.piMessages,
587
+ durationMs: reply.diagnostics.durationMs,
588
+ usage: reply.diagnostics.usage,
589
+ destination,
590
+ publishExternally: false,
591
+ source,
592
+ actor,
593
+ surface: "api"
594
+ });
595
+ }
596
+ if (reply.diagnostics.outcome === "success") {
597
+ await lifecycle.complete({
598
+ conversationId: context.conversationId,
599
+ createdAtMs: Date.now(),
600
+ outcome: assistantMessageDelivered ? "success" : "no_reply",
601
+ turnId
602
+ });
603
+ try {
604
+ await scheduleSessionCompletedPluginTasks(
605
+ {
606
+ conversationId: context.conversationId,
607
+ sessionId: turnId
608
+ },
609
+ {
610
+ send: async (message) => {
611
+ try {
612
+ await processPluginTask(message);
613
+ } catch (error) {
614
+ logException(
615
+ error,
616
+ "api.plugin.session_completion_task.failed",
617
+ {
618
+ conversationId: context.conversationId,
619
+ pluginName: message.plugin,
620
+ taskName: message.name,
621
+ turnId
622
+ }
623
+ );
624
+ }
625
+ }
626
+ }
627
+ );
628
+ } catch (error) {
629
+ logException(error, "api.plugin.session_completion_task.failed", {
630
+ conversationId: context.conversationId,
631
+ turnId
632
+ });
633
+ }
634
+ } else {
635
+ await lifecycle.fail({
636
+ conversationId: context.conversationId,
637
+ createdAtMs: Date.now(),
638
+ ...modelFailureEventId ? { eventId: modelFailureEventId } : {},
639
+ failureCode: "model_execution_failed",
640
+ turnId
641
+ });
642
+ }
643
+ await acknowledge();
644
+ return { status: "completed" };
645
+ } catch (error) {
646
+ const cause = getConversationTurnBoundaryError(error)?.cause ?? error;
647
+ if (isTurnInputCommitLostError(error) || isTurnInputCommitLostError(cause)) {
648
+ return { status: "lost_lease" };
649
+ }
650
+ if (!context.attempt.isFinalAttempt) {
651
+ throw error;
652
+ }
653
+ const failureEventId = modelFailureCaptureAttempted && failureCode === "agent_run_failed" ? modelFailureEventId : captureApiBoundaryFailure({
654
+ conversationId: context.conversationId,
655
+ error,
656
+ failureCode,
657
+ runId: currentRunId,
658
+ turnId
659
+ });
660
+ try {
661
+ markTurnFailed({
662
+ conversation,
663
+ nowMs: Date.now(),
664
+ sessionId: turnId,
665
+ userMessageId,
666
+ markConversationMessage
667
+ });
668
+ await persistThreadStateById(context.conversationId, {
669
+ conversation,
670
+ sandboxRef: initialSandboxRef ?? null
671
+ });
672
+ } catch (persistenceError) {
673
+ captureApiBoundaryFailure({
674
+ conversationId: context.conversationId,
675
+ error: persistenceError,
676
+ failureCode: "persistence_failed",
677
+ runId: currentRunId,
678
+ turnId
679
+ });
680
+ }
681
+ await lifecycle.fail({
682
+ conversationId: context.conversationId,
683
+ createdAtMs: Date.now(),
684
+ ...failureEventId ? { eventId: failureEventId } : {},
685
+ failureCode,
686
+ turnId
687
+ });
688
+ await acknowledge();
689
+ return { status: "completed" };
690
+ }
691
+ }
692
+ );
693
+ };
694
+ }
695
+ function routeApiTurnWork(options) {
696
+ return async (context) => {
697
+ return await isApiTurnWork(context) ? await options.apiTurnWorker(context) : await options.fallbackWorker(context);
698
+ };
699
+ }
700
+
701
+ // src/chat/slack/conversation-context.ts
702
+ function normalizeConversationName(type, channelName) {
703
+ const trimmed = channelName?.trim();
704
+ if (!trimmed) return void 0;
705
+ if (type === "public_channel" || type === "private_channel" || type === "private_channel_or_group_dm") {
706
+ return trimmed.startsWith("#") ? trimmed : `#${trimmed}`;
707
+ }
708
+ return trimmed;
709
+ }
710
+ function typeFromSlackChannelType(channelType) {
711
+ if (channelType === "channel") return "public_channel";
712
+ if (channelType === "group") return "private_channel";
713
+ if (channelType === "mpim") return "group_dm";
714
+ if (channelType === "im") return "direct_message";
715
+ return void 0;
716
+ }
717
+ function typeFromChannelId(channelId, channelName) {
718
+ const normalized = normalizeSlackConversationId(channelId);
719
+ if (!normalized) return void 0;
720
+ if (normalized.startsWith("C")) return "public_channel";
721
+ if (normalized.startsWith("D")) return "direct_message";
722
+ if (normalized.startsWith("G")) {
723
+ return channelName?.trim().startsWith("mpdm-") ? "group_dm" : "private_channel_or_group_dm";
724
+ }
725
+ return void 0;
726
+ }
727
+ function toSlackEventChannelType(channelType) {
728
+ if (channelType === "channel" || channelType === "group" || channelType === "mpim" || channelType === "im") {
729
+ return channelType;
730
+ }
731
+ return void 0;
732
+ }
733
+ function conversationVisibilityFromSlackChannelType(channelType) {
734
+ if (!channelType) return void 0;
735
+ return channelType === "channel" ? "public" : "private";
736
+ }
737
+ function resolveSlackChannelTypeFromMessage(message) {
738
+ const raw = message.raw;
739
+ if (!raw || typeof raw !== "object") {
740
+ return void 0;
741
+ }
742
+ const channelType = raw.channel_type;
743
+ return typeof channelType === "string" ? toSlackEventChannelType(channelType.trim()) : void 0;
744
+ }
745
+ function resolveSlackConversationContext(input) {
746
+ const type = typeFromSlackChannelType(input.channelType) ?? typeFromChannelId(input.channelId, input.channelName);
747
+ if (!type) return void 0;
748
+ const name = normalizeConversationName(type, input.channelName);
749
+ const visibility = conversationVisibilityFromSlackChannelType(
750
+ input.channelType
751
+ );
752
+ return {
753
+ type,
754
+ ...name ? { name } : {},
755
+ ...visibility ? { visibility } : {}
756
+ };
757
+ }
758
+ function resolveSlackConversationContextFromThreadId(input) {
759
+ const slackThread = parseSlackThreadId(input.threadId);
760
+ const context = resolveSlackConversationContext({
761
+ channelId: slackThread?.channelId,
762
+ channelName: input.channelName
763
+ });
764
+ if (!context || !input.visibility) {
765
+ return context;
766
+ }
767
+ return {
768
+ ...context,
769
+ type: input.visibility === "private" && context.type === "public_channel" ? "private_channel" : context.type,
770
+ visibility: input.visibility
771
+ };
772
+ }
773
+ function formatSlackConversationTypeLabel(type) {
774
+ if (type === "public_channel") return "Public Channel";
775
+ if (type === "private_channel") return "Private Channel";
776
+ if (type === "group_dm") return "Group DM";
777
+ if (type === "direct_message") return "Direct Message";
778
+ return "Private Channel or Group DM";
779
+ }
780
+ function formatSlackConversationRedactedLabel(context) {
781
+ if (!context) return void 0;
782
+ return formatSlackConversationTypeLabel(context.type);
783
+ }
784
+
785
+ export {
786
+ readHealthReport,
787
+ GET,
788
+ conversationVisibilityFromSlackChannelType,
789
+ resolveSlackChannelTypeFromMessage,
790
+ resolveSlackConversationContext,
791
+ resolveSlackConversationContextFromThreadId,
792
+ formatSlackConversationRedactedLabel,
793
+ webActorFromEmail,
794
+ createAndEnqueueApiConversation,
795
+ appendAndEnqueueApiConversationMessage,
796
+ createApiTurnWorker,
797
+ routeApiTurnWork
798
+ };