@sentry/junior 0.159.0 → 0.161.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 (82) hide show
  1. package/dist/{agent-hooks-IAD3AANZ.js → agent-hooks-O75UJNC6.js} +13 -10
  2. package/dist/api/conversations/events.d.ts +1 -1
  3. package/dist/api/locations/detail.d.ts +17 -0
  4. package/dist/api/people/plugin-reports.d.ts +7 -0
  5. package/dist/api/schema/conversation.d.ts +109 -8
  6. package/dist/api/schema/location.d.ts +29 -0
  7. package/dist/api/schema/person.d.ts +29 -0
  8. package/dist/api/schema.js +1 -1
  9. package/dist/api.js +137 -27
  10. package/dist/app.js +73 -42
  11. package/dist/{catalog-runtime-6VANKLH5.js → catalog-runtime-Z3FHU4ZY.js} +5 -5
  12. package/dist/chat/artifacts/store.d.ts +42 -0
  13. package/dist/chat/conversations/history.d.ts +31 -1
  14. package/dist/chat/conversations/projection.d.ts +13 -0
  15. package/dist/chat/plugins/agent-hooks.d.ts +9 -1
  16. package/dist/chat/plugins/annotations.d.ts +3 -0
  17. package/dist/chat/plugins/conversation-sidebar.d.ts +3 -0
  18. package/dist/chat/plugins/viewer.d.ts +4 -0
  19. package/dist/chat/slack/mrkdwn.d.ts +3 -4
  20. package/dist/chat/slack/tools/send-files.d.ts +2 -2
  21. package/dist/chat/tool-support/action-review.d.ts +5 -4
  22. package/dist/chat/tools/publish-image.d.ts +44 -0
  23. package/dist/chat/tools/search-conversation-events.d.ts +1 -1
  24. package/dist/chat/tools/unpublish-image.d.ts +35 -0
  25. package/dist/{chunk-YSMAMSOU.js → chunk-4WAZ6DGX.js} +10 -10
  26. package/dist/{chunk-AIQKUJKK.js → chunk-6ILVZOF6.js} +4 -4
  27. package/dist/{chunk-YCBZ47LC.js → chunk-6WTDZKFR.js} +1 -1
  28. package/dist/{chunk-CNHELV2P.js → chunk-CGAIDDLD.js} +1 -1
  29. package/dist/{chunk-U7D4VLUF.js → chunk-DGKLYDX7.js} +2 -2
  30. package/dist/{chunk-TD7ZQZ5W.js → chunk-DNSIU27S.js} +1 -1
  31. package/dist/{chunk-PT7B6A3O.js → chunk-EAIQBR66.js} +1 -1
  32. package/dist/{chunk-O2DZWUPQ.js → chunk-FLMD25Y5.js} +239 -199
  33. package/dist/{chunk-FWDN67U3.js → chunk-G35DUPNB.js} +4 -4
  34. package/dist/{chunk-UG673UVL.js → chunk-HSFYTIS2.js} +1 -1
  35. package/dist/{chunk-WTQ3CKC3.js → chunk-IRHX2S46.js} +1 -1
  36. package/dist/{chunk-DXSZJAVZ.js → chunk-IW7C5C7E.js} +19 -21
  37. package/dist/{chunk-Q5X37U6G.js → chunk-IYFKONIL.js} +1 -1
  38. package/dist/{chunk-EBP6IS66.js → chunk-MNY5M7RB.js} +537 -27
  39. package/dist/{chunk-6IADZQJR.js → chunk-MVG5UPLM.js} +1 -1
  40. package/dist/{chunk-27UQ7UB3.js → chunk-O7JTSJPU.js} +6 -6
  41. package/dist/{chunk-NCJV265M.js → chunk-OPIJ6HY3.js} +9 -9
  42. package/dist/{chunk-X7AIXRVJ.js → chunk-OY2DBUV4.js} +5 -5
  43. package/dist/{chunk-SQOVZSWF.js → chunk-OYQEO72I.js} +480 -125
  44. package/dist/{chunk-UP6KQVF5.js → chunk-QHXT7LLF.js} +1 -1
  45. package/dist/{chunk-UKKOKMRE.js → chunk-QQ7SY5IZ.js} +5 -5
  46. package/dist/{chunk-PQWWCKNB.js → chunk-RULQD7OX.js} +4 -4
  47. package/dist/{chunk-L2PEQ2PY.js → chunk-RVENVPNO.js} +2 -2
  48. package/dist/{chunk-EQXVKNLM.js → chunk-SA6BEEAB.js} +27 -9
  49. package/dist/{chunk-54O6IKUS.js → chunk-STZVUVMS.js} +40 -4
  50. package/dist/{chunk-QODFNKQS.js → chunk-U2XOBFDK.js} +17 -3
  51. package/dist/{chunk-AZW5HKC5.js → chunk-V67ORYKQ.js} +8 -10
  52. package/dist/{chunk-DJZPA72K.js → chunk-VXFDVY4C.js} +8 -36
  53. package/dist/{chunk-LUIKMY35.js → chunk-WKN4ZQTZ.js} +1 -1
  54. package/dist/{chunk-OGMHLKNO.js → chunk-WVSQ2SCS.js} +1 -1
  55. package/dist/{chunk-IJ6KWS3V.js → chunk-YSWHAOPQ.js} +1 -1
  56. package/dist/{chunk-NT6V5WEE.js → chunk-ZBCLWBMV.js} +5 -5
  57. package/dist/cli/chat.js +29 -30
  58. package/dist/cli/check.js +6 -6
  59. package/dist/cli/plugins.js +12 -13
  60. package/dist/cli/snapshot-create.js +9 -9
  61. package/dist/cli/upgrade.js +7 -7
  62. package/dist/db/schema/artifacts.d.ts +178 -0
  63. package/dist/db/schema.d.ts +179 -1
  64. package/dist/{db-BIIMAPFB.js → db-KKOZRE62.js} +7 -7
  65. package/dist/handlers/artifacts.d.ts +8 -0
  66. package/dist/instrumentation.js +4 -4
  67. package/dist/nitro.js +2 -2
  68. package/dist/{oauth-callback-server-4BENCPP3.js → oauth-callback-server-5AUZDWXA.js} +22 -23
  69. package/dist/{runner-TUBJLEYO.js → runner-K5H5NDEC.js} +12 -13
  70. package/dist/{skills-MBRLNBRV.js → skills-OZGPB6G4.js} +6 -6
  71. package/dist/spawn-RCQSC2L5.js +37 -0
  72. package/dist/{validation-YTJGRJYT.js → validation-TVHCNCYL.js} +6 -6
  73. package/dist/version.js +1 -1
  74. package/dist/{work-5MRHKA54.js → work-DXCQMJ5U.js} +17 -17
  75. package/dist/worker-24HZ5HRI.js +24 -0
  76. package/migrations/0028_artifacts.sql +16 -0
  77. package/migrations/meta/0028_snapshot.json +2685 -0
  78. package/migrations/meta/_journal.json +8 -1
  79. package/package.json +4 -4
  80. package/dist/chunk-K2LAI7RW.js +0 -441
  81. package/dist/spawn-ZWIXA46E.js +0 -37
  82. package/dist/worker-S2V7HZJ4.js +0 -24
