@sentry/junior 0.160.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.
- package/dist/{agent-hooks-U6R3O2V4.js → agent-hooks-O75UJNC6.js} +13 -10
- package/dist/api/locations/detail.d.ts +17 -0
- package/dist/api/people/plugin-reports.d.ts +7 -0
- package/dist/api/schema/conversation.d.ts +77 -8
- package/dist/api/schema/location.d.ts +29 -0
- package/dist/api/schema/person.d.ts +29 -0
- package/dist/api/schema.js +1 -1
- package/dist/api.js +128 -27
- package/dist/app.js +73 -42
- package/dist/{catalog-runtime-HIDNHGOP.js → catalog-runtime-Z3FHU4ZY.js} +5 -5
- package/dist/chat/artifacts/store.d.ts +42 -0
- package/dist/chat/plugins/agent-hooks.d.ts +9 -1
- package/dist/chat/plugins/annotations.d.ts +3 -0
- package/dist/chat/plugins/conversation-sidebar.d.ts +3 -0
- package/dist/chat/plugins/viewer.d.ts +4 -0
- package/dist/chat/slack/mrkdwn.d.ts +3 -4
- package/dist/chat/tools/publish-image.d.ts +44 -0
- package/dist/chat/tools/unpublish-image.d.ts +35 -0
- package/dist/{chunk-MBDWMEO6.js → chunk-4WAZ6DGX.js} +10 -10
- package/dist/{chunk-FJR66CQT.js → chunk-6ILVZOF6.js} +4 -4
- package/dist/{chunk-42E7432W.js → chunk-6WTDZKFR.js} +1 -1
- package/dist/{chunk-2SFMRMDV.js → chunk-CGAIDDLD.js} +1 -1
- package/dist/{chunk-XPX3DYG7.js → chunk-DGKLYDX7.js} +2 -2
- package/dist/{chunk-ZNBGWMVQ.js → chunk-DNSIU27S.js} +1 -1
- package/dist/{chunk-TBRJGMTO.js → chunk-EAIQBR66.js} +1 -1
- package/dist/{chunk-4UO3UAW7.js → chunk-FLMD25Y5.js} +239 -199
- package/dist/{chunk-FKMWPA4A.js → chunk-G35DUPNB.js} +4 -4
- package/dist/{chunk-472VVYYV.js → chunk-HSFYTIS2.js} +1 -1
- package/dist/{chunk-LNUZXEEI.js → chunk-IRHX2S46.js} +1 -1
- package/dist/{chunk-TPFOVXRB.js → chunk-IW7C5C7E.js} +19 -21
- package/dist/{chunk-UNDTW2PY.js → chunk-IYFKONIL.js} +1 -1
- package/dist/{chunk-U26KRMYX.js → chunk-MNY5M7RB.js} +537 -27
- package/dist/{chunk-PZUJPPON.js → chunk-MVG5UPLM.js} +1 -1
- package/dist/{chunk-WACYTIIK.js → chunk-O7JTSJPU.js} +6 -6
- package/dist/{chunk-URPLKXLG.js → chunk-OPIJ6HY3.js} +9 -9
- package/dist/{chunk-756F567I.js → chunk-OY2DBUV4.js} +5 -5
- package/dist/{chunk-M5HCOMDR.js → chunk-OYQEO72I.js} +410 -108
- package/dist/{chunk-2MK6S2UA.js → chunk-QHXT7LLF.js} +1 -1
- package/dist/{chunk-3AHIB66R.js → chunk-QQ7SY5IZ.js} +5 -5
- package/dist/{chunk-BGVQGEKB.js → chunk-RULQD7OX.js} +4 -4
- package/dist/{chunk-J77DLAPE.js → chunk-RVENVPNO.js} +2 -2
- package/dist/{chunk-UYUS4L6F.js → chunk-SA6BEEAB.js} +16 -9
- package/dist/{chunk-RLOXMHLA.js → chunk-STZVUVMS.js} +4 -4
- package/dist/{chunk-JQZIUESN.js → chunk-U2XOBFDK.js} +3 -3
- package/dist/{chunk-PVIM2RWO.js → chunk-V67ORYKQ.js} +8 -10
- package/dist/{chunk-P7W3ZL5J.js → chunk-VXFDVY4C.js} +8 -36
- package/dist/{chunk-YPMDNE7R.js → chunk-WKN4ZQTZ.js} +1 -1
- package/dist/{chunk-HDPH4CHJ.js → chunk-WVSQ2SCS.js} +1 -1
- package/dist/{chunk-56TFPTZU.js → chunk-YSWHAOPQ.js} +1 -1
- package/dist/{chunk-RJGRXDA7.js → chunk-ZBCLWBMV.js} +5 -5
- package/dist/cli/chat.js +29 -30
- package/dist/cli/check.js +6 -6
- package/dist/cli/plugins.js +12 -13
- package/dist/cli/snapshot-create.js +9 -9
- package/dist/cli/upgrade.js +7 -7
- package/dist/db/schema/artifacts.d.ts +178 -0
- package/dist/db/schema.d.ts +179 -1
- package/dist/{db-2JKT3BB5.js → db-KKOZRE62.js} +7 -7
- package/dist/handlers/artifacts.d.ts +8 -0
- package/dist/instrumentation.js +4 -4
- package/dist/nitro.js +2 -2
- package/dist/{oauth-callback-server-7MC2UMQC.js → oauth-callback-server-5AUZDWXA.js} +22 -23
- package/dist/{runner-GUU4G7XO.js → runner-K5H5NDEC.js} +12 -13
- package/dist/{skills-7NW5N3VM.js → skills-OZGPB6G4.js} +6 -6
- package/dist/spawn-RCQSC2L5.js +37 -0
- package/dist/{validation-AQLI6N2C.js → validation-TVHCNCYL.js} +6 -6
- package/dist/version.js +1 -1
- package/dist/{work-FLFK5XNU.js → work-DXCQMJ5U.js} +17 -17
- package/dist/worker-24HZ5HRI.js +24 -0
- package/migrations/0028_artifacts.sql +16 -0
- package/migrations/meta/0028_snapshot.json +2685 -0
- package/migrations/meta/_journal.json +8 -1
- package/package.json +4 -4
- package/dist/chunk-OUAZDZTZ.js +0 -441
- package/dist/spawn-WGK4W3XF.js +0 -37
- package/dist/worker-KSJIRBQJ.js +0 -24
|
@@ -1,33 +1,27 @@
|
|
|
1
|
-
import {
|
|
2
|
-
getSlackClient,
|
|
3
|
-
isConversationChannel,
|
|
4
|
-
isConversationScopedChannel,
|
|
5
|
-
isDmChannel,
|
|
6
|
-
normalizeSlackConversationId
|
|
7
|
-
} from "./chunk-OUAZDZTZ.js";
|
|
8
1
|
import {
|
|
9
2
|
getConversationEventStore,
|
|
10
3
|
getDb
|
|
11
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-U2XOBFDK.js";
|
|
12
5
|
import {
|
|
13
6
|
juniorConversationAnnotations,
|
|
14
7
|
juniorConversationEvents,
|
|
15
8
|
juniorIdentities,
|
|
16
9
|
juniorUsers
|
|
17
|
-
} from "./chunk-
|
|
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-
|
|
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-
|
|
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
|
-
|
|
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
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
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
|
};
|