@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
@@ -0,0 +1,678 @@
1
+ import { m as normalizeSlackEmojiName } from "./config-nGCW1OTG.js";
2
+ import { F as isRecord, d as logInfo, f as logWarn } from "./logging-BzAk1S_s.js";
3
+ import { _ as parseSlackMessageTs, g as parseSlackUserId } from "./context-B45_WqVX.js";
4
+ import { B as getSlackClient, Bt as parseDestination, G as withSlackRetries, H as isDmChannel, I as SlackActionError, U as normalizeSlackConversationId, n as applyPluginFormatMarkdown } from "./agent-hooks-Bbcp9e33.js";
5
+ import { n as pluginCatalogRuntime } from "./catalog-runtime-BReos-D4.js";
6
+ import { w as getStateAdapter } from "./profile-CrT5nXbw.js";
7
+ import { r as isExperimentalFeatureEnabled } from "./experimental-CIXf7Uye.js";
8
+ import { randomBytes } from "node:crypto";
9
+ import { actorSchema, sourceSchema } from "@sentry/junior-plugin-api";
10
+ //#region src/chat/agent/types.ts
11
+ /** Resume the agent turn after a transient or ambiguous delivery failure. */
12
+ var RetryableDeliveryError = class extends Error {
13
+ constructor(cause) {
14
+ super("Assistant delivery was transient or ambiguous", { cause });
15
+ this.name = "RetryableDeliveryError";
16
+ }
17
+ };
18
+ /** Return whether one optional agent capability is disabled for this run. */
19
+ function isAgentRunFeatureDisabled(disabledFeatures, feature) {
20
+ return disabledFeatures?.includes(feature) ?? false;
21
+ }
22
+ /** Resolve the explicit actor, dispatch actor, or credential actor for this run. */
23
+ function actorFromRun(run) {
24
+ if (run.dispatch?.actor) return run.dispatch.actor;
25
+ if (run.actor) return run.actor;
26
+ if (run.credentialContext && !("type" in run.credentialContext.actor)) return run.credentialContext.actor;
27
+ }
28
+ /** Reject contradictory provider coordinates before the run touches state. */
29
+ function assertRunConsistency(run) {
30
+ const { destination, source } = run;
31
+ switch (source.kind) {
32
+ case "slack":
33
+ if (destination.platform !== "slack") throw new TypeError("Run source and destination platforms do not match");
34
+ if (source.teamId !== destination.teamId) throw new TypeError("Slack source and destination teams do not match");
35
+ break;
36
+ case "local":
37
+ if (destination.platform !== "local") throw new TypeError("Run source and destination platforms do not match");
38
+ if (source.conversationId !== destination.conversationId) throw new TypeError("Source and destination conversation IDs do not match");
39
+ if (run.surface !== "internal" && destination.conversationId !== run.conversationId) throw new TypeError("Source, destination, and run conversation IDs do not match");
40
+ break;
41
+ case "web":
42
+ if (run.surface !== "internal" && source.conversationId !== run.conversationId) throw new TypeError("Web source and run conversation IDs do not match");
43
+ switch (destination.platform) {
44
+ case "local":
45
+ if (source.conversationId !== destination.conversationId) throw new TypeError("Source and destination conversation IDs do not match");
46
+ if (run.surface !== "internal" && destination.conversationId !== run.conversationId) throw new TypeError("Source, destination, and run conversation IDs do not match");
47
+ }
48
+ break;
49
+ case "resource_event":
50
+ case "scheduled_task":
51
+ case "event_task":
52
+ case "plugin_dispatch":
53
+ case "agent_invocation": return;
54
+ }
55
+ const actor = run.dispatch?.actor ?? run.actor;
56
+ if (!actor || actor.platform === "system") return;
57
+ if (actor.platform !== source.kind) throw new TypeError(`Actor platform "${actor.platform}" does not match Source kind "${source.kind}"`);
58
+ if (actor.platform === "slack" && source.kind === "slack" && actor.teamId !== source.teamId) throw new TypeError("Slack Actor team does not match Source team");
59
+ }
60
+ /** Route tool side effects to the tool channel when one overrides the destination. */
61
+ function toolInvocationDestination(run) {
62
+ if (run.destination.platform !== "slack" || !run.toolChannelId) return run.destination;
63
+ return {
64
+ platform: "slack",
65
+ teamId: run.destination.teamId,
66
+ channelId: run.toolChannelId
67
+ };
68
+ }
69
+ /** Infer the run surface when the caller did not state one. */
70
+ function surfaceFromRun(run) {
71
+ if (run.surface) return run.surface;
72
+ if (run.source.kind === "slack") return "slack";
73
+ if (run.source.kind === "web") return "api";
74
+ return "internal";
75
+ }
76
+ //#endregion
77
+ //#region src/chat/slack/outbound.ts
78
+ const MAX_SLACK_MESSAGE_TEXT_CHARS = 4e4;
79
+ function requireSlackConversationId(channelId, action) {
80
+ const normalized = normalizeSlackConversationId(channelId);
81
+ if (!normalized) throw new Error(`${action} requires a valid channel ID`);
82
+ return normalized;
83
+ }
84
+ function requireSlackThreadTimestamp(threadTs, action) {
85
+ const normalized = threadTs.trim();
86
+ if (!normalized) throw new Error(`${action} requires a thread timestamp`);
87
+ return normalized;
88
+ }
89
+ function requireSlackMessageTimestamp(timestamp, action) {
90
+ const normalized = parseSlackMessageTs(timestamp);
91
+ if (!normalized) throw new Error(`${action} requires a target message timestamp`);
92
+ return normalized;
93
+ }
94
+ function requireSlackMessageText(text, action) {
95
+ if (text.trim().length === 0) throw new Error(`${action} requires non-empty text`);
96
+ if (text.length > MAX_SLACK_MESSAGE_TEXT_CHARS) throw new Error(`${action} text exceeds Slack's 40000 character truncation limit`);
97
+ return text;
98
+ }
99
+ /** Resolve a Slack message permalink without making read-only workflows fail on lookup errors. */
100
+ async function getSlackMessagePermalink(args) {
101
+ try {
102
+ return (await withSlackRetries(() => getSlackClient().chat.getPermalink({
103
+ channel: args.channelId,
104
+ message_ts: args.messageTs
105
+ }), 3, {
106
+ action: "chat.getPermalink",
107
+ idempotent: true,
108
+ spanAttributes: {
109
+ "app.slack.channel_id": args.channelId,
110
+ "app.slack.message_ts": args.messageTs
111
+ }
112
+ })).permalink;
113
+ } catch {
114
+ return;
115
+ }
116
+ }
117
+ /** Post Slack `mrkdwn` text to a conversation or thread via the shared outbound boundary. */
118
+ async function postSlackMessage(input) {
119
+ const channelId = requireSlackConversationId(input.channelId, "Slack message posting");
120
+ const text = requireSlackMessageText(input.text, "Slack message posting");
121
+ const threadTs = input.threadTs ? requireSlackThreadTimestamp(input.threadTs, "Slack thread message posting") : void 0;
122
+ const response = await withSlackRetries(() => getSlackClient().chat.postMessage({
123
+ channel: channelId,
124
+ text,
125
+ unfurl_links: false,
126
+ unfurl_media: false,
127
+ ...input.blocks?.length ? { blocks: input.blocks } : void 0,
128
+ ...threadTs ? { thread_ts: threadTs } : void 0
129
+ }), 3, {
130
+ action: "chat.postMessage",
131
+ spanAttributes: {
132
+ "app.slack.channel_id": channelId,
133
+ ...threadTs ? { "app.slack.thread_ts": threadTs } : void 0
134
+ }
135
+ });
136
+ const messageTs = parseSlackMessageTs(response.ts);
137
+ if (!messageTs) throw new Error("Slack message posted without ts");
138
+ return {
139
+ ts: messageTs,
140
+ ...input.includePermalink ? { permalink: await getSlackMessagePermalink({
141
+ channelId,
142
+ messageTs
143
+ }) } : void 0
144
+ };
145
+ }
146
+ /**
147
+ * Post an ephemeral Slack message. Delivery is best-effort on Slack's side, but
148
+ * request validation and Web API behavior are centralized here.
149
+ */
150
+ async function postSlackEphemeralMessage(input) {
151
+ const channelId = requireSlackConversationId(input.channelId, "Slack ephemeral message posting");
152
+ const userId = parseSlackUserId(input.userId);
153
+ if (!userId) throw new Error("Slack ephemeral message posting requires a user ID");
154
+ const text = requireSlackMessageText(input.text, "Slack ephemeral message posting");
155
+ const threadTs = input.threadTs ? requireSlackThreadTimestamp(input.threadTs, "Slack ephemeral thread message posting") : void 0;
156
+ return { messageTs: (await withSlackRetries(() => getSlackClient().chat.postEphemeral({
157
+ channel: channelId,
158
+ user: userId,
159
+ text,
160
+ ...threadTs ? { thread_ts: threadTs } : void 0
161
+ }), 3, {
162
+ action: "chat.postEphemeral",
163
+ spanAttributes: {
164
+ "app.slack.channel_id": channelId,
165
+ "app.slack.user_id": userId,
166
+ ...threadTs ? { "app.slack.thread_ts": threadTs } : void 0
167
+ }
168
+ })).message_ts };
169
+ }
170
+ /** Upload files into a Slack conversation or thread via the shared outbound file boundary. */
171
+ async function uploadFilesToConversation(input) {
172
+ const channelId = requireSlackConversationId(input.channelId, "Slack file upload");
173
+ const threadTs = input.threadTs ? requireSlackThreadTimestamp(input.threadTs, "Slack file upload") : void 0;
174
+ if (input.files.length === 0) throw new Error("Slack file upload requires at least one file");
175
+ const fileUploads = input.files.map((file) => {
176
+ const filename = file.filename.trim();
177
+ if (!filename) throw new Error("Slack file upload requires every file to have a filename");
178
+ return {
179
+ file: file.data,
180
+ filename
181
+ };
182
+ });
183
+ return { files: ((await withSlackRetries(() => getSlackClient().filesUploadV2({
184
+ channel_id: channelId,
185
+ ...threadTs ? { thread_ts: threadTs } : void 0,
186
+ file_uploads: fileUploads
187
+ }), 3, {
188
+ action: "filesUploadV2",
189
+ spanAttributes: {
190
+ "app.slack.channel_id": channelId,
191
+ ...threadTs ? { "app.slack.thread_ts": threadTs } : void 0
192
+ }
193
+ })).files ?? []).flatMap((completion) => (completion.files ?? []).map((file) => file.id ? { id: file.id } : {})) };
194
+ }
195
+ /** Add a reaction to a Slack message, treating `already_reacted` as idempotent success. */
196
+ async function addReactionToMessage(input) {
197
+ const channelId = requireSlackConversationId(input.channelId, "Slack reaction");
198
+ const timestamp = requireSlackMessageTimestamp(input.timestamp, "Slack reaction");
199
+ const emoji = normalizeSlackEmojiName(input.emoji);
200
+ if (!emoji) throw new Error("Slack reaction requires a valid emoji alias name");
201
+ try {
202
+ await withSlackRetries(() => getSlackClient().reactions.add({
203
+ channel: channelId,
204
+ timestamp,
205
+ name: emoji
206
+ }), 3, {
207
+ action: "reactions.add",
208
+ idempotent: true,
209
+ spanAttributes: {
210
+ "app.slack.channel_id": channelId,
211
+ "app.slack.message_ts": timestamp,
212
+ "app.slack.reaction": emoji
213
+ }
214
+ });
215
+ } catch (error) {
216
+ if (error instanceof SlackActionError && error.code === "already_reacted") return { ok: true };
217
+ throw error;
218
+ }
219
+ return { ok: true };
220
+ }
221
+ /** Remove a reaction from a Slack message, treating `no_reaction` as idempotent success. */
222
+ async function removeReactionFromMessage(input) {
223
+ const channelId = requireSlackConversationId(input.channelId, "Slack reaction removal");
224
+ const timestamp = requireSlackMessageTimestamp(input.timestamp, "Slack reaction removal");
225
+ const emoji = normalizeSlackEmojiName(input.emoji);
226
+ if (!emoji) throw new Error("Slack reaction removal requires a valid emoji alias name");
227
+ try {
228
+ await withSlackRetries(() => getSlackClient().reactions.remove({
229
+ channel: channelId,
230
+ timestamp,
231
+ name: emoji
232
+ }), 3, {
233
+ action: "reactions.remove",
234
+ idempotent: true,
235
+ spanAttributes: {
236
+ "app.slack.channel_id": channelId,
237
+ "app.slack.message_ts": timestamp,
238
+ "app.slack.reaction": emoji
239
+ }
240
+ });
241
+ } catch (error) {
242
+ if (error instanceof SlackActionError && error.code === "no_reaction") return { ok: true };
243
+ throw error;
244
+ }
245
+ return { ok: true };
246
+ }
247
+ //#endregion
248
+ //#region src/chat/slack/status-format.ts
249
+ const SLACK_STATUS_MAX_LENGTH = 50;
250
+ /** Truncate a status string to fit Slack's 50-char assistant status limit. */
251
+ function truncateStatusText(text) {
252
+ const trimmed = text.trim();
253
+ if (!trimmed) return "";
254
+ if (trimmed.length <= SLACK_STATUS_MAX_LENGTH) return trimmed;
255
+ return `${trimmed.slice(0, 47).trimEnd()}...`;
256
+ }
257
+ //#endregion
258
+ //#region src/chat/slack/mrkdwn.ts
259
+ /** Escape dynamic text for Slack mrkdwn without changing intended formatting. */
260
+ function escapeSlackMrkdwnText(text) {
261
+ return text.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;");
262
+ }
263
+ /** Escape a URL for Slack explicit link syntax while preserving query semantics. */
264
+ function escapeSlackLinkUrl(url) {
265
+ return url.replaceAll("&", "&amp;").replaceAll("<", "%3C").replaceAll(">", "%3E");
266
+ }
267
+ /** Build a Slack explicit link from dynamic URL and label values. */
268
+ function formatSlackLink(url, label) {
269
+ return `<${escapeSlackLinkUrl(url)}|${escapeSlackMrkdwnText(label)}>`;
270
+ }
271
+ function readInlineCodeSpan(line, start) {
272
+ if (line[start] !== "`") return;
273
+ let n = 1;
274
+ while (line[start + n] === "`") n++;
275
+ const marker = "`".repeat(n);
276
+ let search = start + n;
277
+ while (search < line.length) {
278
+ const close = line.indexOf(marker, search);
279
+ if (close === -1) return;
280
+ const after = close + n;
281
+ if (line[after] !== "`") return {
282
+ text: line.slice(start, after),
283
+ end: after
284
+ };
285
+ search = after + 1;
286
+ }
287
+ }
288
+ function readExistingSlackAngleToken(line, start) {
289
+ if (line[start] !== "<") return;
290
+ const close = line.indexOf(">", start + 1);
291
+ if (close === -1) return;
292
+ const body = line.slice(start + 1, close);
293
+ if (/^(?:https?:\/\/|@|#|!)/.test(body)) return {
294
+ text: line.slice(start, close + 1),
295
+ end: close + 1
296
+ };
297
+ }
298
+ function readMarkdownLink(line, start) {
299
+ if (line[start] !== "[") return;
300
+ const labelEnd = line.indexOf("](", start + 1);
301
+ if (labelEnd === -1) return;
302
+ const destStart = labelEnd + 2;
303
+ if (!line.startsWith("http://", destStart) && !line.startsWith("https://", destStart)) return;
304
+ const closeParens = line.indexOf(")", destStart);
305
+ if (closeParens === -1) return;
306
+ return {
307
+ text: line.slice(start, closeParens + 1),
308
+ end: closeParens + 1
309
+ };
310
+ }
311
+ function hasUnmatchedClosingParen(text) {
312
+ let balance = 0;
313
+ for (const ch of text) if (ch === "(") balance++;
314
+ else if (ch === ")") balance--;
315
+ return balance < 0;
316
+ }
317
+ function readBareUrl(line, start) {
318
+ let end = start;
319
+ while (end < line.length) {
320
+ const ch = line[end];
321
+ if (/\s/.test(ch) || ch === "<" || ch === ">" || ch === "\"" || ch === "`" || ch === "|" || ch === "*") break;
322
+ end++;
323
+ }
324
+ if (end === start) return;
325
+ let raw = line.slice(start, end);
326
+ let suffix = "";
327
+ const peel = () => {
328
+ suffix = raw.slice(-1) + suffix;
329
+ raw = raw.slice(0, -1);
330
+ };
331
+ const shouldPeel = () => raw.endsWith("_") || /[.,!?;:]$/.test(raw) || raw.endsWith(")") && hasUnmatchedClosingParen(raw);
332
+ while (raw.length > 0 && shouldPeel()) peel();
333
+ if (!/^https?:\/\/.+/.test(raw)) return;
334
+ return {
335
+ url: raw,
336
+ suffix,
337
+ end
338
+ };
339
+ }
340
+ /** Wrap bare http(s) URLs on a single line as Slack explicit `<url>` links. */
341
+ function wrapBareUrlsOnLine(line) {
342
+ let result = "";
343
+ let i = 0;
344
+ while (i < line.length) {
345
+ const codeSpan = readInlineCodeSpan(line, i);
346
+ if (codeSpan) {
347
+ result += codeSpan.text;
348
+ i = codeSpan.end;
349
+ continue;
350
+ }
351
+ const angleToken = readExistingSlackAngleToken(line, i);
352
+ if (angleToken) {
353
+ result += angleToken.text;
354
+ i = angleToken.end;
355
+ continue;
356
+ }
357
+ const mdLink = readMarkdownLink(line, i);
358
+ if (mdLink) {
359
+ result += mdLink.text;
360
+ i = mdLink.end;
361
+ continue;
362
+ }
363
+ if (line.startsWith("https://", i) || line.startsWith("http://", i)) {
364
+ const parsed = readBareUrl(line, i);
365
+ if (parsed) {
366
+ result += `<${parsed.url}>${parsed.suffix}`;
367
+ i = parsed.end;
368
+ continue;
369
+ }
370
+ }
371
+ result += line[i];
372
+ i++;
373
+ }
374
+ return result;
375
+ }
376
+ /**
377
+ * Pre-wrap bare http(s) URLs outside fenced code blocks as Slack explicit
378
+ * links, preventing Slack's auto-linker from consuming adjacent formatting
379
+ * markers into the URL.
380
+ *
381
+ * Uses the same fence-toggle rule as `ensureBlockSpacing` so both passes
382
+ * agree on which lines are code.
383
+ */
384
+ function wrapBareUrls(text) {
385
+ const lines = text.split("\n");
386
+ const out = [];
387
+ let inCodeBlock = false;
388
+ for (const line of lines) {
389
+ if (line.trimStart().startsWith("```")) {
390
+ inCodeBlock = !inCodeBlock;
391
+ out.push(line);
392
+ continue;
393
+ }
394
+ out.push(inCodeBlock ? line : wrapBareUrlsOnLine(line));
395
+ }
396
+ return out.join("\n");
397
+ }
398
+ /** Insert blank lines between content blocks so Slack renders them with visual separation. */
399
+ function ensureBlockSpacing(text) {
400
+ const codeBlockPattern = /^```/;
401
+ const listItemPattern = /^[-*•]\s|^\d+\.\s/;
402
+ const lines = text.split("\n");
403
+ const result = [];
404
+ let inCodeBlock = false;
405
+ for (let i = 0; i < lines.length; i++) {
406
+ const line = lines[i];
407
+ if (codeBlockPattern.test(line.trimStart())) {
408
+ if (!inCodeBlock) {
409
+ const prev = result.length > 0 ? result[result.length - 1] : void 0;
410
+ if (prev !== void 0 && prev.trim() !== "") result.push("");
411
+ }
412
+ inCodeBlock = !inCodeBlock;
413
+ result.push(line);
414
+ continue;
415
+ }
416
+ if (inCodeBlock) {
417
+ result.push(line);
418
+ continue;
419
+ }
420
+ const prev = result.length > 0 ? result[result.length - 1] : void 0;
421
+ if (prev !== void 0 && prev.trim() !== "" && line.trim() !== "" && !(listItemPattern.test(prev.trimStart()) && listItemPattern.test(line.trimStart()))) result.push("");
422
+ result.push(line);
423
+ }
424
+ return result.join("\n");
425
+ }
426
+ /**
427
+ * Normalize model-authored Slack markdown for delivery via `markdown_text`
428
+ * or `{ type: "markdown" }` blocks.
429
+ *
430
+ * Plugins may first rewrite ordinary Markdown. Core then applies Slack-only
431
+ * delivery shaping: bare URL wrapping and block spacing. Slack reply delivery
432
+ * owns chunking and continuation markers separately.
433
+ */
434
+ function normalizeSlackReplyMarkdown(text) {
435
+ let normalized = applyPluginFormatMarkdown(text).replace(/\r\n?/g, "\n").replace(/[ \t]+$/gm, "");
436
+ normalized = wrapBareUrls(normalized);
437
+ normalized = ensureBlockSpacing(normalized);
438
+ return normalized.replace(/\n{3,}/g, "\n\n").trim();
439
+ }
440
+ /** Normalize assistant status text before handing it to Slack. */
441
+ function normalizeSlackStatusText(text) {
442
+ const trimmed = text.trim();
443
+ if (!trimmed) return "";
444
+ return truncateStatusText(trimmed.replace(/(?:\.\s*)+$/, "").trim());
445
+ }
446
+ //#endregion
447
+ //#region src/chat/slack/oauth-authorization-message.ts
448
+ /** Format the private OAuth authorization prompt for Slack delivery. */
449
+ function formatOAuthAuthorizationMessage(args) {
450
+ return `${formatSlackLink(args.authorizationUrl, args.label)}. ${args.completionText}`;
451
+ }
452
+ //#endregion
453
+ //#region src/chat/oauth-flow.ts
454
+ const OAUTH_STATE_TTL_MS = 6e5;
455
+ function optionalString(value) {
456
+ return typeof value === "string" ? value : void 0;
457
+ }
458
+ /** Parse OAuth callback state that was persisted before a provider redirect. */
459
+ function parseOAuthStatePayload(value) {
460
+ if (!isRecord(value)) return;
461
+ if (typeof value.userId !== "string" || typeof value.provider !== "string") return;
462
+ const actor = value.actor === void 0 ? void 0 : actorSchema.safeParse(value.actor);
463
+ if (value.actor !== void 0 && (!actor || !actor.success)) return;
464
+ const destination = parseDestination(value.destination);
465
+ if (value.destination !== void 0 && !destination) return;
466
+ const source = value.source === void 0 ? void 0 : sourceSchema.safeParse(value.source);
467
+ if (value.source !== void 0 && (!source || !source.success)) return;
468
+ return {
469
+ userId: value.userId,
470
+ provider: value.provider,
471
+ ...actor?.success ? { actor: actor.data } : void 0,
472
+ ...optionalString(value.channelId) ? { channelId: optionalString(value.channelId) } : void 0,
473
+ ...destination ? { destination } : void 0,
474
+ ...source?.success ? { source: source.data } : void 0,
475
+ ...optionalString(value.threadTs) ? { threadTs: optionalString(value.threadTs) } : void 0,
476
+ ...optionalString(value.resumeConversationId) ? { resumeConversationId: optionalString(value.resumeConversationId) } : void 0,
477
+ ...optionalString(value.resumeSessionId) ? { resumeSessionId: optionalString(value.resumeSessionId) } : void 0,
478
+ ...optionalString(value.scope) ? { scope: optionalString(value.scope) } : void 0
479
+ };
480
+ }
481
+ /** Return the manifest-owned display label for a provider. */
482
+ function formatProviderLabel(provider) {
483
+ const displayName = pluginCatalogRuntime.getDisplayName(provider);
484
+ if (!displayName) throw new Error(`Unknown plugin provider display name: "${provider}"`);
485
+ return displayName;
486
+ }
487
+ /** Resolve the public base URL from environment variables (JUNIOR_BASE_URL or Vercel). */
488
+ function resolveBaseUrl() {
489
+ const explicit = process.env.JUNIOR_BASE_URL?.trim();
490
+ if (explicit) return explicit;
491
+ const vercelProd = process.env.VERCEL_PROJECT_PRODUCTION_URL?.trim();
492
+ if (vercelProd) return `https://${vercelProd}`;
493
+ const vercelUrl = process.env.VERCEL_URL?.trim();
494
+ if (vercelUrl) return `https://${vercelUrl}`;
495
+ }
496
+ /**
497
+ * Authorization links must only be visible to the requesting user.
498
+ * Try in-context private delivery first, then fall back to a DM.
499
+ */
500
+ async function deliverPrivateMessage(input) {
501
+ let client;
502
+ try {
503
+ client = getSlackClient();
504
+ } catch {
505
+ logWarn("oauth.private_delivery.skipped", { "app.reason": "missing_bot_token" });
506
+ return false;
507
+ }
508
+ if (input.channelId) try {
509
+ if (isDmChannel(input.channelId)) await postSlackMessage({
510
+ channelId: input.channelId,
511
+ text: input.text,
512
+ threadTs: input.threadTs
513
+ });
514
+ else await postSlackEphemeralMessage({
515
+ channelId: input.channelId,
516
+ userId: input.userId,
517
+ text: input.text,
518
+ threadTs: input.threadTs
519
+ });
520
+ return "in_context";
521
+ } catch (error) {
522
+ logWarn("oauth.private_delivery.failed", {
523
+ "app.slack.error": error instanceof Error ? error.message : String(error),
524
+ "app.slack.channel": input.channelId
525
+ });
526
+ }
527
+ try {
528
+ const dmChannelId = (await withSlackRetries(() => client.conversations.open({ users: input.userId }), 3, {
529
+ action: "conversations.open",
530
+ spanAttributes: { "app.slack.user_id": input.userId }
531
+ })).channel?.id;
532
+ if (!dmChannelId) {
533
+ logWarn("oauth.dm.fallback.failed", { "app.reason": "no_dm_channel_id" });
534
+ return false;
535
+ }
536
+ await postSlackMessage({
537
+ channelId: dmChannelId,
538
+ text: input.text
539
+ });
540
+ return "fallback_dm";
541
+ } catch (error) {
542
+ logWarn("oauth.dm.fallback.failed", { "app.slack.error": error instanceof Error ? error.message : String(error) });
543
+ return false;
544
+ }
545
+ }
546
+ /** Present one OAuth request through the active surface's private delivery. */
547
+ async function deliverOAuthAuthorization(request, input) {
548
+ if (input.authorization) {
549
+ await input.authorization.deliver(request);
550
+ return "in_context";
551
+ }
552
+ return await deliverPrivateMessage({
553
+ channelId: input.channelId,
554
+ threadTs: input.threadTs,
555
+ userId: input.userId,
556
+ text: formatOAuthAuthorizationMessage(request)
557
+ });
558
+ }
559
+ /** Initiate an OAuth authorization code flow for a provider and deliver the auth link to the user. */
560
+ async function startOAuthFlow(provider, input) {
561
+ const providerConfig = pluginCatalogRuntime.getOAuthConfig(provider);
562
+ if (!providerConfig) return {
563
+ ok: false,
564
+ error: `Provider "${provider}" does not support OAuth authorization`
565
+ };
566
+ const clientId = process.env[providerConfig.clientIdEnv]?.trim();
567
+ if (!clientId) return {
568
+ ok: false,
569
+ error: `Missing ${providerConfig.clientIdEnv} environment variable`
570
+ };
571
+ const baseUrl = resolveBaseUrl();
572
+ if (!baseUrl) return {
573
+ ok: false,
574
+ error: "Cannot determine base URL (set JUNIOR_BASE_URL or deploy to Vercel)"
575
+ };
576
+ const state = input.authorization ? await input.authorization.createState() : randomBytes(32).toString("hex");
577
+ const requestedScope = input.scope ?? providerConfig.scope;
578
+ await getStateAdapter().set(`oauth-state:${state}`, {
579
+ userId: input.actorId,
580
+ provider,
581
+ ...input.actor ? { actor: input.actor } : void 0,
582
+ ...input.channelId ? { channelId: input.channelId } : void 0,
583
+ ...input.destination ? { destination: input.destination } : void 0,
584
+ ...input.source ? { source: input.source } : void 0,
585
+ ...input.threadTs ? { threadTs: input.threadTs } : void 0,
586
+ ...input.resumeConversationId ? { resumeConversationId: input.resumeConversationId } : void 0,
587
+ ...input.resumeSessionId ? { resumeSessionId: input.resumeSessionId } : void 0,
588
+ ...requestedScope ? { scope: requestedScope } : void 0
589
+ }, OAUTH_STATE_TTL_MS);
590
+ const authorizeParams = new URLSearchParams({
591
+ client_id: clientId,
592
+ state,
593
+ redirect_uri: `${baseUrl}${providerConfig.callbackPath}`,
594
+ response_type: "code"
595
+ });
596
+ if (requestedScope) authorizeParams.set("scope", requestedScope);
597
+ for (const [key, value] of Object.entries(providerConfig.authorizeParams ?? {})) authorizeParams.set(key, value);
598
+ logInfo("jr_rpc.oauth.started", {
599
+ "app.credential.provider": provider,
600
+ ...input.activeSkillName ? { "app.skill.name": input.activeSkillName } : void 0
601
+ });
602
+ return {
603
+ ok: true,
604
+ delivery: await deliverOAuthAuthorization({
605
+ authorizationUrl: `${providerConfig.authorizeEndpoint}?${authorizeParams.toString()}`,
606
+ label: `Click here to link your ${formatProviderLabel(provider)} account`,
607
+ completionText: input.resumeSessionId ? "Once you've authorized, Junior will continue automatically." : "Once you've authorized, you'll see a confirmation in Slack."
608
+ }, {
609
+ authorization: input.authorization,
610
+ channelId: input.channelId,
611
+ threadTs: input.threadTs,
612
+ userId: input.actorId
613
+ })
614
+ };
615
+ }
616
+ //#endregion
617
+ //#region src/chat/runtime/agent-runner.ts
618
+ const AGENT_ABORT_SETTLE_GRACE_MS = 5e3;
619
+ /** Compose the agent executor with stable host dependencies. */
620
+ function createAgentRunner(execute, options) {
621
+ const attachmentStorage = options?.attachmentStorage;
622
+ const streamFn = options?.streamFn;
623
+ const tracePropagation = options?.tracePropagation;
624
+ const bindSpawnAgent = options?.bindSpawnAgent;
625
+ const canBindSpawn = Boolean(bindSpawnAgent) && isExperimentalFeatureEnabled("subagents");
626
+ return { run: async (run) => {
627
+ const spawnAgent = bindSpawnAgent && canBindSpawn && !isAgentRunFeatureDisabled(run.disabledFeatures, "subagents") ? bindSpawnAgent(run) : void 0;
628
+ return await execute({
629
+ ...run,
630
+ environment: {
631
+ ...run.environment,
632
+ attachmentStorage: run.environment?.attachmentStorage ?? attachmentStorage,
633
+ sandboxTracePropagation: run.environment?.sandboxTracePropagation ?? tracePropagation
634
+ },
635
+ ...spawnAgent ? { durability: {
636
+ ...run.durability,
637
+ spawnAgent
638
+ } } : void 0
639
+ }, streamFn);
640
+ } };
641
+ }
642
+ async function waitForAgentSettlement(runPromise) {
643
+ let timeoutId;
644
+ const settleGrace = new Promise((resolve) => {
645
+ timeoutId = setTimeout(resolve, AGENT_ABORT_SETTLE_GRACE_MS);
646
+ timeoutId.unref?.();
647
+ });
648
+ await Promise.race([runPromise.then(() => void 0, () => void 0), settleGrace]);
649
+ if (timeoutId) clearTimeout(timeoutId);
650
+ }
651
+ /** Abort a timed-out agent run and wait briefly for its work to settle. */
652
+ async function runAgentWithTimeout(agentRunner, run, timeoutMs) {
653
+ if (typeof timeoutMs !== "number") return await agentRunner.run(run);
654
+ const timeoutError = /* @__PURE__ */ new Error(`executeAgentRun timed out after ${timeoutMs}ms`);
655
+ const abortController = new AbortController();
656
+ const signal = run.signal ? AbortSignal.any([run.signal, abortController.signal]) : abortController.signal;
657
+ const runPromise = agentRunner.run({
658
+ ...run,
659
+ signal
660
+ });
661
+ let timeoutId;
662
+ const timeoutPromise = new Promise((_, reject) => {
663
+ timeoutId = setTimeout(() => {
664
+ abortController.abort(timeoutError);
665
+ reject(timeoutError);
666
+ }, timeoutMs);
667
+ });
668
+ try {
669
+ return await Promise.race([runPromise, timeoutPromise]);
670
+ } catch (error) {
671
+ if (abortController.signal.aborted) await waitForAgentSettlement(runPromise);
672
+ throw error;
673
+ } finally {
674
+ if (timeoutId) clearTimeout(timeoutId);
675
+ }
676
+ }
677
+ //#endregion
678
+ export { toolInvocationDestination as C, surfaceFromRun as S, uploadFilesToConversation as _, parseOAuthStatePayload as a, assertRunConsistency as b, escapeSlackMrkdwnText as c, normalizeSlackStatusText as d, truncateStatusText as f, removeReactionFromMessage as g, postSlackMessage as h, formatProviderLabel as i, formatSlackLink as l, getSlackMessagePermalink as m, runAgentWithTimeout as n, resolveBaseUrl as o, addReactionToMessage as p, deliverOAuthAuthorization as r, startOAuthFlow as s, createAgentRunner as t, normalizeSlackReplyMarkdown as u, RetryableDeliveryError as v, isAgentRunFeatureDisabled as x, actorFromRun as y };