@sentry/junior 0.117.0 → 0.119.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 (73) hide show
  1. package/dist/{agent-hooks-5IK4LPLP.js → agent-hooks-NZSV4QQR.js} +9 -11
  2. package/dist/api/conversations/detail.d.ts +17 -0
  3. package/dist/api/conversations/event-list.d.ts +17 -0
  4. package/dist/api/schema/conversation.d.ts +68 -0
  5. package/dist/api/schema/stats.d.ts +22 -0
  6. package/dist/api/schema.d.ts +2 -0
  7. package/dist/api/schema.js +7 -3
  8. package/dist/api/stats.d.ts +16 -0
  9. package/dist/api/user-pages/routes.d.ts +4 -0
  10. package/dist/api.js +223 -20
  11. package/dist/app.js +43 -38
  12. package/dist/{catalog-runtime-MTUYW3AA.js → catalog-runtime-TAOYIZIZ.js} +5 -5
  13. package/dist/chat/agent/prompt.d.ts +4 -2
  14. package/dist/chat/plugins/user-pages.d.ts +15 -0
  15. package/dist/chat/plugins/viewer-actors.d.ts +6 -0
  16. package/dist/chat/prompt.d.ts +1 -2
  17. package/dist/chat/sandbox/paths.d.ts +2 -0
  18. package/dist/chat/sandbox/sandbox.d.ts +3 -1
  19. package/dist/chat/sandbox/workspace.d.ts +1 -0
  20. package/dist/chat/skills.d.ts +1 -2
  21. package/dist/chat/slack/tools/canvas/api.d.ts +2 -9
  22. package/dist/chat/slack/tools/canvas/markdown.d.ts +9 -0
  23. package/dist/chat/tools/index.d.ts +4 -1
  24. package/dist/chat/tools/sandbox/file-utils.d.ts +11 -0
  25. package/dist/chat/tools/sandbox/find-files.d.ts +2 -1
  26. package/dist/chat/tools/sandbox/grep.d.ts +2 -1
  27. package/dist/{chunk-6G45D3YG.js → chunk-4UIUDOOV.js} +16 -233
  28. package/dist/{chunk-LUMEBOWC.js → chunk-6ZR455DC.js} +2 -2
  29. package/dist/{chunk-STUI6NIL.js → chunk-BLQZACHV.js} +511 -14
  30. package/dist/{chunk-27KWTFP6.js → chunk-BNJEPYTC.js} +5 -14
  31. package/dist/{chunk-ZRQUQJUV.js → chunk-D3N4BPSL.js} +3 -3
  32. package/dist/{chunk-WKU7TY22.js → chunk-FIANEE7Z.js} +3 -1
  33. package/dist/{chunk-RXRQJF7Y.js → chunk-GCGJGSQL.js} +21 -0
  34. package/dist/{chunk-FAWBONPC.js → chunk-GKFDU3ZI.js} +5 -7
  35. package/dist/{chunk-FXOYURO4.js → chunk-HDFGNU5E.js} +1 -0
  36. package/dist/{chunk-3GE3F4LT.js → chunk-JLJLZS2N.js} +1 -1
  37. package/dist/{chunk-CU7HTGNI.js → chunk-JSTGI7TX.js} +1 -1
  38. package/dist/chunk-JXRAOOXQ.js +37 -0
  39. package/dist/{chunk-WUNPRM76.js → chunk-NUENIPQL.js} +1 -1
  40. package/dist/{chunk-BFIRIZPH.js → chunk-O2ULQJWR.js} +101 -2
  41. package/dist/{chunk-AKG4TZNH.js → chunk-QZ7XRJDB.js} +30 -9
  42. package/dist/{chunk-5O3JDQDG.js → chunk-R53UT2OI.js} +1 -1
  43. package/dist/{chunk-W5JEQQEA.js → chunk-TAGV4Z3J.js} +1 -1
  44. package/dist/{chunk-NHZEUHKH.js → chunk-TGIQN7QW.js} +388 -113
  45. package/dist/{chunk-KU6ALEJC.js → chunk-VJTWKJWQ.js} +30 -2
  46. package/dist/{chunk-DS27ZBSP.js → chunk-VLTWQQKH.js} +2 -2
  47. package/dist/{chunk-WEK3V5RI.js → chunk-YLYJIQRX.js} +226 -21
  48. package/dist/{chunk-LN765Y4N.js → chunk-ZFX4M743.js} +15 -9
  49. package/dist/{chunk-72Z3HNTQ.js → chunk-ZJ6IVZVO.js} +1 -1
  50. package/dist/cli/chat.js +24 -25
  51. package/dist/cli/check.js +6 -6
  52. package/dist/cli/init.js +2 -2
  53. package/dist/cli/plugins.js +13 -15
  54. package/dist/cli/snapshot-create.js +9 -10
  55. package/dist/cli/upgrade.js +8 -8
  56. package/dist/db/schema/stats.d.ts +93 -0
  57. package/dist/db/schema.d.ts +94 -1
  58. package/dist/{db-J2ITKS63.js → db-FWGP5CKY.js} +7 -7
  59. package/dist/instrumentation.js +3 -3
  60. package/dist/nitro.js +3 -3
  61. package/dist/{oauth-callback-server-NQYKBT24.js → oauth-callback-server-PM3QNXJQ.js} +16 -18
  62. package/dist/{plugins-IUJA4VBU.js → plugins-HW4VWW6G.js} +1 -1
  63. package/dist/{runner-53V5GIQM.js → runner-DV55N6ZB.js} +11 -13
  64. package/dist/{skills-BU3ZMLVB.js → skills-MANV5ZK6.js} +6 -6
  65. package/dist/stats.d.ts +18 -0
  66. package/dist/{validation-74EIKKE6.js → validation-QGB4INTI.js} +6 -6
  67. package/dist/version.js +1 -1
  68. package/migrations/0011_sour_golden_guardian.sql +8 -0
  69. package/migrations/meta/0011_snapshot.json +1195 -0
  70. package/migrations/meta/_journal.json +8 -1
  71. package/package.json +5 -5
  72. package/dist/chunk-4ZNGQH7C.js +0 -25
  73. package/dist/chunk-YRWJ3KEU.js +0 -517