@@ -1,33 +1,27 @@
1
- import {
2
- getSlackClient,
3
- isConversationChannel,
4
- isConversationScopedChannel,
5
- isDmChannel,
6
- normalizeSlackConversationId
7
- } from "./chunk-K2LAI7RW.js";
8
1
  import {
9
2
  getConversationEventStore,
10
3
  getDb
11
- } from "./chunk-QODFNKQS.js";
4
+ } from "./chunk-U2XOBFDK.js";
12
5
  import {
13
6
  juniorConversationAnnotations,
14
7
  juniorConversationEvents,
15
8
  juniorIdentities,
16
9
  juniorUsers
17
- } from "./chunk-O2DZWUPQ.js";
10
+ } from "./chunk-FLMD25Y5.js";
18
11
  import {
19
- SANDBOX_WORKSPACE_ROOT
12
+ SANDBOX_WORKSPACE_ROOT,
13
+ sleep
20
14
  } from "./chunk-EM225ITJ.js";
21
15
  import {
22
16
  getStateAdapter
23
- } from "./chunk-Q5X37U6G.js";
17
+ } from "./chunk-IYFKONIL.js";
24
18
  import {
25
19
  botConfig,
26
20
  completeObject,
27
21
  embedTexts,
28
22
  getSlackBotToken,
29
23
  standardModelId
30
- } from "./chunk-L2PEQ2PY.js";
24
+ } from "./chunk-RVENVPNO.js";
31
25
  import {
32
26
  isActorUserId,
33
27
  parseActorUserId,
@@ -39,8 +33,11 @@ import {
39
33
  logException,
40
34
  logInfo,
41
35
  logWarn,
42
- normalizeIdentityEmail
43
- } from "./chunk-TD7ZQZ5W.js";
36
+ normalizeIdentityEmail,
37
+ setSpanAttributes,
38
+ setSpanStatus,
39
+ withSpan
40
+ } from "./chunk-DNSIU27S.js";
44
41
 
