@sentry/junior 0.68.0 → 0.70.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 (67) hide show
  1. package/dist/app.js +1779 -746
  2. package/dist/build/virtual-config.d.ts +2 -2
  3. package/dist/chat/agent-dispatch/heartbeat.d.ts +2 -2
  4. package/dist/chat/agent-dispatch/store.d.ts +4 -1
  5. package/dist/chat/agent-dispatch/types.d.ts +2 -4
  6. package/dist/chat/agent-dispatch/validation.d.ts +3 -2
  7. package/dist/chat/credentials/context.d.ts +49 -24
  8. package/dist/chat/credentials/user-token-store.d.ts +6 -0
  9. package/dist/chat/destination.d.ts +12 -0
  10. package/dist/chat/ingress/message-router.d.ts +1 -1
  11. package/dist/chat/mcp/auth-store.d.ts +2 -0
  12. package/dist/chat/mcp/oauth.d.ts +2 -0
  13. package/dist/chat/oauth-flow.d.ts +7 -0
  14. package/dist/chat/plugins/agent-hooks.d.ts +9 -9
  15. package/dist/chat/plugins/auth/auth-token-placeholder.d.ts +2 -2
  16. package/dist/chat/plugins/auth/oauth-request.d.ts +3 -1
  17. package/dist/chat/plugins/credential-hooks.d.ts +53 -0
  18. package/dist/chat/plugins/logging.d.ts +1 -1
  19. package/dist/chat/plugins/state.d.ts +1 -1
  20. package/dist/chat/plugins/types.d.ts +19 -23
  21. package/dist/chat/respond.d.ts +2 -0
  22. package/dist/chat/runtime/reply-executor.d.ts +3 -1
  23. package/dist/chat/runtime/slack-runtime.d.ts +8 -3
  24. package/dist/chat/sandbox/egress-credentials.d.ts +34 -0
  25. package/dist/chat/sandbox/egress-schemas.d.ts +105 -0
  26. package/dist/chat/sandbox/egress-session.d.ts +17 -17
  27. package/dist/chat/sandbox/sandbox.d.ts +3 -0
  28. package/dist/chat/sandbox/session.d.ts +1 -0
  29. package/dist/chat/services/mcp-auth-orchestration.d.ts +2 -0
  30. package/dist/chat/services/pending-auth.d.ts +2 -0
  31. package/dist/chat/services/plugin-auth-orchestration.d.ts +2 -0
  32. package/dist/chat/services/provider-retry.d.ts +13 -4
  33. package/dist/chat/services/timeout-resume.d.ts +2 -0
  34. package/dist/chat/services/turn-session-record.d.ts +6 -0
  35. package/dist/chat/slack/attachment-fetchers.d.ts +11 -0
  36. package/dist/chat/state/conversation-details.d.ts +46 -0
  37. package/dist/chat/state/conversation.d.ts +1 -0
  38. package/dist/chat/state/turn-session.d.ts +4 -3
  39. package/dist/chat/task-execution/queue.d.ts +2 -0
  40. package/dist/chat/task-execution/store.d.ts +5 -0
  41. package/dist/chat/task-execution/vercel-callback.d.ts +4 -0
  42. package/dist/chat/task-execution/vercel-queue.d.ts +2 -0
  43. package/dist/chat/task-execution/worker.d.ts +4 -2
  44. package/dist/chat/tools/slack/context.d.ts +3 -0
  45. package/dist/chat/tools/types.d.ts +21 -2
  46. package/dist/chunk-76YMBKW7.js +326 -0
  47. package/dist/{chunk-PIVOJIUD.js → chunk-B5HKWWQB.js} +9 -5
  48. package/dist/chunk-BBXYXOJW.js +1858 -0
  49. package/dist/{chunk-V47RLIO2.js → chunk-GT67ZWZQ.js} +4 -4
  50. package/dist/{chunk-UQQSW7QB.js → chunk-HOGQL2H6.js} +197 -343
  51. package/dist/{chunk-75UZ4JLC.js → chunk-IGLNC5H6.js} +21 -9
  52. package/dist/{chunk-EBVQXCD2.js → chunk-JS4HURDT.js} +362 -280
  53. package/dist/chunk-R62YWUNO.js +264 -0
  54. package/dist/{chunk-OIIXZOOC.js → chunk-UXG6TU2U.js} +311 -2015
  55. package/dist/cli/check.js +4 -4
  56. package/dist/cli/init.js +18 -1
  57. package/dist/cli/snapshot-warmup.js +5 -4
  58. package/dist/nitro.d.ts +1 -1
  59. package/dist/nitro.js +21 -19
  60. package/dist/plugins.d.ts +2 -2
  61. package/dist/reporting.d.ts +8 -4
  62. package/dist/reporting.js +72 -29
  63. package/package.json +6 -4
  64. package/dist/chat/plugins/auth/github-app-broker.d.ts +0 -4
  65. package/dist/chat/plugins/github-permissions.d.ts +0 -11
  66. package/dist/chat/queue/thread-message-dispatcher.d.ts +0 -33
  67. package/dist/chunk-KVZL5NZS.js +0 -519
@@ -1,30 +1,40 @@
1
1
  import {
2
2
  SANDBOX_DATA_ROOT,
3
3
  SANDBOX_WORKSPACE_ROOT,
4
- botConfig,
5
- getChatConfig,
6
4
  getConnectedStateContext,
7
- getSlackBotToken,
8
5
  getStateAdapter,
9
- parseSlackThreadId,
10
6
  sandboxSkillDir
11
- } from "./chunk-EBVQXCD2.js";
7
+ } from "./chunk-R62YWUNO.js";
12
8
  import {
13
9
  isActorUserId,
10
+ parseActorUserId
11
+ } from "./chunk-UXG6TU2U.js";
12
+ import {
13
+ isConversationChannel,
14
+ isConversationScopedChannel,
15
+ isDmChannel,
16
+ normalizeSlackConversationId,
17
+ parseDestination
18
+ } from "./chunk-76YMBKW7.js";
19
+ import {
20
+ TURN_CONTEXT_TAG,
21
+ botConfig,
22
+ getChatConfig,
23
+ parseSlackThreadId
24
+ } from "./chunk-JS4HURDT.js";
25
+ import {
14
26
  isRecord,
27
+ listReferenceFiles,
15
28
  logException,
16
29
  logInfo,
17
30
  logWarn,
18
- parseActorUserId
19
- } from "./chunk-OIIXZOOC.js";
31
+ soulPathCandidates,
32
+ toOptionalNumber,
33
+ worldPathCandidates
34
+ } from "./chunk-BBXYXOJW.js";
20
35
  import {
21
36
  sentry_exports
22
37
  } from "./chunk-Z3YD6NHK.js";