@@ -1,23 +1,21 @@
1
1
  import {
2
- SANDBOX_WORKSPACE_ROOT,
3
- getStateAdapter
4
- } from "./chunk-KU6ALEJC.js";
2
+ getDb
3
+ } from "./chunk-D3N4BPSL.js";
5
4
  import {
6
- createPluginAnnotations,
7
- isConversationChannel,
8
- isConversationScopedChannel,
9
- isDmChannel,
10
- normalizeSlackConversationId
11
- } from "./chunk-YRWJ3KEU.js";
5
+ juniorConversationAnnotations
6
+ } from "./chunk-GCGJGSQL.js";
12
7
  import {
13
- getDb
14
- } from "./chunk-ZRQUQJUV.js";
8
+ SANDBOX_WORKSPACE_ROOT,
9
+ getStateAdapter,
10
+ sleep
11
+ } from "./chunk-VJTWKJWQ.js";
15
12
  import {
16
13
  botConfig,
17
14
  completeObject,
18
15
  embedTexts,
16
+ getSlackBotToken,
19
17
  standardModelId
20
- } from "./chunk-DS27ZBSP.js";
18
+ } from "./chunk-VLTWQQKH.js";
21
19
  import {
22
20
  isActorUserId,
23
21
  parseActorUserId
@@ -25,8 +23,11 @@ import {
25
23
  import {
26
24
  logException,
27
25
  logInfo,
28
- logWarn
29
- } from "./chunk-72Z3HNTQ.js";
26
+ logWarn,
27
+ setSpanAttributes,
28
+ setSpanStatus,
29
+ withSpan
30
+ } from "./chunk-ZJ6IVZVO.js";
30
31
  import {
31
32
  parseSlackChannelReferenceId,
32
33
  parseSlackMessageTs,
@@ -40,6 +41,67 @@ import {
40
41
  resourceEventSchema
41
42
  } from "@sentry/junior-plugin-api";
42
43
 
44
+ // src/chat/plugins/annotations.ts
45
+ import { and, desc, eq } from "drizzle-orm";
46
+ import {
47
+ conversationAnnotationInputSchema
48
+ } from "@sentry/junior-plugin-api";
49
+ function createPluginAnnotations(args) {
50
+ return {
51
+ async upsert(input) {
52
+ const annotation = conversationAnnotationInputSchema.parse(input);
53
+ const now = /* @__PURE__ */ new Date();
54
+ await args.db.insert(juniorConversationAnnotations).values({
55
+ annotation,
56
+ conversationId: args.conversationId,
57
+ createdAt: now,
58
+ key: annotation.key,
59
+ kind: annotation.kind,
60
+ plugin: args.plugin,
61
+ updatedAt: now
62
+ }).onConflictDoUpdate({
63
+ target: [
64
+ juniorConversationAnnotations.conversationId,
65
+ juniorConversationAnnotations.plugin,
66
+ juniorConversationAnnotations.kind,
67
+ juniorConversationAnnotations.key
68
+ ],
69
+ set: { annotation, updatedAt: now }
70
+ });
71
+ },
72
+ async remove(kind, key) {
73
+ await args.db.delete(juniorConversationAnnotations).where(
74
+ and(
75
+ eq(
76
+ juniorConversationAnnotations.conversationId,
77
+ args.conversationId
78
+ ),
79
+ eq(juniorConversationAnnotations.plugin, args.plugin),
80
+ eq(juniorConversationAnnotations.kind, kind),
81
+ eq(juniorConversationAnnotations.key, key)
82
+ )
83
+ );
84
+ },
85
+ async list() {
86
+ return (await listConversationAnnotations(args.db, args.conversationId)).filter((a) => a.plugin === args.plugin);
87
+ }
88
+ };
89
+ }
90
+ async function listConversationAnnotations(db, conversationId) {
91
+ const rows = await db.select().from(juniorConversationAnnotations).where(eq(juniorConversationAnnotations.conversationId, conversationId)).orderBy(desc(juniorConversationAnnotations.updatedAt));
92
+ return rows.flatMap((row) => {
93
+ const parsed = conversationAnnotationInputSchema.safeParse(row.annotation);
94
+ return parsed.success ? [
95
+ {
96
+ ...parsed.data,
97
+ plugin: row.plugin,
98
+ createdAt: row.createdAt.toISOString(),
99
+ updatedAt: row.updatedAt.toISOString()
100
+ }
101
+ ] : [];
102
+ });
103
+ }
104
+
43
105
  // src/chat/plugins/logging.ts
44
106
  function createPluginLogger(plugin) {
45
107
  return {
@@ -261,6 +323,428 @@ function getSlackToolContext(context) {
261
323
 
262
324
  // src/chat/credentials/subject.ts
263
325
  import { createHmac, timingSafeEqual } from "crypto";
326
+
327
+ // src/chat/slack/client.ts
328
+ import { AsyncLocalStorage as AsyncLocalStorage2 } from "async_hooks";
329
+ import { WebClient } from "@slack/web-api";
330
+
331
+ // src/chat/slack/workspace-context.ts
332
+ import { AsyncLocalStorage } from "async_hooks";
333
+ var workspaceTeamIdStorage = new AsyncLocalStorage();
334
+ function runWithWorkspaceTeamId(teamId, fn) {
335
+ if (!teamId) return fn();
336
+ return workspaceTeamIdStorage.run(teamId, fn);
337
+ }
338
+ function getWorkspaceTeamId() {
339
+ return workspaceTeamIdStorage.getStore();
340
+ }
341
+
342
+ // src/chat/slack/client.ts
343
+ var SlackActionError = class extends Error {
344
+ code;
345
+ apiError;
346
+ needed;
347
+ provided;
348
+ statusCode;
349
+ requestId;
350
+ errorData;
351
+ retryAfterSeconds;
352
+ detail;
353
+ detailLine;
354
+ detailRule;
355
+ constructor(message, code, options = {}) {
356
+ super(message);
357
+ this.name = "SlackActionError";
358
+ this.code = code;
359
+ this.apiError = options.apiError;
360
+ this.needed = options.needed;
361
+ this.provided = options.provided;
362
+ this.statusCode = options.statusCode;
363
+ this.requestId = options.requestId;
364
+ this.errorData = options.errorData;
365
+ this.retryAfterSeconds = options.retryAfterSeconds;
366
+ this.detail = options.detail;
367
+ this.detailLine = options.detailLine;
368
+ this.detailRule = options.detailRule;
369
+ }
370
+ };
371
+ function serializeSlackErrorData(data) {
372
+ if (!data || typeof data !== "object") {
373
+ return void 0;
374
+ }
375
+ const filtered = Object.fromEntries(
376
+ Object.entries(data).filter(
377
+ ([key]) => key !== "error"
378
+ )
379
+ );
380
+ if (Object.keys(filtered).length === 0) {
381
+ return void 0;
382
+ }
383
+ try {
384
+ const serialized = JSON.stringify(filtered);
385
+ return serialized.length <= 600 ? serialized : `${serialized.slice(0, 597)}...`;
386
+ } catch {
387
+ return void 0;
388
+ }
389
+ }
390
+ function getHeaderString(headers, name) {
391
+ if (!headers || typeof headers !== "object") {
392
+ return void 0;
393
+ }
394
+ const key = name.toLowerCase();
395
+ const entries = headers;
396
+ for (const [entryKey, value] of Object.entries(entries)) {
397
+ if (entryKey.toLowerCase() !== key) continue;
398
+ if (typeof value === "string") return value;
399
+ if (Array.isArray(value)) {
400
+ const first = value.find((entry) => typeof entry === "string");
401
+ return typeof first === "string" ? first : void 0;
402
+ }
403
+ }
404
+ return void 0;
405
+ }
406
+ function parseSlackCanvasDetail(detail) {
407
+ if (typeof detail !== "string") {
408
+ return {};
409
+ }
410
+ const trimmed = detail.trim();
411
+ if (!trimmed) {
412
+ return {};
413
+ }
414
+ const parsed = {
415
+ detail: trimmed
416
+ };
417
+ const lineMatch = trimmed.match(/line\s+(\d+):/i);
418
+ if (lineMatch) {
419
+ const line = Number.parseInt(lineMatch[1] ?? "", 10);
420
+ if (Number.isFinite(line)) {
421
+ parsed.detailLine = line;
422
+ }
423
+ }
424
+ if (/unsupported heading depth/i.test(trimmed)) {
425
+ parsed.detailRule = "unsupported_heading_depth";
426
+ }
427
+ return parsed;
428
+ }
429
+ var installationTokenStorage = new AsyncLocalStorage2();
430
+ var clientsByToken = /* @__PURE__ */ new Map();
431
+ function runWithSlackInstallationToken(token, fn) {
432
+ const trimmed = token.trim();
433
+ if (!trimmed) {
434
+ throw new SlackActionError(
435
+ "Slack installation token binding requires a non-empty token",
436
+ "missing_token"
437
+ );
438
+ }
439
+ return installationTokenStorage.run({ token: trimmed }, fn);
440
+ }
441
+ function resolveSlackToken() {
442
+ const ambientToken = installationTokenStorage.getStore()?.token;
443
+ if (ambientToken) {
444
+ return ambientToken;
445
+ }
446
+ const envToken = getSlackBotToken();
447
+ if (envToken) {
448
+ return envToken;
449
+ }
450
+ const teamId = getWorkspaceTeamId();
451
+ if (teamId) {
452
+ throw new SlackActionError(
453
+ `Slack call is scoped to workspace ${teamId} but no installation token is bound and no default bot token is configured`,
454
+ "missing_token"
455
+ );
456
+ }
457
+ throw new SlackActionError(
458
+ "SLACK_BOT_TOKEN (or SLACK_BOT_USER_TOKEN) is required for Slack Web API actions in this service",
459
+ "missing_token"
460
+ );
461
+ }
462
+ function normalizeSlackConversationId(channelId) {
463
+ return parseSlackChannelReferenceId(channelId);
464
+ }
465
+ function getClient() {
466
+ const token = resolveSlackToken();
467
+ let cached = clientsByToken.get(token);
468
+ if (!cached) {
469
+ cached = new WebClient(token, {
470
+ retryConfig: { retries: 0 },
471
+ rejectRateLimitedCalls: true
472
+ });
473
+ clientsByToken.set(token, cached);
474
+ }
475
+ return cached;
476
+ }
477
+ function mapSlackError(error) {
478
+ if (error instanceof SlackActionError) {
479
+ return error;
480
+ }
481
+ const candidate = error;
482
+ const apiError = candidate.data?.error;
483
+ const message = candidate.message ?? "Slack action failed";
484
+ const baseOptions = {
485
+ apiError,
486
+ statusCode: candidate.statusCode,
487
+ requestId: getHeaderString(candidate.headers, "x-slack-req-id"),
488
+ errorData: serializeSlackErrorData(candidate.data),
489
+ ...parseSlackCanvasDetail(candidate.data?.detail)
490
+ };
491
+ if (apiError === "missing_scope") {
492
+ return new SlackActionError(message, "missing_scope", {
493
+ ...baseOptions,
494
+ needed: candidate.data?.needed,
495
+ provided: candidate.data?.provided
496
+ });
497
+ }
498
+ if (apiError === "not_in_channel") {
499
+ return new SlackActionError(message, "not_in_channel", baseOptions);
500
+ }
501
+ if (apiError === "restricted_action_read_only_channel") {
502
+ return new SlackActionError(message, "read_only_channel", baseOptions);
503
+ }
504
+ if (apiError === "already_reacted") {
505
+ return new SlackActionError(message, "already_reacted", baseOptions);
506
+ }
507
+ if (apiError === "no_reaction") {
508
+ return new SlackActionError(message, "no_reaction", baseOptions);
509
+ }
510
+ if (apiError === "invalid_arguments") {
511
+ return new SlackActionError(message, "invalid_arguments", baseOptions);
512
+ }
513
+ if (apiError === "invalid_cursor") {
514
+ return new SlackActionError(message, "invalid_arguments", baseOptions);
515
+ }
516
+ if (apiError === "invalid_name") {
517
+ return new SlackActionError(message, "invalid_arguments", baseOptions);
518
+ }
519
+ if (apiError === "not_found" || apiError === "channel_not_found" || apiError === "message_not_found") {
520
+ return new SlackActionError(message, "not_found", baseOptions);
521
+ }
522
+ if (apiError === "feature_not_enabled" || apiError === "not_allowed_token_type") {
523
+ return new SlackActionError(message, "feature_unavailable", baseOptions);
524
+ }
525
+ if (apiError === "canvas_creation_failed") {
526
+ return new SlackActionError(message, "canvas_creation_failed", baseOptions);
527
+ }
528
+ if (apiError === "canvas_editing_failed") {
529
+ return new SlackActionError(message, "canvas_editing_failed", baseOptions);
530
+ }
531
+ if (candidate.code === "slack_webapi_rate_limited_error" || candidate.statusCode === 429) {
532
+ return new SlackActionError(message, "rate_limited", {
533
+ ...baseOptions,
534
+ retryAfterSeconds: candidate.retryAfter
535
+ });
536
+ }
537
+ return new SlackActionError(message, "internal_error", baseOptions);
538
+ }
539
+ var MAX_RETRY_DELAY_MS = 1e4;
540
+ var MAX_TOTAL_RETRY_DELAY_MS = 2e4;
541
+ var CONNECTION_ERROR_CODES = /* @__PURE__ */ new Set([
542
+ "ECONNREFUSED",
543
+ "ECONNRESET",
544
+ "ENOTFOUND",
545
+ "EAI_AGAIN"
546
+ ]);
547
+ var TIMEOUT_ERROR_CODES = /* @__PURE__ */ new Set([
548
+ "ETIMEDOUT",
549
+ "ECONNABORTED",
550
+ "ESOCKETTIMEDOUT"
551
+ ]);
552
+ function findNetworkErrorCode(error, depth = 0) {
553
+ if (!error || typeof error !== "object" || depth > 4) {
554
+ return void 0;
555
+ }
556
+ const candidate = error;
557
+ if (typeof candidate.code === "string" && (CONNECTION_ERROR_CODES.has(candidate.code) || TIMEOUT_ERROR_CODES.has(candidate.code))) {
558
+ return candidate.code;
559
+ }
560
+ return findNetworkErrorCode(candidate.original, depth + 1) ?? findNetworkErrorCode(candidate.cause, depth + 1);
561
+ }
562
+ function hasSocketHangUpMessage(error) {
563
+ return error instanceof Error && error.message.toLowerCase().includes("socket hang up");
564
+ }
565
+ function classifySlackRetry(raw, mapped) {
566
+ if (mapped.code === "rate_limited") {
567
+ return "rate_limited";
568
+ }
569
+ if (mapped.statusCode !== void 0 && mapped.statusCode >= 500) {
570
+ return "server_error";
571
+ }
572
+ const networkCode = findNetworkErrorCode(raw);
573
+ if (networkCode && CONNECTION_ERROR_CODES.has(networkCode)) {
574
+ return "connection";
575
+ }
576
+ if (networkCode && TIMEOUT_ERROR_CODES.has(networkCode)) {
577
+ return "timeout";
578
+ }
579
+ if (hasSocketHangUpMessage(raw)) {
580
+ return "connection";
581
+ }
582
+ return "none";
583
+ }
584
+ async function withSlackRetries(task, maxAttempts = 3, context = {}) {
585
+ let attempt = 0;
586
+ let totalDelayMs = 0;
587
+ const action = context.action ?? "unknown";
588
+ while (attempt < maxAttempts) {
589
+ attempt += 1;
590
+ const attemptNumber = attempt;
591
+ try {
592
+ return await withSpan(
593
+ `POST slack.com/api/${action}`,
594
+ "http.client",
595
+ {},
596
+ async () => {
597
+ try {
598
+ return await task();
599
+ } catch (error) {
600
+ const mapped = mapSlackError(error);
601
+ const errorAttrs = {
602
+ "error.type": mapped.code
603
+ };
604
+ if (mapped.apiError) {
605
+ errorAttrs["app.slack.api_error_code"] = mapped.apiError;
606
+ }
607
+ if (mapped.code === "rate_limited") {
608
+ errorAttrs["http.response.status_code"] = 429;
609
+ if (mapped.retryAfterSeconds) {
610
+ errorAttrs["app.slack.retry_after_ms"] = mapped.retryAfterSeconds * 1e3;
611
+ }
612
+ } else if (mapped.statusCode != null) {
613
+ errorAttrs["http.response.status_code"] = mapped.statusCode;
614
+ }
615
+ setSpanAttributes(errorAttrs);
616
+ setSpanStatus("error");
617
+ throw error;
618
+ }
619
+ },
620
+ {
621
+ "http.request.method": "POST",
622
+ "server.address": "slack.com",
623
+ "url.scheme": "https",
624
+ "url.path": `/api/${action}`,
625
+ "app.slack.method": action,
626
+ "app.retry.max_attempts": maxAttempts,
627
+ ...attemptNumber > 1 ? { "http.resend_count": attemptNumber - 1 } : {},
628
+ ...context.attributes ?? {},
629
+ ...context.spanAttributes ?? {}
630
+ }
631
+ );
632
+ } catch (error) {
633
+ const mapped = mapSlackError(error);
634
+ const retryClass = classifySlackRetry(error, mapped);
635
+ const isRetryable = retryClass === "rate_limited" || retryClass === "connection" || retryClass === "server_error" || retryClass === "timeout" && context.idempotent === true;
636
+ const remainingDelayBudgetMs = MAX_TOTAL_RETRY_DELAY_MS - totalDelayMs;
637
+ const baseLogAttributes = {
638
+ "app.slack.action": action,
639
+ "app.slack.error_code": mapped.code,
640
+ ...mapped.apiError ? { "app.slack.api_error": mapped.apiError } : {},
641
+ ...mapped.detail ? { "app.slack.detail": mapped.detail } : {},
642
+ ...mapped.detailLine !== void 0 ? { "app.slack.detail_line": mapped.detailLine } : {},
643
+ ...mapped.detailRule ? { "app.slack.detail_rule": mapped.detailRule } : {},
644
+ ...mapped.requestId ? { "app.slack.request_id": mapped.requestId } : {},
645
+ ...mapped.statusCode !== void 0 ? { "http.response.status_code": mapped.statusCode } : {},
646
+ ...context.attributes ?? {}
647
+ };
648
+ if (!isRetryable || attempt >= maxAttempts || remainingDelayBudgetMs <= 0) {
649
+ logWarn(
650
+ "slack_action_failed",
651
+ {},
652
+ {
653
+ ...baseLogAttributes,
654
+ ...mapped.errorData ? { "app.slack.error_data": mapped.errorData } : {}
655
+ },
656
+ "Slack action failed"
657
+ );
658
+ throw mapped;
659
+ }
660
+ logWarn(
661
+ "slack_action_retrying",
662
+ {},
663
+ {
664
+ ...baseLogAttributes,
665
+ "app.slack.retry_attempt": attempt,
666
+ "app.slack.retry_class": retryClass
667
+ },
668
+ "Retrying Slack action after transient failure"
669
+ );
670
+ const retryAfterMs = retryClass === "rate_limited" && mapped.retryAfterSeconds && mapped.retryAfterSeconds > 0 ? mapped.retryAfterSeconds * 1e3 : void 0;
671
+ const backoffMs = Math.min(2e3, 250 * 2 ** (attempt - 1));
672
+ const delayMs = Math.min(
673
+ retryAfterMs ?? backoffMs,
674
+ MAX_RETRY_DELAY_MS,
675
+ remainingDelayBudgetMs
676
+ );
677
+ totalDelayMs += delayMs;
678
+ await sleep(delayMs);
679
+ }
680
+ }
681
+ throw new SlackActionError(
682
+ "Slack action exhausted retries",
683
+ "internal_error"
684
+ );
685
+ }
686
+ function getSlackClient() {
687
+ return getClient();
688
+ }
689
+ function isDmChannel(channelId) {
690
+ const normalized = normalizeSlackConversationId(channelId);
691
+ return Boolean(normalized && normalized.startsWith("D"));
692
+ }
693
+ function isConversationScopedChannel(channelId) {
694
+ const normalized = normalizeSlackConversationId(channelId);
695
+ if (!normalized) return false;
696
+ return normalized.startsWith("C") || normalized.startsWith("G") || normalized.startsWith("D");
697
+ }
698
+ function isConversationChannel(channelId) {
699
+ const normalized = normalizeSlackConversationId(channelId);
700
+ if (!normalized) return false;
701
+ return normalized.startsWith("C") || normalized.startsWith("G");
702
+ }
703
+ async function getFilePermalink(fileId) {
704
+ const client = getClient();
705
+ const response = await withSlackRetries(
706
+ () => client.files.info({
707
+ file: fileId
708
+ }),
709
+ 3,
710
+ {
711
+ action: "files.info",
712
+ idempotent: true,
713
+ spanAttributes: { "app.slack.file_id": fileId }
714
+ }
715
+ );
716
+ return response.file?.permalink;
717
+ }
718
+ async function downloadPrivateSlackFile(url) {
719
+ const token = resolveSlackToken();
720
+ return withSpan(
721
+ "GET files.slack.com",
722
+ "http.client",
723
+ {},
724
+ async () => {
725
+ const response = await fetch(url, {
726
+ headers: {
727
+ Authorization: `Bearer ${token}`
728
+ }
729
+ });
730
+ setSpanAttributes({ "http.response.status_code": response.status });
731
+ if (!response.ok) {
732
+ setSpanAttributes({ "error.type": String(response.status) });
733
+ setSpanStatus("error");
734
+ throw new Error(`Slack file download failed: ${response.status}`);
735
+ }
736
+ return Buffer.from(await response.arrayBuffer());
737
+ },
738
+ {
739
+ "http.request.method": "GET",
740
+ "server.address": "files.slack.com",
741
+ "url.scheme": "https",
742
+ "app.slack.method": "files.download"
743
+ }
744
+ );
745
+ }
746
+
747
+ // src/chat/credentials/subject.ts
264
748
  var CREDENTIAL_SUBJECT_HMAC_CONTEXT = "junior.credential_subject.v1";
265
749
  var CREDENTIAL_SUBJECT_SIGNATURE_VERSION = "v1";
266
750
  function getCredentialSubjectSecret() {
@@ -1361,6 +1845,7 @@ function createPluginHookRunner(input = {}) {
1361
1845
  }
1362
1846
 
1363
1847
  export {
1848
+ listConversationAnnotations,
1364
1849
  createPluginLogger,
1365
1850
  createPluginModel,
1366
1851
  createPluginEmbedder,
@@ -1368,6 +1853,18 @@ export {
1368
1853
  setDashboardConversationLinkOptions,
1369
1854
  getDashboardConversationLink,
1370
1855
  getSlackToolContext,
1856
+ runWithWorkspaceTeamId,
1857
+ getWorkspaceTeamId,
1858
+ SlackActionError,
1859
+ getHeaderString,
1860
+ runWithSlackInstallationToken,
1861
+ normalizeSlackConversationId,
1862
+ withSlackRetries,
1863
+ getSlackClient,
1864
+ isDmChannel,
1865
+ isConversationScopedChannel,
1866
+ getFilePermalink,
1867
+ downloadPrivateSlackFile,
1371
1868
  bindSlackDirectCredentialSubject,
1372
1869
  verifySlackDirectCredentialSubject,
1373
1870
  bindScheduledTaskCredentialSubject,
@@ -2,9 +2,6 @@ import {
2
2
  addAgentTurnUsage,
3
3
  agentTurnUsageSchema
4
4
  } from "./chunk-MU6HHZEN.js";
5
- import {
6
- getStateAdapter
7
- } from "./chunk-KU6ALEJC.js";
8
5
  import {
9
6
  agentHistoryItemSchema,
10
7
  contextProvenance,
@@ -16,16 +13,17 @@ import {
16
13
  instructionProvenanceFor,
17
14
  sanitizePostgresJson,
18
15
  withConversationEventLock
19
- } from "./chunk-ZRQUQJUV.js";
16
+ } from "./chunk-D3N4BPSL.js";
20
17
  import {
18
+ getStateAdapter,
21
19
  sleep
22
- } from "./chunk-4ZNGQH7C.js";
20
+ } from "./chunk-VJTWKJWQ.js";
23
21
  import {
24
22
  botConfig,
25
23
  escapeXml,
26
24
  resolveGatewayModel,
27
25
  unwrapCurrentInstruction
28
- } from "./chunk-DS27ZBSP.js";
26
+ } from "./chunk-VLTWQQKH.js";
29
27
  import {
30
28
  toStoredSlackActor
31
29
  } from "./chunk-HZF5P5UN.js";
@@ -34,7 +32,7 @@ import {
34
32
  logException,
35
33
  logWarn,
36
34
  setSpanAttributes
37
- } from "./chunk-72Z3HNTQ.js";
35
+ } from "./chunk-ZJ6IVZVO.js";
38
36
  import {
39
37
  isRecord,
40
38
  toOptionalNumber,
@@ -2715,12 +2713,6 @@ function getChannelConfigurationServiceById(channelId) {
2715
2713
  });
2716
2714
  }
2717
2715
 
2718
- // src/chat/interruption-marker.ts
2719
- var INTERRUPTED_MARKER = "\n\n[Response interrupted before completion]";
2720
- function getInterruptionMarker() {
2721
- return INTERRUPTED_MARKER;
2722
- }
2723
-
2724
2716
  // src/chat/services/pending-auth.ts
2725
2717
  var AUTH_LINK_REUSE_WINDOW_MS = 10 * 60 * 1e3;
2726
2718
  var NON_REQUEST_SKIPPED_REASON_PREFIXES = [
@@ -2842,7 +2834,6 @@ export {
2842
2834
  persistAuthPauseSessionRecord,
2843
2835
  persistContinuationSessionRecord,
2844
2836
  persistYieldSessionRecord,
2845
- getInterruptionMarker,
2846
2837
  JUNIOR_THREAD_STATE_TTL_MS,
2847
2838
  canReusePendingAuthLink,
2848
2839
  getConversationPendingAuth,
@@ -4,18 +4,18 @@ import {
4
4
  juniorConversations,
5
5
  juniorIdentities,
6
6
  juniorUsers
7
- } from "./chunk-RXRQJF7Y.js";
7
+ } from "./chunk-GCGJGSQL.js";
8
8
  import {
9
9
  TURN_REASONING_LEVELS,
10
10
  getChatConfig,
11
11
  modelProfileSchema
12
- } from "./chunk-DS27ZBSP.js";
12
+ } from "./chunk-VLTWQQKH.js";
13
13
  import {
14
14
  juniorDestinations
15
15
  } from "./chunk-VFUK3X5B.js";
16
16
  import {
17
17
  normalizeIdentityEmail
18
- } from "./chunk-72Z3HNTQ.js";
18
+ } from "./chunk-ZJ6IVZVO.js";
19
19
  import {
20
20
  parseSlackChannelReferenceId,
21
21
  parseSlackTeamId
@@ -114,7 +114,9 @@ function pluginCatalogConfigFromEnv(env = process.env) {
114
114
  return packages ? { packages } : void 0;
115
115
  }
116
116
  function pluginRuntimeRegistrationsFromPluginSet(pluginSet) {
117
- return pluginSet?.registrations.filter((plugin) => plugin.hooks || plugin.tasks) ?? [];
117
+ return pluginSet?.registrations.filter(
118
+ (plugin) => plugin.hooks || plugin.tasks || plugin.userPages?.length
119
+ ) ?? [];
118
120
  }
119
121
  function pluginCliRegistrationsFromPluginSet(pluginSet) {
120
122
  return pluginSet?.registrations.filter((plugin) => plugin.cli) ?? [];
@@ -272,6 +272,25 @@ var juniorConversationEvents = pgTable6(
272
272
  ]
273
273
  );
274
274
 
275
+ // src/db/schema/stats.ts
276
+ import { date, integer as integer3, pgTable as pgTable7, primaryKey as primaryKey3, text as text7 } from "drizzle-orm/pg-core";
277
+ var juniorStats = pgTable7(
278
+ "junior_stats",
279
+ {
280
+ date: date("date", { mode: "string" }).notNull(),
281
+ namespace: text7("namespace").notNull(),
282
+ metric: text7("metric").notNull(),
283
+ name: text7("name").notNull(),
284
+ count: integer3("count").notNull().default(0)
285
+ },
286
+ (table) => [
287
+ primaryKey3({
288
+ name: "junior_stats_date_namespace_metric_name_pk",
289
+ columns: [table.date, table.namespace, table.metric, table.name]
290
+ })
291
+ ]
292
+ );
293
+
275
294
  // src/db/schema.ts
276
295
  var juniorSqlSchema = {
277
296
  juniorConversationAnnotations,
@@ -280,6 +299,7 @@ var juniorSqlSchema = {
280
299
  juniorConversations,
281
300
  juniorDestinations,
282
301
  juniorIdentities,
302
+ juniorStats,
283
303
  juniorUsers
284
304
  };
285
305
 
@@ -530,5 +550,6 @@ export {
530
550
  juniorConversationAnnotations,
531
551
  juniorApiTokens,
532
552
  juniorConversationEvents,
553
+ juniorStats,
533
554
  createJuniorSqlExecutor
534
555
  };