45
42
  // src/chat/plugins/agent-hooks.ts
46
43
  import {
@@ -53,10 +50,20 @@ import {
53
50
  } from "@sentry/junior-plugin-api";
54
51
 
55
52
  // src/chat/plugins/annotations.ts
56
- import { and, desc, eq } from "drizzle-orm";
53
+ import { and, desc, eq, inArray } from "drizzle-orm";
57
54
  import {
58
55
  conversationAnnotationInputSchema
59
56
  } from "@sentry/junior-plugin-api";
57
+ function annotationFromRow(row) {
58
+ const parsed = conversationAnnotationInputSchema.safeParse(row.annotation);
59
+ if (!parsed.success) return void 0;
60
+ return {
61
+ ...parsed.data,
62
+ plugin: row.plugin,
63
+ createdAt: row.createdAt.toISOString(),
64
+ updatedAt: row.updatedAt.toISOString()
65
+ };
66
+ }
60
67
  function createPluginAnnotations(args) {
61
68
  return {
62
69
  async upsert(input) {
@@ -99,18 +106,30 @@ function createPluginAnnotations(args) {
99
106
  };
100
107
  }
101
108
  async function listConversationAnnotations(db, conversationId) {
102
- const rows = await db.select().from(juniorConversationAnnotations).where(eq(juniorConversationAnnotations.conversationId, conversationId)).orderBy(desc(juniorConversationAnnotations.updatedAt));
103
- return rows.flatMap((row) => {
104
- const parsed = conversationAnnotationInputSchema.safeParse(row.annotation);
105
- return parsed.success ? [
106
- {
107
- ...parsed.data,
108
- plugin: row.plugin,
109
- createdAt: row.createdAt.toISOString(),
110
- updatedAt: row.updatedAt.toISOString()
111
- }
112
- ] : [];
113
- });
109
+ const byConversation = await listConversationAnnotationsById(db, [
110
+ conversationId
111
+ ]);
112
+ return byConversation.get(conversationId) ?? [];
113
+ }
114
+ async function listConversationAnnotationsById(db, conversationIds) {
115
+ const byConversation = /* @__PURE__ */ new Map();
116
+ if (conversationIds.length === 0) return byConversation;
117
+ const rows = await db.select().from(juniorConversationAnnotations).where(
118
+ inArray(juniorConversationAnnotations.conversationId, [
119
+ ...conversationIds
120
+ ])
121
+ ).orderBy(desc(juniorConversationAnnotations.updatedAt));
122
+ for (const row of rows) {
123
+ const annotation = annotationFromRow(row);
124
+ if (!annotation) continue;
125
+ const current = byConversation.get(row.conversationId);
126
+ if (current) {
127
+ current.push(annotation);
128
+ } else {
129
+ byConversation.set(row.conversationId, [annotation]);
130
+ }
131
+ }
132
+ return byConversation;
114
133
  }
115
134
 
116
135
  // src/chat/plugins/conversation-events.ts
@@ -436,6 +455,416 @@ function getDashboardTaskLink(taskId) {
436
455
  return resolveDashboardPath(`/tasks/${encodeURIComponent(taskId)}`);
437
456
  }
438
457
 
458
+ // src/chat/slack/client.ts
459
+ import { AsyncLocalStorage as AsyncLocalStorage2 } from "async_hooks";
460
+ import { WebClient } from "@slack/web-api";
461
+
462
+ // src/chat/slack/workspace-context.ts
463
+ import { AsyncLocalStorage } from "async_hooks";
464
+ var workspaceTeamIdStorage = new AsyncLocalStorage();
465
+ function runWithWorkspaceTeamId(teamId, fn) {
466
+ if (!teamId) return fn();
467
+ return workspaceTeamIdStorage.run(teamId, fn);
468
+ }
469
+ function getWorkspaceTeamId() {
470
+ return workspaceTeamIdStorage.getStore();
471
+ }
472
+
473
+ // src/chat/slack/client.ts
474
+ var SlackActionError = class extends Error {
475
+ code;
476
+ apiError;
477
+ needed;
478
+ provided;
479
+ statusCode;
480
+ requestId;
481
+ errorData;
482
+ retryAfterSeconds;
483
+ detail;
484
+ detailLine;
485
+ detailRule;
486
+ constructor(message, code, options = {}) {
487
+ super(message);
488
+ this.name = "SlackActionError";
489
+ this.code = code;
490
+ this.apiError = options.apiError;
491
+ this.needed = options.needed;
492
+ this.provided = options.provided;
493
+ this.statusCode = options.statusCode;
494
+ this.requestId = options.requestId;
495
+ this.errorData = options.errorData;
496
+ this.retryAfterSeconds = options.retryAfterSeconds;
497
+ this.detail = options.detail;
498
+ this.detailLine = options.detailLine;
499
+ this.detailRule = options.detailRule;
500
+ }
501
+ };
502
+ function serializeSlackErrorData(data) {
503
+ if (!data || typeof data !== "object") {
504
+ return void 0;
505
+ }
506
+ const filtered = Object.fromEntries(
507
+ Object.entries(data).filter(
508
+ ([key]) => key !== "error"
509
+ )
510
+ );
511
+ if (Object.keys(filtered).length === 0) {
512
+ return void 0;
513
+ }
514
+ try {
515
+ const serialized = JSON.stringify(filtered);
516
+ return serialized.length <= 600 ? serialized : `${serialized.slice(0, 597)}...`;
517
+ } catch {
518
+ return void 0;
519
+ }
520
+ }
521
+ function getHeaderString(headers, name) {
522
+ if (!headers || typeof headers !== "object") {
523
+ return void 0;
524
+ }
525
+ const key = name.toLowerCase();
526
+ const entries = headers;
527
+ for (const [entryKey, value] of Object.entries(entries)) {
528
+ if (entryKey.toLowerCase() !== key) continue;
529
+ if (typeof value === "string") return value;
530
+ if (Array.isArray(value)) {
531
+ const first = value.find((entry) => typeof entry === "string");
532
+ return typeof first === "string" ? first : void 0;
533
+ }
534
+ }
535
+ return void 0;
536
+ }
537
+ function parseSlackCanvasDetail(detail) {
538
+ if (typeof detail !== "string") {
539
+ return {};
540
+ }
541
+ const trimmed = detail.trim();
542
+ if (!trimmed) {
543
+ return {};
544
+ }
545
+ const parsed = {
546
+ detail: trimmed
547
+ };
548
+ const lineMatch = trimmed.match(/line\s+(\d+):/i);
549
+ if (lineMatch) {
550
+ const line = Number.parseInt(lineMatch[1] ?? "", 10);
551
+ if (Number.isFinite(line)) {
552
+ parsed.detailLine = line;
553
+ }
554
+ }
555
+ if (/unsupported heading depth/i.test(trimmed)) {
556
+ parsed.detailRule = "unsupported_heading_depth";
557
+ }
558
+ return parsed;
559
+ }
560
+ var installationTokenStorage = new AsyncLocalStorage2();
561
+ var clientsByToken = /* @__PURE__ */ new Map();
562
+ function runWithSlackInstallationToken(token, fn) {
563
+ const trimmed = token.trim();
564
+ if (!trimmed) {
565
+ throw new SlackActionError(
566
+ "Slack installation token binding requires a non-empty token",
567
+ "missing_token"
568
+ );
569
+ }
570
+ return installationTokenStorage.run({ token: trimmed }, fn);
571
+ }
572
+ function resolveSlackToken() {
573
+ const ambientToken = installationTokenStorage.getStore()?.token;
574
+ if (ambientToken) {
575
+ return ambientToken;
576
+ }
577
+ const envToken = getSlackBotToken();
578
+ if (envToken) {
579
+ return envToken;
580
+ }
581
+ const teamId = getWorkspaceTeamId();
582
+ if (teamId) {
583
+ throw new SlackActionError(
584
+ `Slack call is scoped to workspace ${teamId} but no installation token is bound and no default bot token is configured`,
585
+ "missing_token"
586
+ );
587
+ }
588
+ throw new SlackActionError(
589
+ "SLACK_BOT_TOKEN (or SLACK_BOT_USER_TOKEN) is required for Slack Web API actions in this service",
590
+ "missing_token"
591
+ );
592
+ }
593
+ function normalizeSlackConversationId(channelId) {
594
+ return parseSlackChannelReferenceId(channelId);
595
+ }
596
+ function getClient() {
597
+ const token = resolveSlackToken();
598
+ let cached = clientsByToken.get(token);
599
+ if (!cached) {
600
+ cached = new WebClient(token, {
601
+ retryConfig: { retries: 0 },
602
+ rejectRateLimitedCalls: true
603
+ });
604
+ clientsByToken.set(token, cached);
605
+ }
606
+ return cached;
607
+ }
608
+ function mapSlackError(error) {
609
+ if (error instanceof SlackActionError) {
610
+ return error;
611
+ }
612
+ const candidate = error;
613
+ const apiError = candidate.data?.error;
614
+ const message = candidate.message ?? "Slack action failed";
615
+ const baseOptions = {
616
+ apiError,
617
+ statusCode: candidate.statusCode,
618
+ requestId: getHeaderString(candidate.headers, "x-slack-req-id"),
619
+ errorData: serializeSlackErrorData(candidate.data),
620
+ ...parseSlackCanvasDetail(candidate.data?.detail)
621
+ };
622
+ if (apiError === "missing_scope") {
623
+ return new SlackActionError(message, "missing_scope", {
624
+ ...baseOptions,
625
+ needed: candidate.data?.needed,
626
+ provided: candidate.data?.provided
627
+ });
628
+ }
629
+ if (apiError === "not_in_channel") {
630
+ return new SlackActionError(message, "not_in_channel", baseOptions);
631
+ }
632
+ if (apiError === "restricted_action_read_only_channel") {
633
+ return new SlackActionError(message, "read_only_channel", baseOptions);
634
+ }
635
+ if (apiError === "already_reacted") {
636
+ return new SlackActionError(message, "already_reacted", baseOptions);
637
+ }
638
+ if (apiError === "no_reaction") {
639
+ return new SlackActionError(message, "no_reaction", baseOptions);
640
+ }
641
+ if (apiError === "invalid_arguments") {
642
+ return new SlackActionError(message, "invalid_arguments", baseOptions);
643
+ }
644
+ if (apiError === "invalid_cursor") {
645
+ return new SlackActionError(message, "invalid_arguments", baseOptions);
646
+ }
647
+ if (apiError === "invalid_name") {
648
+ return new SlackActionError(message, "invalid_arguments", baseOptions);
649
+ }
650
+ if (apiError === "not_found" || apiError === "channel_not_found" || apiError === "message_not_found") {
651
+ return new SlackActionError(message, "not_found", baseOptions);
652
+ }
653
+ if (apiError === "feature_not_enabled" || apiError === "not_allowed_token_type") {
654
+ return new SlackActionError(message, "feature_unavailable", baseOptions);
655
+ }
656
+ if (apiError === "canvas_creation_failed") {
657
+ return new SlackActionError(message, "canvas_creation_failed", baseOptions);
658
+ }
659
+ if (apiError === "canvas_editing_failed") {
660
+ return new SlackActionError(message, "canvas_editing_failed", baseOptions);
661
+ }
662
+ if (candidate.code === "slack_webapi_rate_limited_error" || candidate.statusCode === 429) {
663
+ return new SlackActionError(message, "rate_limited", {
664
+ ...baseOptions,
665
+ retryAfterSeconds: candidate.retryAfter
666
+ });
667
+ }
668
+ return new SlackActionError(message, "internal_error", baseOptions);
669
+ }
670
+ var MAX_RETRY_DELAY_MS = 1e4;
671
+ var MAX_TOTAL_RETRY_DELAY_MS = 2e4;
672
+ var CONNECTION_ERROR_CODES = /* @__PURE__ */ new Set([
673
+ "ECONNREFUSED",
674
+ "ECONNRESET",
675
+ "ENOTFOUND",
676
+ "EAI_AGAIN"
677
+ ]);
678
+ var TIMEOUT_ERROR_CODES = /* @__PURE__ */ new Set([
679
+ "ETIMEDOUT",
680
+ "ECONNABORTED",
681
+ "ESOCKETTIMEDOUT"
682
+ ]);
683
+ function findNetworkErrorCode(error, depth = 0) {
684
+ if (!error || typeof error !== "object" || depth > 4) {
685
+ return void 0;
686
+ }
687
+ const candidate = error;
688
+ if (typeof candidate.code === "string" && (CONNECTION_ERROR_CODES.has(candidate.code) || TIMEOUT_ERROR_CODES.has(candidate.code))) {
689
+ return candidate.code;
690
+ }
691
+ return findNetworkErrorCode(candidate.original, depth + 1) ?? findNetworkErrorCode(candidate.cause, depth + 1);
692
+ }
693
+ function hasSocketHangUpMessage(error) {
694
+ return error instanceof Error && error.message.toLowerCase().includes("socket hang up");
695
+ }
696
+ function classifySlackRetry(raw, mapped) {
697
+ if (mapped.code === "rate_limited") {
698
+ return "rate_limited";
699
+ }
700
+ if (mapped.statusCode !== void 0 && mapped.statusCode >= 500) {
701
+ return "server_error";
702
+ }
703
+ const networkCode = findNetworkErrorCode(raw);
704
+ if (networkCode && CONNECTION_ERROR_CODES.has(networkCode)) {
705
+ return "connection";
706
+ }
707
+ if (networkCode && TIMEOUT_ERROR_CODES.has(networkCode)) {
708
+ return "timeout";
709
+ }
710
+ if (hasSocketHangUpMessage(raw)) {
711
+ return "connection";
712
+ }
713
+ return "none";
714
+ }
715
+ async function withSlackRetries(task, maxAttempts = 3, context = {}) {
716
+ let attempt = 0;
717
+ let totalDelayMs = 0;
718
+ const action = context.action ?? "unknown";
719
+ while (attempt < maxAttempts) {
720
+ attempt += 1;
721
+ const attemptNumber = attempt;
722
+ try {
723
+ return await withSpan(
724
+ `POST slack.com/api/${action}`,
725
+ "http.client",
726
+ {},
727
+ async () => {
728
+ try {
729
+ return await task();
730
+ } catch (error) {
731
+ const mapped = mapSlackError(error);
732
+ const errorAttrs = {
733
+ "error.type": mapped.code
734
+ };
735
+ if (mapped.apiError) {
736
+ errorAttrs["app.slack.api_error_code"] = mapped.apiError;
737
+ }
738
+ if (mapped.code === "rate_limited") {
739
+ errorAttrs["http.response.status_code"] = 429;
740
+ if (mapped.retryAfterSeconds) {
741
+ errorAttrs["app.slack.retry_after_ms"] = mapped.retryAfterSeconds * 1e3;
742
+ }
743
+ } else if (mapped.statusCode != null) {
744
+ errorAttrs["http.response.status_code"] = mapped.statusCode;
745
+ }
746
+ setSpanAttributes(errorAttrs);
747
+ setSpanStatus("error");
748
+ throw error;
749
+ }
750
+ },
751
+ {
752
+ "http.request.method": "POST",
753
+ "server.address": "slack.com",
754
+ "url.scheme": "https",
755
+ "url.path": `/api/${action}`,
756
+ "app.slack.method": action,
757
+ "app.retry.max_attempts": maxAttempts,
758
+ ...attemptNumber > 1 ? { "http.request.resend_count": attemptNumber - 1 } : {},
759
+ ...context.attributes ?? {},
760
+ ...context.spanAttributes ?? {}
761
+ }
762
+ );
763
+ } catch (error) {
764
+ const mapped = mapSlackError(error);
765
+ const retryClass = classifySlackRetry(error, mapped);
766
+ const isRetryable = retryClass === "rate_limited" || retryClass === "connection" || retryClass === "server_error" || retryClass === "timeout" && context.idempotent === true;
767
+ const remainingDelayBudgetMs = MAX_TOTAL_RETRY_DELAY_MS - totalDelayMs;
768
+ const baseLogAttributes = {
769
+ "app.slack.action": action,
770
+ "app.slack.error_code": mapped.code,
771
+ ...mapped.apiError ? { "app.slack.api_error": mapped.apiError } : {},
772
+ ...mapped.detail ? { "app.slack.detail": mapped.detail } : {},
773
+ ...mapped.detailLine !== void 0 ? { "app.slack.detail_line": mapped.detailLine } : {},
774
+ ...mapped.detailRule ? { "app.slack.detail_rule": mapped.detailRule } : {},
775
+ ...mapped.requestId ? { "app.slack.request_id": mapped.requestId } : {},
776
+ ...mapped.statusCode !== void 0 ? { "http.response.status_code": mapped.statusCode } : {},
777
+ ...context.attributes ?? {}
778
+ };
779
+ if (!isRetryable || attempt >= maxAttempts || remainingDelayBudgetMs <= 0) {
780
+ logWarn("slack.action.failed", {
781
+ ...baseLogAttributes,
782
+ ...mapped.errorData ? { "app.slack.error_data": mapped.errorData } : {}
783
+ });
784
+ throw mapped;
785
+ }
786
+ logWarn("slack.action.retrying", {
787
+ ...baseLogAttributes,
788
+ "app.slack.retry_attempt": attempt,
789
+ "app.slack.retry_class": retryClass
790
+ });
791
+ const retryAfterMs = retryClass === "rate_limited" && mapped.retryAfterSeconds && mapped.retryAfterSeconds > 0 ? mapped.retryAfterSeconds * 1e3 : void 0;
792
+ const backoffMs = Math.min(2e3, 250 * 2 ** (attempt - 1));
793
+ const delayMs = Math.min(
794
+ retryAfterMs ?? backoffMs,
795
+ MAX_RETRY_DELAY_MS,
796
+ remainingDelayBudgetMs
797
+ );
798
+ totalDelayMs += delayMs;
799
+ await sleep(delayMs);
800
+ }
801
+ }
802
+ throw new SlackActionError(
803
+ "Slack action exhausted retries",
804
+ "internal_error"
805
+ );
806
+ }
807
+ function getSlackClient() {
808
+ return getClient();
809
+ }
810
+ function isDmChannel(channelId) {
811
+ const normalized = normalizeSlackConversationId(channelId);
812
+ return Boolean(normalized && normalized.startsWith("D"));
813
+ }
814
+ function isConversationScopedChannel(channelId) {
815
+ const normalized = normalizeSlackConversationId(channelId);
816
+ if (!normalized) return false;
817
+ return normalized.startsWith("C") || normalized.startsWith("G") || normalized.startsWith("D");
818
+ }
819
+ function isConversationChannel(channelId) {
820
+ const normalized = normalizeSlackConversationId(channelId);
821
+ if (!normalized) return false;
822
+ return normalized.startsWith("C") || normalized.startsWith("G");
823
+ }
824
+ async function getFilePermalink(fileId) {
825
+ const client = getClient();
826
+ const response = await withSlackRetries(
827
+ () => client.files.info({
828
+ file: fileId
829
+ }),
830
+ 3,
831
+ {
832
+ action: "files.info",
833
+ idempotent: true,
834
+ spanAttributes: { "app.slack.file_id": fileId }
835
+ }
836
+ );
837
+ return response.file?.permalink;
838
+ }
839
+ async function downloadPrivateSlackFile(url) {
840
+ const token = resolveSlackToken();
841
+ return withSpan(
842
+ "GET files.slack.com",
843
+ "http.client",
844
+ {},
845
+ async () => {
846
+ const response = await fetch(url, {
847
+ headers: {
848
+ Authorization: `Bearer ${token}`
849
+ }
850
+ });
851
+ setSpanAttributes({ "http.response.status_code": response.status });
852
+ if (!response.ok) {
853
+ setSpanAttributes({ "error.type": String(response.status) });
854
+ setSpanStatus("error");
855
+ throw new Error(`Slack file download failed: ${response.status}`);
856
+ }
857
+ return Buffer.from(await response.arrayBuffer());
858
+ },
859
+ {
860
+ "http.request.method": "GET",
861
+ "server.address": "files.slack.com",
862
+ "url.scheme": "https",
863
+ "app.slack.method": "files.download"
864
+ }
865
+ );
866
+ }
867
+
439
868
  // src/chat/resource-events/workspace.ts
440
869
  function canRouteResourceEvents() {
441
870
  return Boolean(getSlackBotToken());
@@ -541,6 +970,15 @@ async function resolveViewerUserFromSql(db, email) {
541
970
  async function resolveViewerUser(email) {
542
971
  return await resolveViewerUserFromSql(getDb(), email);
543
972
  }
973
+ async function findUserByEmailFromSql(db, email) {
974
+ const normalizedEmail = normalizeIdentityEmail(email);
975
+ if (!normalizedEmail) return void 0;
976
+ const userRow = (await db.select().from(juniorUsers).where(eq3(juniorUsers.primaryEmailNormalized, normalizedEmail)).limit(1))[0];
977
+ return userRow ? await readUserById(db, userRow) : void 0;
978
+ }
979
+ async function findUserByEmail(email) {
980
+ return await findUserByEmailFromSql(getDb(), email);
981
+ }
544
982
  async function updateViewerDisplayNameFromSql(db, userId, displayName) {
545
983
  const rows = await db.update(juniorUsers).set({ displayName, updatedAt: /* @__PURE__ */ new Date() }).where(eq3(juniorUsers.id, userId)).returning();
546
984
  const userRow = rows[0];
@@ -1000,6 +1438,27 @@ function setPlugins(nextPlugins) {
1000
1438
  function getPlugins() {
1001
1439
  return [...registeredPlugins];
1002
1440
  }
1441
+ function applyPluginFormatMarkdown(text) {
1442
+ let transformed = text;
1443
+ for (const plugin of getPlugins()) {
1444
+ const hook = plugin.hooks?.formatMarkdown;
1445
+ if (!hook) {
1446
+ continue;
1447
+ }
1448
+ try {
1449
+ const next = hook({ text: transformed });
1450
+ if (typeof next === "string") {
1451
+ transformed = next;
1452
+ }
1453
+ } catch (error) {
1454
+ logWarn("plugin.format_markdown.hook.failed", {
1455
+ "app.plugin.name": plugin.manifest.name,
1456
+ "exception.message": safeErrorMessage(error)
1457
+ });
1458
+ }
1459
+ }
1460
+ return transformed;
1461
+ }
1003
1462
  async function getPluginSystemPromptContributions(source) {
1004
1463
  const contributions = [];
1005
1464
  let totalChars = 0;
@@ -1674,6 +2133,41 @@ async function getPluginOperationalReports(nowMs) {
1674
2133
  }
1675
2134
  return reports;
1676
2135
  }
2136
+ async function getPluginProfileReports(args) {
2137
+ const reports = [];
2138
+ for (const plugin of getPlugins()) {
2139
+ const pluginName = plugin.manifest.name;
2140
+ const hook = plugin.hooks?.profileReport;
2141
+ if (!hook) {
2142
+ continue;
2143
+ }
2144
+ try {
2145
+ const state = createPluginState(pluginName);
2146
+ const report = await hook({
2147
+ ...basePluginContext(plugin),
2148
+ nowMs: args.nowMs,
2149
+ state: pluginReadState(state),
2150
+ subject: args.subject,
2151
+ viewer: args.viewer
2152
+ });
2153
+ if (!report) {
2154
+ continue;
2155
+ }
2156
+ reports.push(
2157
+ sanitizeOperationalReport({
2158
+ pluginName,
2159
+ report
2160
+ })
2161
+ );
2162
+ } catch (error) {
2163
+ const log = createPluginLogger(pluginName);
2164
+ log.error("Plugin profile report failed", {
2165
+ error: error instanceof Error ? error.message : String(error)
2166
+ });
2167
+ }
2168
+ }
2169
+ return reports;
2170
+ }
1677
2171
  function normalizeEnv(value) {
1678
2172
  if (!isRecord(value)) {
1679
2173
  return {};
@@ -1826,6 +2320,7 @@ function createPluginHookRunner(input = {}) {
1826
2320
 
1827
2321
  export {
1828
2322
  listConversationAnnotations,
2323
+ listConversationAnnotationsById,
1829
2324
  createPluginConversationEvents,
1830
2325
  renderPluginConversationEvent,
1831
2326
  createPluginLogger,
@@ -1835,10 +2330,23 @@ export {
1835
2330
  setDashboardConversationLinkOptions,
1836
2331
  getDashboardConversationLink,
1837
2332
  getDashboardTaskLink,
2333
+ runWithWorkspaceTeamId,
2334
+ getWorkspaceTeamId,
2335
+ SlackActionError,
2336
+ getHeaderString,
2337
+ runWithSlackInstallationToken,
2338
+ normalizeSlackConversationId,
2339
+ withSlackRetries,
2340
+ getSlackClient,
2341
+ isDmChannel,
2342
+ isConversationScopedChannel,
2343
+ getFilePermalink,
2344
+ downloadPrivateSlackFile,
1838
2345
  canRouteResourceEvents,
1839
2346
  createResourceEventTeamIdResolver,
1840
2347
  getSlackToolContext,
1841
2348
  resolveViewerUser,
2349
+ findUserByEmail,
1842
2350
  updateViewerDisplayName,
1843
2351
  readActorIdentity,
1844
2352
  bindSlackDirectCredentialSubject,
@@ -1852,6 +2360,7 @@ export {
1852
2360
  validatePlugins,
1853
2361
  setPlugins,
1854
2362
  getPlugins,
2363
+ applyPluginFormatMarkdown,
1855
2364
  getPluginSystemPromptContributions,
1856
2365
  getPluginUserPromptContributions,
1857
2366
  getPluginTools,
@@ -1859,5 +2368,6 @@ export {
1859
2368
  getPluginApiRoutes,
1860
2369
  getPluginSlackConversationLink,
1861
2370
  getPluginOperationalReports,
2371
+ getPluginProfileReports,
1862
2372
  createPluginHookRunner
1863
2373
  };
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  pluginCatalogRuntime
3
- } from "./chunk-CNHELV2P.js";
3
+ } from "./chunk-CGAIDDLD.js";
4
4
 
5
5
  // src/chat/plugins/validation.ts
6
6
  import { isDeepStrictEqual } from "util";