23
- import {
24
- listReferenceFiles,
25
- soulPathCandidates,
26
- worldPathCandidates
27
- } from "./chunk-KVZL5NZS.js";
28
38
 
29
39
  // src/chat/plugins/logging.ts
30
40
  function createAgentPluginLogger(plugin) {
@@ -152,277 +162,6 @@ function createPluginState(plugin, options) {
152
162
 
153
163
  // src/chat/credentials/subject.ts
154
164
  import { createHmac, timingSafeEqual } from "crypto";
155
-
156
- // src/chat/slack/client.ts
157
- import { WebClient } from "@slack/web-api";
158
- var SlackActionError = class extends Error {
159
- code;
160
- apiError;
161
- needed;
162
- provided;
163
- statusCode;
164
- requestId;
165
- errorData;
166
- retryAfterSeconds;
167
- detail;
168
- detailLine;
169
- detailRule;
170
- constructor(message, code, options = {}) {
171
- super(message);
172
- this.name = "SlackActionError";
173
- this.code = code;
174
- this.apiError = options.apiError;
175
- this.needed = options.needed;
176
- this.provided = options.provided;
177
- this.statusCode = options.statusCode;
178
- this.requestId = options.requestId;
179
- this.errorData = options.errorData;
180
- this.retryAfterSeconds = options.retryAfterSeconds;
181
- this.detail = options.detail;
182
- this.detailLine = options.detailLine;
183
- this.detailRule = options.detailRule;
184
- }
185
- };
186
- function serializeSlackErrorData(data) {
187
- if (!data || typeof data !== "object") {
188
- return void 0;
189
- }
190
- const filtered = Object.fromEntries(
191
- Object.entries(data).filter(
192
- ([key2]) => key2 !== "error"
193
- )
194
- );
195
- if (Object.keys(filtered).length === 0) {
196
- return void 0;
197
- }
198
- try {
199
- const serialized = JSON.stringify(filtered);
200
- return serialized.length <= 600 ? serialized : `${serialized.slice(0, 597)}...`;
201
- } catch {
202
- return void 0;
203
- }
204
- }
205
- function getHeaderString(headers, name) {
206
- if (!headers || typeof headers !== "object") {
207
- return void 0;
208
- }
209
- const key2 = name.toLowerCase();
210
- const entries = headers;
211
- for (const [entryKey, value] of Object.entries(entries)) {
212
- if (entryKey.toLowerCase() !== key2) continue;
213
- if (typeof value === "string") return value;
214
- if (Array.isArray(value)) {
215
- const first = value.find((entry) => typeof entry === "string");
216
- return typeof first === "string" ? first : void 0;
217
- }
218
- }
219
- return void 0;
220
- }
221
- function parseSlackCanvasDetail(detail) {
222
- if (typeof detail !== "string") {
223
- return {};
224
- }
225
- const trimmed = detail.trim();
226
- if (!trimmed) {
227
- return {};
228
- }
229
- const parsed = {
230
- detail: trimmed
231
- };
232
- const lineMatch = trimmed.match(/line\s+(\d+):/i);
233
- if (lineMatch) {
234
- const line = Number.parseInt(lineMatch[1] ?? "", 10);
235
- if (Number.isFinite(line)) {
236
- parsed.detailLine = line;
237
- }
238
- }
239
- if (/unsupported heading depth/i.test(trimmed)) {
240
- parsed.detailRule = "unsupported_heading_depth";
241
- }
242
- return parsed;
243
- }
244
- var client = null;
245
- function normalizeSlackConversationId(channelId) {
246
- if (!channelId) return void 0;
247
- const trimmed = channelId.trim();
248
- if (!trimmed) return void 0;
249
- if (!trimmed.startsWith("slack:")) {
250
- return trimmed;
251
- }
252
- const parts = trimmed.split(":");
253
- return parts[1]?.trim() || void 0;
254
- }
255
- function getClient() {
256
- if (client) return client;
257
- const token = getSlackBotToken();
258
- if (!token) {
259
- throw new SlackActionError(
260
- "SLACK_BOT_TOKEN (or SLACK_BOT_USER_TOKEN) is required for Slack canvas/list actions in this service",
261
- "missing_token"
262
- );
263
- }
264
- client = new WebClient(token);
265
- return client;
266
- }
267
- function mapSlackError(error) {
268
- if (error instanceof SlackActionError) {
269
- return error;
270
- }
271
- const candidate = error;
272
- const apiError = candidate.data?.error;
273
- const message = candidate.message ?? "Slack action failed";
274
- const baseOptions = {
275
- apiError,
276
- statusCode: candidate.statusCode,
277
- requestId: getHeaderString(candidate.headers, "x-slack-req-id"),
278
- errorData: serializeSlackErrorData(candidate.data),
279
- ...parseSlackCanvasDetail(candidate.data?.detail)
280
- };
281
- if (apiError === "missing_scope") {
282
- return new SlackActionError(message, "missing_scope", {
283
- ...baseOptions,
284
- needed: candidate.data?.needed,
285
- provided: candidate.data?.provided
286
- });
287
- }
288
- if (apiError === "not_in_channel") {
289
- return new SlackActionError(message, "not_in_channel", baseOptions);
290
- }
291
- if (apiError === "already_reacted") {
292
- return new SlackActionError(message, "already_reacted", baseOptions);
293
- }
294
- if (apiError === "no_reaction") {
295
- return new SlackActionError(message, "no_reaction", baseOptions);
296
- }
297
- if (apiError === "invalid_arguments") {
298
- return new SlackActionError(message, "invalid_arguments", baseOptions);
299
- }
300
- if (apiError === "invalid_cursor") {
301
- return new SlackActionError(message, "invalid_arguments", baseOptions);
302
- }
303
- if (apiError === "invalid_name") {
304
- return new SlackActionError(message, "invalid_arguments", baseOptions);
305
- }
306
- if (apiError === "not_found" || apiError === "channel_not_found" || apiError === "message_not_found") {
307
- return new SlackActionError(message, "not_found", baseOptions);
308
- }
309
- if (apiError === "feature_not_enabled" || apiError === "not_allowed_token_type") {
310
- return new SlackActionError(message, "feature_unavailable", baseOptions);
311
- }
312
- if (apiError === "canvas_creation_failed") {
313
- return new SlackActionError(message, "canvas_creation_failed", baseOptions);
314
- }
315
- if (apiError === "canvas_editing_failed") {
316
- return new SlackActionError(message, "canvas_editing_failed", baseOptions);
317
- }
318
- if (candidate.code === "slack_webapi_rate_limited_error" || candidate.statusCode === 429) {
319
- return new SlackActionError(message, "rate_limited", {
320
- ...baseOptions,
321
- retryAfterSeconds: candidate.retryAfter
322
- });
323
- }
324
- return new SlackActionError(message, "internal_error", baseOptions);
325
- }
326
- function sleep(ms) {
327
- return new Promise((resolve) => setTimeout(resolve, ms));
328
- }
329
- async function withSlackRetries(task, maxAttempts = 3, context = {}) {
330
- let attempt = 0;
331
- while (attempt < maxAttempts) {
332
- attempt += 1;
333
- try {
334
- return await task();
335
- } catch (error) {
336
- const mapped = mapSlackError(error);
337
- const isRetryable = mapped.code === "rate_limited";
338
- const baseLogAttributes = {
339
- "app.slack.action": context.action ?? "unknown",
340
- "app.slack.error_code": mapped.code,
341
- ...mapped.apiError ? { "app.slack.api_error": mapped.apiError } : {},
342
- ...mapped.detail ? { "app.slack.detail": mapped.detail } : {},
343
- ...mapped.detailLine !== void 0 ? { "app.slack.detail_line": mapped.detailLine } : {},
344
- ...mapped.detailRule ? { "app.slack.detail_rule": mapped.detailRule } : {},
345
- ...mapped.requestId ? { "app.slack.request_id": mapped.requestId } : {},
346
- ...mapped.statusCode !== void 0 ? { "http.response.status_code": mapped.statusCode } : {},
347
- ...context.attributes ?? {}
348
- };
349
- if (!isRetryable || attempt >= maxAttempts) {
350
- logWarn(
351
- "slack_action_failed",
352
- {},
353
- {
354
- ...baseLogAttributes,
355
- ...mapped.errorData ? { "app.slack.error_data": mapped.errorData } : {}
356
- },
357
- "Slack action failed"
358
- );
359
- throw mapped;
360
- }
361
- logWarn(
362
- "slack_action_retrying",
363
- {},
364
- {
365
- ...baseLogAttributes,
366
- "app.slack.retry_attempt": attempt
367
- },
368
- "Retrying Slack action after transient failure"
369
- );
370
- const retryAfterMs = mapped.code === "rate_limited" && mapped.retryAfterSeconds && mapped.retryAfterSeconds > 0 ? mapped.retryAfterSeconds * 1e3 : void 0;
371
- const backoffMs = Math.min(2e3, 250 * 2 ** (attempt - 1));
372
- await sleep(retryAfterMs ?? backoffMs);
373
- }
374
- }
375
- throw new SlackActionError(
376
- "Slack action exhausted retries",
377
- "internal_error"
378
- );
379
- }
380
- function getSlackClient() {
381
- return getClient();
382
- }
383
- function isDmChannel(channelId) {
384
- const normalized = normalizeSlackConversationId(channelId);
385
- return Boolean(normalized && normalized.startsWith("D"));
386
- }
387
- function isConversationScopedChannel(channelId) {
388
- const normalized = normalizeSlackConversationId(channelId);
389
- if (!normalized) return false;
390
- return normalized.startsWith("C") || normalized.startsWith("G") || normalized.startsWith("D");
391
- }
392
- function isConversationChannel(channelId) {
393
- const normalized = normalizeSlackConversationId(channelId);
394
- if (!normalized) return false;
395
- return normalized.startsWith("C") || normalized.startsWith("G");
396
- }
397
- async function getFilePermalink(fileId) {
398
- const client2 = getClient();
399
- const response = await withSlackRetries(
400
- () => client2.files.info({
401
- file: fileId
402
- })
403
- );
404
- return response.file?.permalink;
405
- }
406
- async function downloadPrivateSlackFile(url) {
407
- const token = getSlackBotToken();
408
- if (!token) {
409
- throw new SlackActionError(
410
- "SLACK_BOT_TOKEN (or SLACK_BOT_USER_TOKEN) is required for Slack file downloads in this service",
411
- "missing_token"
412
- );
413
- }
414
- const response = await fetch(url, {
415
- headers: {
416
- Authorization: `Bearer ${token}`
417
- }
418
- });
419
- if (!response.ok) {
420
- throw new Error(`Slack file download failed: ${response.status}`);
421
- }
422
- return Buffer.from(await response.arrayBuffer());
423
- }
424
-
425
- // src/chat/credentials/subject.ts
426
165
  var CREDENTIAL_SUBJECT_HMAC_CONTEXT = "junior.credential_subject.v1";
427
166
  var CREDENTIAL_SUBJECT_SIGNATURE_VERSION = "v1";
428
167
  function getCredentialSubjectSecret() {
@@ -514,6 +253,15 @@ function verifySlackDirectCredentialSubject(input) {
514
253
  return timingSafeMatch(expected, binding.signature);
515
254
  }
516
255
 
256
+ // src/chat/tools/channel-capabilities.ts
257
+ function resolveChannelCapabilities(channelId) {
258
+ return {
259
+ canCreateCanvas: isConversationScopedChannel(channelId),
260
+ canPostToChannel: isConversationChannel(channelId),
261
+ canAddReactions: isConversationScopedChannel(channelId)
262
+ };
263
+ }
264
+
517
265
  // src/chat/plugins/agent-hooks.ts
518
266
  var AgentPluginHookDeniedError = class extends Error {
519
267
  constructor(message) {
@@ -540,6 +288,9 @@ var AGENT_PLUGIN_ROUTE_METHODS = /* @__PURE__ */ new Set([
540
288
  "OPTIONS",
541
289
  "ALL"
542
290
  ]);
291
+ function isRecord2(value) {
292
+ return Boolean(value && typeof value === "object" && !Array.isArray(value));
293
+ }
543
294
  function validateLegacyStatePrefixes(plugin) {
544
295
  const prefixes = plugin.legacyStatePrefixes;
545
296
  if (prefixes === void 0) {
@@ -547,7 +298,7 @@ function validateLegacyStatePrefixes(plugin) {
547
298
  }
548
299
  if (!Array.isArray(prefixes)) {
549
300
  throw new Error(
550
- `Trusted plugin "${plugin.name}" legacyStatePrefixes must be an array`
301
+ `Plugin "${plugin.name}" legacyStatePrefixes must be an array`
551
302
  );
552
303
  }
553
304
  const allowedPrefix = `junior:${plugin.name}`;
@@ -555,12 +306,12 @@ function validateLegacyStatePrefixes(plugin) {
555
306
  const prefix = typeof rawPrefix === "string" ? rawPrefix.trim() : "";
556
307
  if (!prefix) {
557
308
  throw new Error(
558
- `Trusted plugin "${plugin.name}" legacy state prefixes must be non-empty strings`
309
+ `Plugin "${plugin.name}" legacy state prefixes must be non-empty strings`
559
310
  );
560
311
  }
561
312
  if (prefix !== allowedPrefix && !prefix.startsWith(`${allowedPrefix}:`)) {
562
313
  throw new Error(
563
- `Trusted plugin "${plugin.name}" legacy state prefix "${prefix}" must stay under "${allowedPrefix}"`
314
+ `Plugin "${plugin.name}" legacy state prefix "${prefix}" must stay under "${allowedPrefix}"`
564
315
  );
565
316
  }
566
317
  }
@@ -570,11 +321,11 @@ function validateAgentPlugins(plugins) {
570
321
  for (const plugin of plugins) {
571
322
  if (!AGENT_PLUGIN_NAME_RE.test(plugin.name)) {
572
323
  throw new Error(
573
- `Trusted plugin name "${plugin.name}" must be a lowercase plugin identifier`
324
+ `Plugin name "${plugin.name}" must be a lowercase plugin identifier`
574
325
  );
575
326
  }
576
327
  if (seen.has(plugin.name)) {
577
- throw new Error(`Duplicate trusted plugin name "${plugin.name}"`);
328
+ throw new Error(`Duplicate plugin name "${plugin.name}"`);
578
329
  }
579
330
  seen.add(plugin.name);
580
331
  validateLegacyStatePrefixes(plugin);
@@ -599,18 +350,22 @@ function getAgentPluginTools(context) {
599
350
  continue;
600
351
  }
601
352
  const log = createAgentPluginLogger(plugin.name);
353
+ const destination = context.destination;
602
354
  const credentialSubject = createSlackDirectCredentialSubject({
603
355
  channelId: context.channelId,
604
356
  teamId: context.teamId,
605
357
  userId: context.requester?.userId
606
358
  });
359
+ const pluginCapabilities = resolveChannelCapabilities(context.channelId);
607
360
  const pluginTools = hook({
608
361
  plugin: { name: plugin.name },
609
362
  log,
610
363
  requester: context.requester,
611
- channelCapabilities: context.channelCapabilities,
364
+ channelCapabilities: pluginCapabilities,
612
365
  channelId: context.channelId,
366
+ conversationId: context.conversationId,
613
367
  ...credentialSubject ? { credentialSubject } : {},
368
+ ...destination ? { destination } : {},
614
369
  teamId: context.teamId,
615
370
  messageTs: context.messageTs,
616
371
  threadTs: context.threadTs,
@@ -622,12 +377,12 @@ function getAgentPluginTools(context) {
622
377
  for (const [name, tool] of Object.entries(pluginTools)) {
623
378
  if (!AGENT_PLUGIN_TOOL_NAME_RE.test(name)) {
624
379
  throw new Error(
625
- `Trusted plugin tool "${name}" from plugin "${plugin.name}" must be a camelCase identifier`
380
+ `Plugin tool "${name}" from plugin "${plugin.name}" must be a camelCase identifier`
626
381
  );
627
382
  }
628
383
  if (tools[name]) {
629
384
  throw new Error(
630
- `Duplicate trusted plugin tool "${name}" from plugin "${plugin.name}"`
385
+ `Duplicate plugin tool "${name}" from plugin "${plugin.name}"`
631
386
  );
632
387
  }
633
388
  tools[name] = tool;
@@ -639,19 +394,19 @@ function routeMethods(route, pluginName) {
639
394
  const methods = Array.isArray(route.method) ? route.method : [route.method ?? "ALL"];
640
395
  if (methods.length === 0) {
641
396
  throw new Error(
642
- `Trusted plugin route "${route.path}" from plugin "${pluginName}" must declare at least one method`
397
+ `Plugin route "${route.path}" from plugin "${pluginName}" must declare at least one method`
643
398
  );
644
399
  }
645
400
  for (const method of methods) {
646
401
  if (!AGENT_PLUGIN_ROUTE_METHODS.has(method)) {
647
402
  throw new Error(
648
- `Trusted plugin route "${route.path}" from plugin "${pluginName}" has invalid method "${String(method)}"`
403
+ `Plugin route "${route.path}" from plugin "${pluginName}" has invalid method "${String(method)}"`
649
404
  );
650
405
  }
651
406
  }
652
407
  if (methods.includes("ALL") && methods.length > 1) {
653
408
  throw new Error(
654
- `Trusted plugin route "${route.path}" from plugin "${pluginName}" must not combine ALL with explicit methods`
409
+ `Plugin route "${route.path}" from plugin "${pluginName}" must not combine ALL with explicit methods`
655
410
  );
656
411
  }
657
412
  return methods;
@@ -672,38 +427,36 @@ function getAgentPluginRoutes() {
672
427
  });
673
428
  if (!Array.isArray(pluginRoutes)) {
674
429
  throw new Error(
675
- `Trusted plugin routes hook from plugin "${plugin.name}" must return an array`
430
+ `Plugin routes hook from plugin "${plugin.name}" must return an array`
676
431
  );
677
432
  }
678
433
  for (const route of pluginRoutes) {
679
434
  if (!isRecord2(route)) {
680
435
  throw new Error(
681
- `Trusted plugin route from plugin "${plugin.name}" must be an object`
436
+ `Plugin route from plugin "${plugin.name}" must be an object`
682
437
  );
683
438
  }
684
439
  if (typeof route.path !== "string" || !route.path.startsWith("/")) {
685
440
  throw new Error(
686
- `Trusted plugin route "${route.path}" from plugin "${plugin.name}" must start with /`
441
+ `Plugin route "${route.path}" from plugin "${plugin.name}" must start with /`
687
442
  );
688
443
  }
689
444
  if (typeof route.handler !== "function") {
690
445
  throw new Error(
691
- `Trusted plugin route "${route.path}" from plugin "${plugin.name}" must provide a handler`
446
+ `Plugin route "${route.path}" from plugin "${plugin.name}" must provide a handler`
692
447
  );
693
448
  }
694
449
  const methods = routeMethods(route, plugin.name);
695
450
  const pathMethods = methodsByPath.get(route.path) ?? /* @__PURE__ */ new Set();
696
451
  if (pathMethods.has("ALL") || methods.includes("ALL") && pathMethods.size > 0) {
697
452
  throw new Error(
698
- `Trusted plugin route "${route.path}" conflicts with an ALL route for the same path`
453
+ `Plugin route "${route.path}" conflicts with an ALL route for the same path`
699
454
  );
700
455
  }
701
456
  for (const method of methods) {
702
457
  const key2 = `${method}:${route.path}`;
703
458
  if (seen.has(key2)) {
704
- throw new Error(
705
- `Duplicate trusted plugin route "${method} ${route.path}"`
706
- );
459
+ throw new Error(`Duplicate plugin route "${method} ${route.path}"`);
707
460
  }
708
461
  seen.add(key2);
709
462
  pathMethods.add(method);
@@ -727,13 +480,13 @@ function trustedSlackConversationUrl(pluginName, link) {
727
480
  parsed = new URL(url);
728
481
  } catch (error) {
729
482
  throw new Error(
730
- `Trusted plugin "${pluginName}" slackConversationLink must return an absolute http(s) URL`,
483
+ `Plugin "${pluginName}" slackConversationLink must return an absolute http(s) URL`,
731
484
  { cause: error }
732
485
  );
733
486
  }
734
487
  if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
735
488
  throw new Error(
736
- `Trusted plugin "${pluginName}" slackConversationLink must return an absolute http(s) URL`
489
+ `Plugin "${pluginName}" slackConversationLink must return an absolute http(s) URL`
737
490
  );
738
491
  }
739
492
  return parsed.toString();
@@ -923,7 +676,7 @@ async function getAgentPluginOperationalReports(nowMs = Date.now()) {
923
676
  })
924
677
  );
925
678
  } catch (error) {
926
- log.error("Trusted plugin operational report failed", {
679
+ log.error("Plugin operational report failed", {
927
680
  error: error instanceof Error ? error.message : String(error)
928
681
  });
929
682
  reports.push(failedOperationalReport({ nowMs, pluginName: plugin.name }));
@@ -931,9 +684,6 @@ async function getAgentPluginOperationalReports(nowMs = Date.now()) {
931
684
  }
932
685
  return reports;
933
686
  }
934
- function isRecord2(value) {
935
- return Boolean(value && typeof value === "object" && !Array.isArray(value));
936
- }
937
687
  function normalizeEnv(value) {
938
688
  if (!isRecord2(value)) {
939
689
  return {};
@@ -1072,9 +822,6 @@ function GET() {
1072
822
  import fs from "fs";
1073
823
  import path from "path";
1074
824
 
1075
- // src/chat/turn-context-tag.ts
1076
- var TURN_CONTEXT_TAG = "runtime-turn-context";
1077
-
1078
825
  // src/chat/interruption-marker.ts
1079
826
  var INTERRUPTED_MARKER = "\n\n[Response interrupted before completion]";
1080
827
  function getInterruptionMarker() {
@@ -2202,20 +1949,20 @@ function commitEntries(existingMessages, nextMessages, sessionId, entries) {
2202
1949
  return [resetEntry(nextMessages, nextSessionId(entries))];
2203
1950
  }
2204
1951
  function redisStore(redisStateAdapter) {
2205
- const client2 = redisStateAdapter.getClient();
1952
+ const client = redisStateAdapter.getClient();
2206
1953
  return {
2207
1954
  async append({ entries, scope, ttlMs }) {
2208
1955
  const listKey = key(scope);
2209
1956
  if (entries.length > 0) {
2210
- await client2.rPush(
1957
+ await client.rPush(
2211
1958
  listKey,
2212
1959
  entries.map((entry) => JSON.stringify(entry))
2213
1960
  );
2214
1961
  }
2215
- await client2.pExpire(listKey, Math.max(1, ttlMs));
1962
+ await client.pExpire(listKey, Math.max(1, ttlMs));
2216
1963
  },
2217
1964
  async read(scope) {
2218
- const values = await client2.lRange(key(scope), 0, -1);
1965
+ const values = await client.lRange(key(scope), 0, -1);
2219
1966
  return values.map(decode);
2220
1967
  }
2221
1968
  };
@@ -2428,7 +2175,6 @@ function parseAgentTurnSessionFields(parsed) {
2428
2175
  return void 0;
2429
2176
  }
2430
2177
  const channelName = typeof parsed.channelName === "string" && parsed.channelName.trim() ? parsed.channelName.trim() : void 0;
2431
- const conversationTitle = typeof parsed.conversationTitle === "string" && parsed.conversationTitle.trim() ? parsed.conversationTitle.trim() : void 0;
2432
2178
  const conversationId = parsed.conversationId;
2433
2179
  const sessionId = parsed.sessionId;
2434
2180
  const sliceId = toFiniteNonNegativeNumber(parsed.sliceId);
@@ -2441,13 +2187,13 @@ function parseAgentTurnSessionFields(parsed) {
2441
2187
  const requester = parseAgentTurnRequester(parsed.requester);
2442
2188
  const startedAtMs = toFiniteNonNegativeNumber(parsed.startedAtMs);
2443
2189
  const surface = parseAgentTurnSurface(parsed.surface);
2444
- if (typeof conversationId !== "string" || typeof sessionId !== "string" || sliceId === void 0 || version === void 0 || updatedAtMs === void 0) {
2190
+ const destination = parsed.destination === void 0 ? void 0 : parseDestination(parsed.destination);
2191
+ if (typeof conversationId !== "string" || typeof sessionId !== "string" || sliceId === void 0 || version === void 0 || updatedAtMs === void 0 || parsed.destination !== void 0 && !destination) {
2445
2192
  return void 0;
2446
2193
  }
2447
2194
  return {
2448
2195
  version,
2449
2196
  ...channelName ? { channelName } : {},
2450
- ...conversationTitle ? { conversationTitle } : {},
2451
2197
  conversationId,
2452
2198
  sessionId,
2453
2199
  sliceId,
@@ -2458,6 +2204,7 @@ function parseAgentTurnSessionFields(parsed) {
2458
2204
  cumulativeDurationMs,
2459
2205
  ...logSessionId ? { logSessionId } : {},
2460
2206
  ...cumulativeUsage ? { cumulativeUsage } : {},
2207
+ ...destination ? { destination } : {},
2461
2208
  ...requester ? { requester } : {},
2462
2209
  ...Array.isArray(parsed.loadedSkillNames) ? {
2463
2210
  loadedSkillNames: parsed.loadedSkillNames.filter(
@@ -2537,7 +2284,6 @@ function materializeAgentTurnSessionRecord(stored, piMessages) {
2537
2284
  return {
2538
2285
  version: stored.version,
2539
2286
  ...stored.channelName ? { channelName: stored.channelName } : {},
2540
- ...stored.conversationTitle ? { conversationTitle: stored.conversationTitle } : {},
2541
2287
  conversationId: stored.conversationId,
2542
2288
  sessionId: stored.sessionId,
2543
2289
  sliceId: stored.sliceId,
@@ -2547,6 +2293,7 @@ function materializeAgentTurnSessionRecord(stored, piMessages) {
2547
2293
  updatedAtMs: stored.updatedAtMs,
2548
2294
  piMessages,
2549
2295
  cumulativeDurationMs: stored.cumulativeDurationMs,
2296
+ ...stored.destination ? { destination: stored.destination } : {},
2550
2297
  ...stored.cumulativeUsage ? { cumulativeUsage: stored.cumulativeUsage } : {},
2551
2298
  ...stored.resumeReason ? { resumeReason: stored.resumeReason } : {},
2552
2299
  ...stored.errorMessage ? { errorMessage: stored.errorMessage } : {},
@@ -2598,7 +2345,6 @@ function buildStoredRecord(args) {
2598
2345
  return {
2599
2346
  version: (args.previousVersion ?? 0) + 1,
2600
2347
  ...args.channelName ? { channelName: args.channelName } : {},
2601
- ...args.conversationTitle ? { conversationTitle: args.conversationTitle } : {},
2602
2348
  conversationId: args.conversationId,
2603
2349
  sessionId: args.sessionId,
2604
2350
  sliceId: args.sliceId,
@@ -2610,6 +2356,7 @@ function buildStoredRecord(args) {
2610
2356
  ...args.logSessionId ? { logSessionId: args.logSessionId } : {},
2611
2357
  cumulativeDurationMs: args.cumulativeDurationMs,
2612
2358
  ...args.cumulativeUsage ? { cumulativeUsage: args.cumulativeUsage } : {},
2359
+ ...args.destination ? { destination: args.destination } : {},
2613
2360
  ...args.requester ? { requester: args.requester } : {},
2614
2361
  ...Array.isArray(args.loadedSkillNames) ? {
2615
2362
  loadedSkillNames: args.loadedSkillNames.filter(
@@ -2658,13 +2405,13 @@ async function updateAgentTurnSessionState(args) {
2658
2405
  state: args.state,
2659
2406
  committedMessageCount: parsed.committedMessageCount,
2660
2407
  ...parsed.channelName ? { channelName: parsed.channelName } : {},
2661
- ...parsed.conversationTitle ? { conversationTitle: parsed.conversationTitle } : {},
2662
2408
  startedAtMs: parsed.startedAtMs,
2663
2409
  lastProgressAtMs: parsed.lastProgressAtMs,
2664
2410
  previousVersion: parsed.version,
2665
2411
  ...parsed.logSessionId ? { logSessionId: parsed.logSessionId } : {},
2666
2412
  cumulativeDurationMs: args.existing.cumulativeDurationMs,
2667
2413
  ...args.existing.cumulativeUsage ? { cumulativeUsage: args.existing.cumulativeUsage } : {},
2414
+ ...args.existing.destination ? { destination: args.existing.destination } : {},
2668
2415
  ...args.existing.loadedSkillNames ? { loadedSkillNames: args.existing.loadedSkillNames } : {},
2669
2416
  ...args.existing.requester ? { requester: args.existing.requester } : {},
2670
2417
  ...args.existing.resumeReason ? { resumeReason: args.existing.resumeReason } : {},
@@ -2691,9 +2438,6 @@ async function upsertAgentTurnSessionRecord(args) {
2691
2438
  ttlMs,
2692
2439
  record: buildStoredRecord({
2693
2440
  ...args.channelName ?? existingRecord?.channelName ? { channelName: args.channelName ?? existingRecord?.channelName } : {},
2694
- ...args.conversationTitle ?? existingRecord?.conversationTitle ? {
2695
- conversationTitle: args.conversationTitle ?? existingRecord?.conversationTitle
2696
- } : {},
2697
2441
  conversationId: args.conversationId,
2698
2442
  sessionId: args.sessionId,
2699
2443
  sliceId: args.sliceId,
@@ -2705,6 +2449,7 @@ async function upsertAgentTurnSessionRecord(args) {
2705
2449
  previousVersion: existingRecord?.version,
2706
2450
  cumulativeDurationMs: toFiniteNonNegativeNumber(args.cumulativeDurationMs) ?? existingRecord?.cumulativeDurationMs ?? 0,
2707
2451
  ...args.cumulativeUsage ? { cumulativeUsage: args.cumulativeUsage } : {},
2452
+ ...args.destination ?? existingRecord?.destination ? { destination: args.destination ?? existingRecord?.destination } : {},
2708
2453
  ...args.loadedSkillNames ? { loadedSkillNames: args.loadedSkillNames } : {},
2709
2454
  ...args.requester ?? existingRecord?.requester ? { requester: args.requester ?? existingRecord?.requester } : {},
2710
2455
  ...args.resumeReason ? { resumeReason: args.resumeReason } : {},
@@ -2726,9 +2471,6 @@ async function recordAgentTurnSessionSummary(args) {
2726
2471
  {
2727
2472
  version: existing?.version ?? 0,
2728
2473
  ...args.channelName ?? existing?.channelName ? { channelName: args.channelName ?? existing?.channelName } : {},
2729
- ...args.conversationTitle ?? existing?.conversationTitle ? {
2730
- conversationTitle: args.conversationTitle ?? existing?.conversationTitle
2731
- } : {},
2732
2474
  conversationId: args.conversationId,
2733
2475
  sessionId: args.sessionId,
2734
2476
  sliceId: args.sliceId,
@@ -2738,6 +2480,7 @@ async function recordAgentTurnSessionSummary(args) {
2738
2480
  updatedAtMs: nowMs,
2739
2481
  cumulativeDurationMs: toFiniteNonNegativeNumber(args.cumulativeDurationMs) ?? existing?.cumulativeDurationMs ?? 0,
2740
2482
  ...args.cumulativeUsage ?? existing?.cumulativeUsage ? { cumulativeUsage: args.cumulativeUsage ?? existing?.cumulativeUsage } : {},
2483
+ ...args.destination ?? existing?.destination ? { destination: args.destination ?? existing?.destination } : {},
2741
2484
  ...args.requester ?? existing?.requester ? { requester: args.requester ?? existing?.requester } : {},
2742
2485
  ...Array.isArray(args.loadedSkillNames) ? {
2743
2486
  loadedSkillNames: args.loadedSkillNames.filter(
@@ -2828,8 +2571,8 @@ function buildSentryWebBaseUrl(dsn) {
2828
2571
  return `${dsn.protocol}://${dsn.host}${port}${path2}`;
2829
2572
  }
2830
2573
  function buildSentryConversationUrl(conversationId) {
2831
- const client2 = sentry_exports.getClient();
2832
- const dsn = client2?.getDsn();
2574
+ const client = sentry_exports.getClient();
2575
+ const dsn = client?.getDsn();
2833
2576
  if (!dsn?.host || !dsn.projectId) {
2834
2577
  return void 0;
2835
2578
  }
@@ -2847,8 +2590,8 @@ function buildSentryConversationUrl(conversationId) {
2847
2590
  return `${buildSentryWebBaseUrl(dsn)}/organizations/${orgSlug}/${path2}`;
2848
2591
  }
2849
2592
  function buildSentryTraceUrl(traceId) {
2850
- const client2 = sentry_exports.getClient();
2851
- const dsn = client2?.getDsn();
2593
+ const client = sentry_exports.getClient();
2594
+ const dsn = client?.getDsn();
2852
2595
  if (!dsn?.host || !dsn.projectId) {
2853
2596
  return void 0;
2854
2597
  }
@@ -2934,21 +2677,129 @@ function formatSlackConversationRedactedLabel(context) {
2934
2677
  return formatSlackConversationTypeLabel(context.type);
2935
2678
  }
2936
2679
 
2680
+ // src/chat/state/conversation-details.ts
2681
+ import { THREAD_STATE_TTL_MS as THREAD_STATE_TTL_MS2 } from "chat";
2682
+ var CONVERSATION_PREFIX = "junior:conversation";
2683
+ var CONVERSATION_DETAILS_TTL_MS = THREAD_STATE_TTL_MS2;
2684
+ function conversationContextKey(conversationId) {
2685
+ return `${CONVERSATION_PREFIX}:${conversationId}:context`;
2686
+ }
2687
+ function conversationTitleKey(conversationId) {
2688
+ return `${CONVERSATION_PREFIX}:${conversationId}:title`;
2689
+ }
2690
+ function parseAgentTurnRequester2(value) {
2691
+ if (!isRecord(value)) return void 0;
2692
+ const requester = {
2693
+ ...typeof value.email === "string" ? { email: value.email } : {},
2694
+ ...typeof value.fullName === "string" ? { fullName: value.fullName } : {},
2695
+ ...typeof value.slackUserId === "string" ? { slackUserId: value.slackUserId } : {},
2696
+ ...typeof value.slackUserName === "string" ? { slackUserName: value.slackUserName } : {}
2697
+ };
2698
+ return Object.keys(requester).length > 0 ? requester : void 0;
2699
+ }
2700
+ function parseOriginSurface(value) {
2701
+ if (value === "slack" || value === "api" || value === "scheduler" || value === "internal") {
2702
+ return value;
2703
+ }
2704
+ return void 0;
2705
+ }
2706
+ function storedContextFromInput(context) {
2707
+ return {
2708
+ ...context.channelName ? { channelName: context.channelName } : {},
2709
+ ...context.originSurface ? { originSurface: context.originSurface } : {},
2710
+ ...context.originRequester ? { originRequester: context.originRequester } : {},
2711
+ startedAtMs: context.startedAtMs
2712
+ };
2713
+ }
2714
+ function parseContext(value) {
2715
+ if (!isRecord(value)) return void 0;
2716
+ const startedAtMs = toOptionalNumber(value.startedAtMs);
2717
+ if (startedAtMs === void 0) return void 0;
2718
+ return {
2719
+ ...typeof value.channelName === "string" && value.channelName.trim() ? { channelName: value.channelName.trim() } : {},
2720
+ ...parseOriginSurface(value.originSurface) ? { originSurface: parseOriginSurface(value.originSurface) } : {},
2721
+ ...parseAgentTurnRequester2(value.originRequester) ? { originRequester: parseAgentTurnRequester2(value.originRequester) } : {},
2722
+ startedAtMs
2723
+ };
2724
+ }
2725
+ function parseTitle(value) {
2726
+ if (!isRecord(value)) return void 0;
2727
+ const displayTitle = typeof value.displayTitle === "string" && value.displayTitle.trim() ? value.displayTitle.trim() : void 0;
2728
+ if (!displayTitle) return void 0;
2729
+ return {
2730
+ displayTitle,
2731
+ ...typeof value.titleSourceMessageId === "string" ? { titleSourceMessageId: value.titleSourceMessageId } : {}
2732
+ };
2733
+ }
2734
+ async function initConversationContext(conversationId, context) {
2735
+ const stateAdapter = getStateAdapter();
2736
+ await stateAdapter.connect();
2737
+ const key2 = conversationContextKey(conversationId);
2738
+ const inserted = await stateAdapter.setIfNotExists(
2739
+ key2,
2740
+ storedContextFromInput(context),
2741
+ CONVERSATION_DETAILS_TTL_MS
2742
+ );
2743
+ if (inserted) return;
2744
+ const existing = parseContext(await stateAdapter.get(key2));
2745
+ if (!existing) {
2746
+ return;
2747
+ }
2748
+ await stateAdapter.set(key2, existing, CONVERSATION_DETAILS_TTL_MS);
2749
+ }
2750
+ async function setConversationTitle(conversationId, title) {
2751
+ const stateAdapter = getStateAdapter();
2752
+ await stateAdapter.connect();
2753
+ await stateAdapter.set(
2754
+ conversationTitleKey(conversationId),
2755
+ {
2756
+ displayTitle: title.displayTitle,
2757
+ ...title.titleSourceMessageId ? { titleSourceMessageId: title.titleSourceMessageId } : {}
2758
+ },
2759
+ CONVERSATION_DETAILS_TTL_MS
2760
+ );
2761
+ }
2762
+ async function getConversationDetails(conversationId) {
2763
+ const stateAdapter = getStateAdapter();
2764
+ await stateAdapter.connect();
2765
+ const [rawContext, rawTitle] = await Promise.all([
2766
+ stateAdapter.get(conversationContextKey(conversationId)),
2767
+ stateAdapter.get(conversationTitleKey(conversationId))
2768
+ ]);
2769
+ const context = parseContext(rawContext);
2770
+ const title = parseTitle(rawTitle);
2771
+ if (!context && !title) return void 0;
2772
+ return {
2773
+ conversationId,
2774
+ ...title?.displayTitle ? { displayTitle: title.displayTitle } : {},
2775
+ ...title?.titleSourceMessageId ? { titleSourceMessageId: title.titleSourceMessageId } : {},
2776
+ ...context?.channelName ? { channelName: context.channelName } : {},
2777
+ ...context?.originSurface ? { originSurface: context.originSurface } : {},
2778
+ ...context?.originRequester ? { originRequester: context.originRequester } : {},
2779
+ ...context?.startedAtMs !== void 0 ? { startedAtMs: context.startedAtMs } : {}
2780
+ };
2781
+ }
2782
+ async function getConversationDetailsForIds(conversationIds) {
2783
+ const uniqueIds = [...new Set(conversationIds)].filter(Boolean);
2784
+ const entries = await Promise.all(
2785
+ uniqueIds.map(async (id) => {
2786
+ const details = await getConversationDetails(id);
2787
+ return details ? [id, details] : void 0;
2788
+ })
2789
+ );
2790
+ const result = /* @__PURE__ */ new Map();
2791
+ for (const entry of entries) {
2792
+ if (entry) result.set(entry[0], entry[1]);
2793
+ }
2794
+ return result;
2795
+ }
2796
+
2937
2797
  export {
2938
2798
  createAgentPluginLogger,
2939
2799
  createPluginState,
2940
- SlackActionError,
2941
- getHeaderString,
2942
- normalizeSlackConversationId,
2943
- withSlackRetries,
2944
- getSlackClient,
2945
- isDmChannel,
2946
- isConversationScopedChannel,
2947
- isConversationChannel,
2948
- getFilePermalink,
2949
- downloadPrivateSlackFile,
2950
2800
  bindSlackDirectCredentialSubject,
2951
2801
  verifySlackDirectCredentialSubject,
2802
+ resolveChannelCapabilities,
2952
2803
  validateAgentPlugins,
2953
2804
  setAgentPlugins,
2954
2805
  getAgentPlugins,
@@ -2958,7 +2809,6 @@ export {
2958
2809
  getAgentPluginOperationalReports,
2959
2810
  createAgentPluginHookRunner,
2960
2811
  GET,
2961
- TURN_CONTEXT_TAG,
2962
2812
  getInterruptionMarker,
2963
2813
  truncateStatusText,
2964
2814
  normalizeSlackStatusText,
@@ -2986,5 +2836,9 @@ export {
2986
2836
  resolveSlackChannelTypeFromMessage,
2987
2837
  resolveSlackConversationContext,
2988
2838
  resolveSlackConversationContextFromThreadId,
2989
- formatSlackConversationRedactedLabel
2839
+ formatSlackConversationRedactedLabel,
2840
+ initConversationContext,
2841
+ setConversationTitle,
2842
+ getConversationDetails,
2843
+ getConversationDetailsForIds
2990
2844
  };