@sentry/junior 0.202.0 → 0.204.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/agent-CMyTD4DD.js +17509 -0
- package/dist/agent-hooks-Bbcp9e33.js +4040 -0
- package/dist/agent-runner-DNb1A49M.js +678 -0
- package/dist/api/schema.d.ts +2728 -2639
- package/dist/api/schema.js +4 -169
- package/dist/api.d.ts +23 -17
- package/dist/api.js +3881 -5326
- package/dist/app-B1TOqBI8.d.ts +246 -0
- package/dist/app.d.ts +4 -520
- package/dist/app.js +9522 -11875
- package/dist/auth-pause-state-DosEalIG.js +168 -0
- package/dist/catalog-runtime-BReos-D4.js +7 -0
- package/dist/checkpoint-CNrPyRJN.js +2450 -0
- package/dist/cli/chat.d.ts +20 -0
- package/dist/cli/chat.js +229 -336
- package/dist/cli/check.d.ts +8 -0
- package/dist/cli/check.js +513 -806
- package/dist/cli/env.d.ts +13 -0
- package/dist/cli/env.js +63 -12
- package/dist/cli/init.d.ts +3 -0
- package/dist/cli/init.js +90 -159
- package/dist/cli/main.d.ts +6 -0
- package/dist/cli/main.js +43 -64
- package/dist/cli/plugins.d.ts +13 -0
- package/dist/cli/plugins.js +148 -227
- package/dist/cli/run.d.ts +16 -0
- package/dist/cli/run.js +52 -8
- package/dist/cli/snapshot-create.d.ts +4 -0
- package/dist/cli/snapshot-create.js +71 -125
- package/dist/cli/upgrade.d.ts +15 -0
- package/dist/cli/upgrade.js +232 -335
- package/dist/config-nGCW1OTG.js +941 -0
- package/dist/context-B45_WqVX.js +298 -0
- package/dist/conversation-privacy-B4voFRSm.js +182 -0
- package/dist/conversation-work-C6y3T5Hx.js +51 -0
- package/dist/credential-sync-kXr1hvO1.js +2660 -0
- package/dist/db-BeyXKJOI.d.ts +5 -0
- package/dist/destinations-BeBiJeEM.js +29 -0
- package/dist/execution-stats-DWfSihcH.js +373 -0
- package/dist/executor-DcMSlI5c.js +721 -0
- package/dist/experimental-CIXf7Uye.js +46 -0
- package/dist/history-ClE4toxe.d.ts +32 -0
- package/dist/history-CqTuSAdI.js +441 -0
- package/dist/instrumentation.d.ts +3 -3
- package/dist/instrumentation.js +130 -176
- package/dist/job-queue-Drn_LXlM.js +63 -0
- package/dist/logging-BzAk1S_s.js +1210 -0
- package/dist/model-profile-CZmVoGdX.js +49 -0
- package/dist/nitro.d.ts +31 -36
- package/dist/nitro.js +275 -456
- package/dist/oauth-callback-server-JiftokvA.js +130 -0
- package/dist/oauth-relay-CdCnu80W.js +62 -0
- package/dist/package-discovery-7a9YqLhE.js +455 -0
- package/dist/pending-auth-CWmSdZVg.js +185 -0
- package/dist/plugin-module-BKGOXca2.js +81 -0
- package/dist/plugins-BI9A1vKA.d.ts +95 -0
- package/dist/plugins-egKWjgue.js +99 -0
- package/dist/private-trace-result-BLvBjy_e.js +16 -0
- package/dist/profile-CrT5nXbw.js +861 -0
- package/dist/registry-DH1xUo2N.js +1419 -0
- package/dist/reporting-schema-DosTN__Q.js +105 -0
- package/dist/resolve-CZHnfxaS.js +546 -0
- package/dist/rolldown-runtime-B4FMCO8f.js +28 -0
- package/dist/runner-CZV2sC46.js +392 -0
- package/dist/sandbox-egress-signals-bHZfmhbd.js +46 -0
- package/dist/schema-Rn8-XEaN.js +1140 -0
- package/dist/sentry-CD3SdJl-.js +26 -0
- package/dist/skills-CIcDQSgM.js +316 -0
- package/dist/spawn-DdkUFRmq.js +29 -0
- package/dist/stats-DTUbAb88.js +2634 -0
- package/dist/store-C5qxF2ac.d.ts +208 -0
- package/dist/task-queue-CJIR4RF7.js +136 -0
- package/dist/task-runner-D-O3A-Cp.js +639 -0
- package/dist/thread-state-CRc9jTJS.js +431 -0
- package/dist/turn-execution-BkinIdJF.js +47 -0
- package/dist/turn-lifecycle-DQV19VTW.js +54 -0
- package/dist/usage-D3EbSO7w.js +296 -0
- package/dist/usage-schema-CKaDkH_j.js +20 -0
- package/dist/validation-C2CR-OX3.js +40 -0
- package/dist/vercel-queue-DIkVcfK4.js +167 -0
- package/dist/vercel.d.ts +6 -6
- package/dist/vercel.js +14 -6
- package/dist/version-CnXZ614E.d.ts +5 -0
- package/dist/version-DxcWdoKa.js +8 -0
- package/dist/version.d.ts +2 -4
- package/dist/version.js +2 -6
- package/dist/web-authorization-RMBxAR_t.js +76 -0
- package/dist/web-input-3EPfpf55.js +494 -0
- package/dist/work-fGofI7Op.js +509 -0
- package/dist/worker-CsOLq-mR.js +625 -0
- package/package.json +9 -9
- package/dist/agent-hooks-UXITLYOK.js +0 -51
- package/dist/catalog-runtime-BG3JLLK2.js +0 -14
- package/dist/chunk-2LYHWMR4.js +0 -46
- package/dist/chunk-2MK3QBGA.js +0 -321
- package/dist/chunk-3PGQSKJV.js +0 -692
- package/dist/chunk-3UI3WFTX.js +0 -519
- package/dist/chunk-3Y47VMHV.js +0 -6
- package/dist/chunk-4UO6FK4G.js +0 -64
- package/dist/chunk-5NOPO64K.js +0 -198
- package/dist/chunk-5PKWYPFT.js +0 -2943
- package/dist/chunk-63NKKFJL.js +0 -1228
- package/dist/chunk-6AYH6252.js +0 -787
- package/dist/chunk-6Y5B5PEX.js +0 -80
- package/dist/chunk-7DHZP5KJ.js +0 -333
- package/dist/chunk-7H2VZTWN.js +0 -103
- package/dist/chunk-7NL7T6VU.js +0 -406
- package/dist/chunk-A76OIMYN.js +0 -102
- package/dist/chunk-AWMS3SPP.js +0 -58
- package/dist/chunk-B3LY5RCL.js +0 -229
- package/dist/chunk-BD2KT3OB.js +0 -36
- package/dist/chunk-BXCX5QKA.js +0 -2086
- package/dist/chunk-C6VVF43U.js +0 -81
- package/dist/chunk-CEMMLV5L.js +0 -98
- package/dist/chunk-CM5HNV2W.js +0 -131
- package/dist/chunk-DA2KGZZI.js +0 -13
- package/dist/chunk-DBVY6HLX.js +0 -64
- package/dist/chunk-DIB4GX3G.js +0 -8
- package/dist/chunk-DKIJ2RP5.js +0 -68
- package/dist/chunk-DL2KLKVH.js +0 -931
- package/dist/chunk-DWPN6SCH.js +0 -172
- package/dist/chunk-ELWOYVOY.js +0 -206
- package/dist/chunk-EWT7H65X.js +0 -506
- package/dist/chunk-FA6POGLH.js +0 -70
- package/dist/chunk-HMREVIO2.js +0 -135
- package/dist/chunk-IIWUT4FO.js +0 -820
- package/dist/chunk-JAL3CH4G.js +0 -27
- package/dist/chunk-JSJQEWBJ.js +0 -3295
- package/dist/chunk-KHKKHDLD.js +0 -387
- package/dist/chunk-LA6R4A7Q.js +0 -706
- package/dist/chunk-LOOU2DUR.js +0 -20428
- package/dist/chunk-M6FHWXE3.js +0 -700
- package/dist/chunk-M76HMB7M.js +0 -1856
- package/dist/chunk-MAGNK5J4.js +0 -1520
- package/dist/chunk-MTM5IFCY.js +0 -1190
- package/dist/chunk-N7TMSOZH.js +0 -235
- package/dist/chunk-NHJU2ZAL.js +0 -56
- package/dist/chunk-NSXKRGRT.js +0 -717
- package/dist/chunk-OWMPFXJA.js +0 -1764
- package/dist/chunk-PDO5BLNM.js +0 -22
- package/dist/chunk-PEQXYSSE.js +0 -45
- package/dist/chunk-PW4P4JYF.js +0 -1135
- package/dist/chunk-QB54UQUS.js +0 -320
- package/dist/chunk-QP3OGJXF.js +0 -105
- package/dist/chunk-RC745GGZ.js +0 -122
- package/dist/chunk-REOIRYPR.js +0 -37
- package/dist/chunk-SCOZRLZ4.js +0 -164
- package/dist/chunk-TXJSXBV5.js +0 -1433
- package/dist/chunk-TXKUKMVO.js +0 -417
- package/dist/chunk-U2GPUYAL.js +0 -3656
- package/dist/chunk-UIE3R5XU.js +0 -17
- package/dist/chunk-UXPG6ZIN.js +0 -84
- package/dist/chunk-VFUK3X5B.js +0 -50
- package/dist/chunk-WQSHDTTI.js +0 -1163
- package/dist/chunk-X6GDDWQH.js +0 -60
- package/dist/chunk-Y2CM7HXH.js +0 -111
- package/dist/chunk-Y7X25LFY.js +0 -15
- package/dist/chunk-YDUR3ZYW.js +0 -10
- package/dist/conversation-work-VIOQ5ZF3.js +0 -54
- package/dist/db-3L2MCVRI.js +0 -26
- package/dist/discovery-DWVDM7UK.js +0 -44
- package/dist/experimental-P5FZM4CU.js +0 -12
- package/dist/oauth-callback-server-YVV6C6T2.js +0 -243
- package/dist/oauth-relay-5XPVRPYB.js +0 -8
- package/dist/plugins-YTYJMMJF.js +0 -14
- package/dist/runner-LQ7RETDP.js +0 -522
- package/dist/sandbox-egress-signals-FIL7EGPR.js +0 -73
- package/dist/sentry-MB3NL3IR.js +0 -42
- package/dist/skills-XRWYEDCD.js +0 -27
- package/dist/spawn-RC6SJUAI.js +0 -42
- package/dist/task-runner-7DY7ARYH.js +0 -36
- package/dist/validation-7MNQGCJA.js +0 -17
- package/dist/work-COALHUCG.js +0 -51
- package/dist/worker-6MURZIPD.js +0 -24
package/dist/chunk-TXJSXBV5.js
DELETED
|
@@ -1,1433 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
ToolInputError
|
|
3
|
-
} from "./chunk-7DHZP5KJ.js";
|
|
4
|
-
import {
|
|
5
|
-
getPlugins
|
|
6
|
-
} from "./chunk-U2GPUYAL.js";
|
|
7
|
-
import {
|
|
8
|
-
getDb
|
|
9
|
-
} from "./chunk-M76HMB7M.js";
|
|
10
|
-
import {
|
|
11
|
-
juniorAttachments,
|
|
12
|
-
juniorConversations,
|
|
13
|
-
juniorEventTasks,
|
|
14
|
-
juniorSchedulerRuns,
|
|
15
|
-
juniorSchedulerTasks,
|
|
16
|
-
juniorStats
|
|
17
|
-
} from "./chunk-PW4P4JYF.js";
|
|
18
|
-
import {
|
|
19
|
-
juniorDestinations
|
|
20
|
-
} from "./chunk-VFUK3X5B.js";
|
|
21
|
-
|
|
22
|
-
// src/chat/event-tasks/store.ts
|
|
23
|
-
import {
|
|
24
|
-
resourceEventMatches
|
|
25
|
-
} from "@sentry/junior-plugin-api";
|
|
26
|
-
import { and, asc, desc, eq, inArray, ne, or, sql } from "drizzle-orm";
|
|
27
|
-
|
|
28
|
-
// src/chat/event-tasks/types.ts
|
|
29
|
-
import {
|
|
30
|
-
actorUserIdSchema,
|
|
31
|
-
destinationVisibilitySchema,
|
|
32
|
-
resourceEventMatchSchema,
|
|
33
|
-
resourceEventTypeSchema,
|
|
34
|
-
slackDestinationSchema
|
|
35
|
-
} from "@sentry/junior-plugin-api";
|
|
36
|
-
import { z } from "zod";
|
|
37
|
-
var EVENT_TASK_IDENTIFIER_MAX_LENGTH = 300;
|
|
38
|
-
var eventTaskPrincipalSchema = z.object({
|
|
39
|
-
slackUserId: actorUserIdSchema,
|
|
40
|
-
fullName: z.string().optional(),
|
|
41
|
-
userName: z.string().optional()
|
|
42
|
-
}).strict();
|
|
43
|
-
var eventTaskTriggerSchema = z.object({
|
|
44
|
-
events: z.array(resourceEventTypeSchema).min(1),
|
|
45
|
-
label: z.string().min(1),
|
|
46
|
-
match: resourceEventMatchSchema.optional(),
|
|
47
|
-
namespace: z.string().min(1),
|
|
48
|
-
identifier: z.string().min(1).max(EVENT_TASK_IDENTIFIER_MAX_LENGTH),
|
|
49
|
-
resourceType: z.string().min(1)
|
|
50
|
-
}).strict();
|
|
51
|
-
var eventTaskSchema = z.object({
|
|
52
|
-
id: z.string().min(1),
|
|
53
|
-
createdAtMs: z.number().finite(),
|
|
54
|
-
createdBy: eventTaskPrincipalSchema,
|
|
55
|
-
credentialMode: z.enum(["system", "creator"]),
|
|
56
|
-
destination: slackDestinationSchema,
|
|
57
|
-
destinationVisibility: destinationVisibilitySchema,
|
|
58
|
-
task: z.object({ text: z.string().min(1) }).strict(),
|
|
59
|
-
trigger: eventTaskTriggerSchema
|
|
60
|
-
}).strict();
|
|
61
|
-
|
|
62
|
-
// src/chat/event-tasks/store.ts
|
|
63
|
-
function eventTaskJsonPayload(task) {
|
|
64
|
-
const {
|
|
65
|
-
status: _status,
|
|
66
|
-
title: _title,
|
|
67
|
-
...payload
|
|
68
|
-
} = task;
|
|
69
|
-
return payload;
|
|
70
|
-
}
|
|
71
|
-
function parseTask(row) {
|
|
72
|
-
const raw = row.task && typeof row.task === "object" ? { ...row.task } : row.task;
|
|
73
|
-
if (raw && typeof raw === "object" && "title" in raw) {
|
|
74
|
-
delete raw.title;
|
|
75
|
-
}
|
|
76
|
-
const payload = eventTaskSchema.parse(raw);
|
|
77
|
-
const title = row.title?.trim();
|
|
78
|
-
return {
|
|
79
|
-
...payload,
|
|
80
|
-
status: row.status === "deleted" ? "deleted" : "active",
|
|
81
|
-
...title ? { title } : void 0
|
|
82
|
-
};
|
|
83
|
-
}
|
|
84
|
-
function activeEventTaskWhere() {
|
|
85
|
-
return ne(juniorEventTasks.status, "deleted");
|
|
86
|
-
}
|
|
87
|
-
async function getEventTask(db, id) {
|
|
88
|
-
const rows = await db.select({
|
|
89
|
-
status: juniorEventTasks.status,
|
|
90
|
-
task: juniorEventTasks.task,
|
|
91
|
-
title: juniorEventTasks.title
|
|
92
|
-
}).from(juniorEventTasks).where(eq(juniorEventTasks.id, id)).limit(1);
|
|
93
|
-
return rows[0] ? parseTask(rows[0]) : void 0;
|
|
94
|
-
}
|
|
95
|
-
async function createEventTask(db, task) {
|
|
96
|
-
const parsed = eventTaskSchema.parse(eventTaskJsonPayload(task));
|
|
97
|
-
const title = task.title?.trim() || null;
|
|
98
|
-
await db.insert(juniorEventTasks).values({
|
|
99
|
-
id: parsed.id,
|
|
100
|
-
teamId: parsed.destination.teamId,
|
|
101
|
-
namespace: parsed.trigger.namespace,
|
|
102
|
-
identifier: parsed.trigger.identifier,
|
|
103
|
-
createdAtMs: parsed.createdAtMs,
|
|
104
|
-
status: "active",
|
|
105
|
-
title,
|
|
106
|
-
task: parsed
|
|
107
|
-
}).onConflictDoNothing();
|
|
108
|
-
const existing = await getEventTask(db, parsed.id);
|
|
109
|
-
if (!existing) {
|
|
110
|
-
return {
|
|
111
|
-
...parsed,
|
|
112
|
-
status: "active",
|
|
113
|
-
...title ? { title } : void 0
|
|
114
|
-
};
|
|
115
|
-
}
|
|
116
|
-
if (existing.status !== "deleted") {
|
|
117
|
-
return existing;
|
|
118
|
-
}
|
|
119
|
-
const rows = await db.update(juniorEventTasks).set({
|
|
120
|
-
teamId: parsed.destination.teamId,
|
|
121
|
-
namespace: parsed.trigger.namespace,
|
|
122
|
-
identifier: parsed.trigger.identifier,
|
|
123
|
-
status: "active",
|
|
124
|
-
title,
|
|
125
|
-
task: parsed
|
|
126
|
-
}).where(
|
|
127
|
-
and(
|
|
128
|
-
eq(juniorEventTasks.id, parsed.id),
|
|
129
|
-
eq(juniorEventTasks.status, "deleted")
|
|
130
|
-
)
|
|
131
|
-
).returning({
|
|
132
|
-
status: juniorEventTasks.status,
|
|
133
|
-
task: juniorEventTasks.task,
|
|
134
|
-
title: juniorEventTasks.title
|
|
135
|
-
});
|
|
136
|
-
return rows[0] ? parseTask(rows[0]) : await getEventTask(db, parsed.id) ?? {
|
|
137
|
-
...parsed,
|
|
138
|
-
status: "active",
|
|
139
|
-
...title ? { title } : void 0
|
|
140
|
-
};
|
|
141
|
-
}
|
|
142
|
-
async function saveEventTask(db, task) {
|
|
143
|
-
const parsed = eventTaskSchema.parse(eventTaskJsonPayload(task));
|
|
144
|
-
const title = task.title?.trim() || null;
|
|
145
|
-
const rows = await db.update(juniorEventTasks).set({
|
|
146
|
-
namespace: parsed.trigger.namespace,
|
|
147
|
-
identifier: parsed.trigger.identifier,
|
|
148
|
-
title,
|
|
149
|
-
task: parsed
|
|
150
|
-
}).where(and(eq(juniorEventTasks.id, parsed.id), activeEventTaskWhere())).returning({
|
|
151
|
-
status: juniorEventTasks.status,
|
|
152
|
-
task: juniorEventTasks.task,
|
|
153
|
-
title: juniorEventTasks.title
|
|
154
|
-
});
|
|
155
|
-
return rows[0] ? parseTask(rows[0]) : void 0;
|
|
156
|
-
}
|
|
157
|
-
async function deleteEventTask(db, id) {
|
|
158
|
-
const rows = await db.update(juniorEventTasks).set({ status: "deleted" }).where(and(eq(juniorEventTasks.id, id), activeEventTaskWhere())).returning({
|
|
159
|
-
status: juniorEventTasks.status,
|
|
160
|
-
task: juniorEventTasks.task,
|
|
161
|
-
title: juniorEventTasks.title
|
|
162
|
-
});
|
|
163
|
-
return rows[0] ? parseTask(rows[0]) : void 0;
|
|
164
|
-
}
|
|
165
|
-
async function listEventTasksForTeam(db, teamId) {
|
|
166
|
-
const rows = await db.select({
|
|
167
|
-
status: juniorEventTasks.status,
|
|
168
|
-
task: juniorEventTasks.task,
|
|
169
|
-
title: juniorEventTasks.title
|
|
170
|
-
}).from(juniorEventTasks).where(and(eq(juniorEventTasks.teamId, teamId), activeEventTaskWhere())).orderBy(asc(juniorEventTasks.createdAtMs), asc(juniorEventTasks.id));
|
|
171
|
-
return rows.map(parseTask);
|
|
172
|
-
}
|
|
173
|
-
function viewerSlackIdentities(user) {
|
|
174
|
-
return user.identities.filter(
|
|
175
|
-
(identity) => identity.provider === "slack" && Boolean(identity.providerTenantId) && Boolean(identity.providerSubjectId)
|
|
176
|
-
);
|
|
177
|
-
}
|
|
178
|
-
function eventTaskBelongsToUser(task, user) {
|
|
179
|
-
return viewerSlackIdentities(user).some(
|
|
180
|
-
(identity) => identity.providerTenantId === task.destination.teamId && identity.providerSubjectId === task.createdBy.slackUserId
|
|
181
|
-
);
|
|
182
|
-
}
|
|
183
|
-
async function listEventTasksCreatedBy(db, user, limit) {
|
|
184
|
-
const identities = viewerSlackIdentities(user);
|
|
185
|
-
const ownership = or(
|
|
186
|
-
...identities.map(
|
|
187
|
-
(identity) => and(
|
|
188
|
-
eq(juniorEventTasks.teamId, identity.providerTenantId),
|
|
189
|
-
sql`${juniorEventTasks.task}->'createdBy'->>'slackUserId' = ${identity.providerSubjectId}`
|
|
190
|
-
)
|
|
191
|
-
)
|
|
192
|
-
);
|
|
193
|
-
if (!ownership) return [];
|
|
194
|
-
const rows = await db.select({
|
|
195
|
-
status: juniorEventTasks.status,
|
|
196
|
-
task: juniorEventTasks.task,
|
|
197
|
-
title: juniorEventTasks.title
|
|
198
|
-
}).from(juniorEventTasks).where(and(ownership, activeEventTaskWhere())).orderBy(desc(juniorEventTasks.createdAtMs), desc(juniorEventTasks.id)).limit(limit);
|
|
199
|
-
return rows.map(parseTask);
|
|
200
|
-
}
|
|
201
|
-
async function listDeletedEventTasksCreatedBy(db, user, limit) {
|
|
202
|
-
const identities = viewerSlackIdentities(user);
|
|
203
|
-
const ownership = or(
|
|
204
|
-
...identities.map(
|
|
205
|
-
(identity) => and(
|
|
206
|
-
eq(juniorEventTasks.teamId, identity.providerTenantId),
|
|
207
|
-
sql`${juniorEventTasks.task}->'createdBy'->>'slackUserId' = ${identity.providerSubjectId}`
|
|
208
|
-
)
|
|
209
|
-
)
|
|
210
|
-
);
|
|
211
|
-
if (!ownership) return [];
|
|
212
|
-
const rows = await db.select({
|
|
213
|
-
status: juniorEventTasks.status,
|
|
214
|
-
task: juniorEventTasks.task,
|
|
215
|
-
title: juniorEventTasks.title
|
|
216
|
-
}).from(juniorEventTasks).where(and(ownership, eq(juniorEventTasks.status, "deleted"))).orderBy(desc(juniorEventTasks.createdAtMs), desc(juniorEventTasks.id)).limit(limit);
|
|
217
|
-
return rows.map(parseTask);
|
|
218
|
-
}
|
|
219
|
-
async function listPublicEventTasksForTeams(db, teamIds, limit) {
|
|
220
|
-
if (teamIds.length === 0) return [];
|
|
221
|
-
const rows = await db.select({
|
|
222
|
-
status: juniorEventTasks.status,
|
|
223
|
-
task: juniorEventTasks.task,
|
|
224
|
-
title: juniorEventTasks.title
|
|
225
|
-
}).from(juniorEventTasks).innerJoin(
|
|
226
|
-
juniorDestinations,
|
|
227
|
-
and(
|
|
228
|
-
eq(juniorDestinations.provider, "slack"),
|
|
229
|
-
eq(juniorDestinations.providerTenantId, juniorEventTasks.teamId),
|
|
230
|
-
sql`${juniorDestinations.providerDestinationId} = ${juniorEventTasks.task}->'destination'->>'channelId'`
|
|
231
|
-
)
|
|
232
|
-
).where(
|
|
233
|
-
and(
|
|
234
|
-
inArray(juniorEventTasks.teamId, teamIds),
|
|
235
|
-
activeEventTaskWhere(),
|
|
236
|
-
eq(juniorDestinations.visibility, "public")
|
|
237
|
-
)
|
|
238
|
-
).orderBy(desc(juniorEventTasks.createdAtMs), desc(juniorEventTasks.id)).limit(limit);
|
|
239
|
-
return rows.map(parseTask);
|
|
240
|
-
}
|
|
241
|
-
async function collectEventTaskMatchKeys(db, input) {
|
|
242
|
-
const eventTypes = new Set(
|
|
243
|
-
input.eventTypes.map((eventType) => eventType.trim()).filter(Boolean)
|
|
244
|
-
);
|
|
245
|
-
const identifiers = [
|
|
246
|
-
...new Set(input.identifiers.map((value) => value.trim()).filter(Boolean))
|
|
247
|
-
];
|
|
248
|
-
if (eventTypes.size === 0 || identifiers.length === 0) return [];
|
|
249
|
-
const rows = await db.select({
|
|
250
|
-
status: juniorEventTasks.status,
|
|
251
|
-
task: juniorEventTasks.task,
|
|
252
|
-
title: juniorEventTasks.title
|
|
253
|
-
}).from(juniorEventTasks).where(
|
|
254
|
-
and(
|
|
255
|
-
eq(juniorEventTasks.teamId, input.teamId),
|
|
256
|
-
eq(juniorEventTasks.namespace, input.namespace),
|
|
257
|
-
inArray(juniorEventTasks.identifier, identifiers),
|
|
258
|
-
activeEventTaskWhere()
|
|
259
|
-
)
|
|
260
|
-
).orderBy(asc(juniorEventTasks.createdAtMs), asc(juniorEventTasks.id));
|
|
261
|
-
const keys = /* @__PURE__ */ new Set();
|
|
262
|
-
for (const row of rows) {
|
|
263
|
-
const task = parseTask(row);
|
|
264
|
-
if (!task.trigger.events.some((eventType) => eventTypes.has(eventType))) {
|
|
265
|
-
continue;
|
|
266
|
-
}
|
|
267
|
-
for (const key of Object.keys(task.trigger.match ?? {})) {
|
|
268
|
-
keys.add(key);
|
|
269
|
-
}
|
|
270
|
-
}
|
|
271
|
-
return [...keys].sort();
|
|
272
|
-
}
|
|
273
|
-
async function findMatchingEventTasks(db, event, teamId) {
|
|
274
|
-
const rows = await db.select({
|
|
275
|
-
status: juniorEventTasks.status,
|
|
276
|
-
task: juniorEventTasks.task,
|
|
277
|
-
title: juniorEventTasks.title
|
|
278
|
-
}).from(juniorEventTasks).where(
|
|
279
|
-
and(
|
|
280
|
-
eq(juniorEventTasks.teamId, teamId),
|
|
281
|
-
eq(juniorEventTasks.namespace, event.namespace),
|
|
282
|
-
eq(juniorEventTasks.identifier, event.identifier),
|
|
283
|
-
activeEventTaskWhere()
|
|
284
|
-
)
|
|
285
|
-
).orderBy(asc(juniorEventTasks.createdAtMs), asc(juniorEventTasks.id));
|
|
286
|
-
return rows.map(parseTask).filter(
|
|
287
|
-
(task) => task.trigger.events.includes(event.eventType) && resourceEventMatches(task.trigger.match, event.data)
|
|
288
|
-
);
|
|
289
|
-
}
|
|
290
|
-
|
|
291
|
-
// src/stats.ts
|
|
292
|
-
import { and as and2, asc as asc2, gte, lte, sql as sql2 } from "drizzle-orm";
|
|
293
|
-
function utcDate(nowMs) {
|
|
294
|
-
return new Date(nowMs).toISOString().slice(0, 10);
|
|
295
|
-
}
|
|
296
|
-
async function incrementStat(key, options = {}) {
|
|
297
|
-
const date = utcDate(options.nowMs ?? Date.now());
|
|
298
|
-
await getDb().insert(juniorStats).values({ ...key, date, count: 1 }).onConflictDoUpdate({
|
|
299
|
-
target: [
|
|
300
|
-
juniorStats.date,
|
|
301
|
-
juniorStats.namespace,
|
|
302
|
-
juniorStats.metric,
|
|
303
|
-
juniorStats.name
|
|
304
|
-
],
|
|
305
|
-
set: { count: sql2`${juniorStats.count} + 1` }
|
|
306
|
-
});
|
|
307
|
-
}
|
|
308
|
-
async function readStats(start, end) {
|
|
309
|
-
return await getDb().select().from(juniorStats).where(and2(gte(juniorStats.date, start), lte(juniorStats.date, end))).orderBy(
|
|
310
|
-
asc2(juniorStats.date),
|
|
311
|
-
asc2(juniorStats.namespace),
|
|
312
|
-
asc2(juniorStats.metric),
|
|
313
|
-
asc2(juniorStats.name)
|
|
314
|
-
);
|
|
315
|
-
}
|
|
316
|
-
|
|
317
|
-
// src/chat/tool-support/structured-result.ts
|
|
318
|
-
import { z as z2 } from "zod";
|
|
319
|
-
var juniorToolContinuationSchema = z2.object({
|
|
320
|
-
arguments: z2.record(z2.string(), z2.unknown()),
|
|
321
|
-
reason: z2.string().min(1).optional()
|
|
322
|
-
}).strict();
|
|
323
|
-
var juniorToolBoundContinuationSchema = juniorToolContinuationSchema.extend({
|
|
324
|
-
tool_name: z2.string().min(1)
|
|
325
|
-
});
|
|
326
|
-
var juniorToolOutputSchema = z2.object({
|
|
327
|
-
target: z2.string().min(1).optional(),
|
|
328
|
-
truncated: z2.boolean().optional(),
|
|
329
|
-
/** True when this attempt did not finish before its time budget. */
|
|
330
|
-
timed_out: z2.boolean().optional(),
|
|
331
|
-
continuation: juniorToolContinuationSchema.optional()
|
|
332
|
-
}).passthrough();
|
|
333
|
-
function sortJsonValue(value) {
|
|
334
|
-
if (Array.isArray(value)) {
|
|
335
|
-
return value.map(sortJsonValue);
|
|
336
|
-
}
|
|
337
|
-
if (!value || typeof value !== "object") {
|
|
338
|
-
return value;
|
|
339
|
-
}
|
|
340
|
-
return Object.fromEntries(
|
|
341
|
-
Object.entries(value).filter(([, item]) => item !== void 0).sort(([left], [right]) => left.localeCompare(right)).map(([key, item]) => [key, sortJsonValue(item)])
|
|
342
|
-
);
|
|
343
|
-
}
|
|
344
|
-
function isRecord(value) {
|
|
345
|
-
return Boolean(value && typeof value === "object" && !Array.isArray(value));
|
|
346
|
-
}
|
|
347
|
-
function injectContinuationToolName(details, toolName) {
|
|
348
|
-
if (!isRecord(details) || !isRecord(details.continuation)) {
|
|
349
|
-
return details;
|
|
350
|
-
}
|
|
351
|
-
return {
|
|
352
|
-
...details,
|
|
353
|
-
continuation: juniorToolBoundContinuationSchema.parse({
|
|
354
|
-
...details.continuation,
|
|
355
|
-
tool_name: toolName
|
|
356
|
-
})
|
|
357
|
-
};
|
|
358
|
-
}
|
|
359
|
-
function makeStructuredToolOutput(details, options = {}) {
|
|
360
|
-
return {
|
|
361
|
-
content: [
|
|
362
|
-
{
|
|
363
|
-
type: "text",
|
|
364
|
-
text: JSON.stringify(sortJsonValue(details))
|
|
365
|
-
},
|
|
366
|
-
...options.content ?? []
|
|
367
|
-
],
|
|
368
|
-
details
|
|
369
|
-
};
|
|
370
|
-
}
|
|
371
|
-
|
|
372
|
-
// src/chat/resource-events/catalog.ts
|
|
373
|
-
import {
|
|
374
|
-
normalizeResourceEventIdentifier,
|
|
375
|
-
resourceEventMatchSchema as resourceEventMatchSchema2
|
|
376
|
-
} from "@sentry/junior-plugin-api";
|
|
377
|
-
import { z as z3 } from "zod";
|
|
378
|
-
var CORE_RESOURCE_EVENT_NAMESPACE = "junior";
|
|
379
|
-
function hasResourceEventCatalogEntries(catalog) {
|
|
380
|
-
return Object.keys(catalog).length > 0;
|
|
381
|
-
}
|
|
382
|
-
function hasPluginResourceEventCatalogEntries(catalog) {
|
|
383
|
-
return Object.keys(pluginResourceEventCatalog(catalog)).length > 0;
|
|
384
|
-
}
|
|
385
|
-
function pluginResourceEventCatalog(catalog) {
|
|
386
|
-
return Object.fromEntries(
|
|
387
|
-
Object.entries(catalog).filter(
|
|
388
|
-
([namespace]) => namespace !== CORE_RESOURCE_EVENT_NAMESPACE
|
|
389
|
-
)
|
|
390
|
-
);
|
|
391
|
-
}
|
|
392
|
-
function enumSchema(values, unavailableMessage) {
|
|
393
|
-
if (values.length === 0) {
|
|
394
|
-
return z3.string().refine(() => false, unavailableMessage);
|
|
395
|
-
}
|
|
396
|
-
return z3.enum(values);
|
|
397
|
-
}
|
|
398
|
-
function eventNamespaceSchema(catalog) {
|
|
399
|
-
return enumSchema(
|
|
400
|
-
Object.keys(catalog).sort(),
|
|
401
|
-
"No resource event namespaces are enabled."
|
|
402
|
-
);
|
|
403
|
-
}
|
|
404
|
-
function registeredEventTypeSchema(catalog) {
|
|
405
|
-
return enumSchema(
|
|
406
|
-
[
|
|
407
|
-
...new Set(
|
|
408
|
-
Object.values(catalog).flatMap(
|
|
409
|
-
(registration) => registration.resourceTypes.flatMap(
|
|
410
|
-
(resourceType) => resourceType.supportedEvents
|
|
411
|
-
)
|
|
412
|
-
)
|
|
413
|
-
)
|
|
414
|
-
].sort(),
|
|
415
|
-
"No resource event types are enabled."
|
|
416
|
-
);
|
|
417
|
-
}
|
|
418
|
-
function registeredResourceTypeSchema(catalog) {
|
|
419
|
-
return enumSchema(
|
|
420
|
-
[
|
|
421
|
-
...new Set(
|
|
422
|
-
Object.values(catalog).flatMap(
|
|
423
|
-
(registration) => registration.resourceTypes.map((resourceType) => resourceType.type)
|
|
424
|
-
)
|
|
425
|
-
)
|
|
426
|
-
].sort(),
|
|
427
|
-
"No resource types are enabled."
|
|
428
|
-
);
|
|
429
|
-
}
|
|
430
|
-
function pluginSupportsEvent(catalog, namespace, resourceType, eventType) {
|
|
431
|
-
return catalog[namespace]?.resourceTypes.find((candidate) => candidate.type === resourceType)?.supportedEvents.includes(eventType) ?? false;
|
|
432
|
-
}
|
|
433
|
-
function resourceEventGuidance(catalog, namespace, resourceType, eventType) {
|
|
434
|
-
return catalog[namespace]?.resourceTypes.find(
|
|
435
|
-
(candidate) => candidate.type === resourceType
|
|
436
|
-
)?.guidance?.[eventType];
|
|
437
|
-
}
|
|
438
|
-
function resourceEventMatchFields(catalog, namespace, resourceType) {
|
|
439
|
-
return catalog[namespace]?.resourceTypes.find(
|
|
440
|
-
(candidate) => candidate.type === resourceType
|
|
441
|
-
)?.matchFields;
|
|
442
|
-
}
|
|
443
|
-
function registeredResourceEventMatchSchema() {
|
|
444
|
-
return resourceEventMatchSchema2.optional().describe(
|
|
445
|
-
"Optional exact values from the resource type matchFields. A single value must equal the event data. A list means the event value may be any one entry. Omit a key to ignore it. Unmatched events are dropped with no agent turn."
|
|
446
|
-
);
|
|
447
|
-
}
|
|
448
|
-
function requireSupportedResourceEventMatch(catalog, input) {
|
|
449
|
-
const match = input.match;
|
|
450
|
-
if (!match || Object.keys(match).length === 0) return void 0;
|
|
451
|
-
const fields = resourceEventMatchFields(
|
|
452
|
-
catalog,
|
|
453
|
-
input.namespace,
|
|
454
|
-
input.resourceType
|
|
455
|
-
);
|
|
456
|
-
if (!fields || Object.keys(fields).length === 0) {
|
|
457
|
-
throw new ToolInputError(
|
|
458
|
-
`Resource type "${input.namespace}:${input.resourceType}" does not support match.`
|
|
459
|
-
);
|
|
460
|
-
}
|
|
461
|
-
const normalized = {};
|
|
462
|
-
for (const [key, value] of Object.entries(match)) {
|
|
463
|
-
const field = fields[key];
|
|
464
|
-
if (!field) {
|
|
465
|
-
throw new ToolInputError(
|
|
466
|
-
`Resource type "${input.namespace}:${input.resourceType}" does not support match key "${key}".`
|
|
467
|
-
);
|
|
468
|
-
}
|
|
469
|
-
if (Array.isArray(value)) {
|
|
470
|
-
if (field.kind === "boolean") {
|
|
471
|
-
throw new ToolInputError(
|
|
472
|
-
`Match key "${key}" is boolean and cannot use a list.`
|
|
473
|
-
);
|
|
474
|
-
}
|
|
475
|
-
for (const entry of value) {
|
|
476
|
-
if (typeof entry !== field.kind) {
|
|
477
|
-
throw new ToolInputError(
|
|
478
|
-
`Match key "${key}" expects ${field.kind} values.`
|
|
479
|
-
);
|
|
480
|
-
}
|
|
481
|
-
if (field.enum && typeof entry === "string" && !field.enum.includes(entry)) {
|
|
482
|
-
throw new ToolInputError(
|
|
483
|
-
`Match key "${key}" does not allow value "${entry}".`
|
|
484
|
-
);
|
|
485
|
-
}
|
|
486
|
-
}
|
|
487
|
-
normalized[key] = [...new Set(value)];
|
|
488
|
-
continue;
|
|
489
|
-
}
|
|
490
|
-
if (typeof value !== field.kind) {
|
|
491
|
-
throw new ToolInputError(
|
|
492
|
-
`Match key "${key}" expects a ${field.kind} value.`
|
|
493
|
-
);
|
|
494
|
-
}
|
|
495
|
-
if (field.enum && typeof value === "string" && !field.enum.includes(value)) {
|
|
496
|
-
throw new ToolInputError(
|
|
497
|
-
`Match key "${key}" does not allow value "${value}".`
|
|
498
|
-
);
|
|
499
|
-
}
|
|
500
|
-
normalized[key] = value;
|
|
501
|
-
}
|
|
502
|
-
return Object.keys(normalized).length > 0 ? normalized : void 0;
|
|
503
|
-
}
|
|
504
|
-
function normalizeEventIdentifier(catalog, namespace, identifier) {
|
|
505
|
-
return normalizeResourceEventIdentifier(catalog[namespace], identifier);
|
|
506
|
-
}
|
|
507
|
-
|
|
508
|
-
// src/chat/sandbox/snapshot/events.ts
|
|
509
|
-
var WORKSPACE_SNAPSHOT_NAMESPACE = CORE_RESOURCE_EVENT_NAMESPACE;
|
|
510
|
-
var WORKSPACE_SNAPSHOT_RESOURCE_TYPE = "workspace_snapshot";
|
|
511
|
-
var WORKSPACE_SNAPSHOT_READY_EVENT = "workspace_snapshot.ready";
|
|
512
|
-
var WORKSPACE_SNAPSHOT_FAILED_EVENT = "workspace_snapshot.failed";
|
|
513
|
-
function workspaceSnapshotResourceEvents() {
|
|
514
|
-
return {
|
|
515
|
-
resourceTypes: [
|
|
516
|
-
{
|
|
517
|
-
type: WORKSPACE_SNAPSHOT_RESOURCE_TYPE,
|
|
518
|
-
supportedEvents: [
|
|
519
|
-
WORKSPACE_SNAPSHOT_READY_EVENT,
|
|
520
|
-
WORKSPACE_SNAPSHOT_FAILED_EVENT
|
|
521
|
-
],
|
|
522
|
-
// switchWorkspace already creates the temporary subscription.
|
|
523
|
-
suggestedEvents: [],
|
|
524
|
-
guidance: {
|
|
525
|
-
[WORKSPACE_SNAPSHOT_READY_EVENT]: "Call switchWorkspace again with the same Workspace name. Do not create another watch when switchWorkspace already returned a subscription.",
|
|
526
|
-
[WORKSPACE_SNAPSHOT_FAILED_EVENT]: "Report the snapshot failure. Do not keep waiting for this Workspace unless the user asks to retry."
|
|
527
|
-
}
|
|
528
|
-
}
|
|
529
|
-
]
|
|
530
|
-
};
|
|
531
|
-
}
|
|
532
|
-
function workspaceSnapshotWatch(input) {
|
|
533
|
-
return {
|
|
534
|
-
identifier: input.workspaceId,
|
|
535
|
-
label: `Workspace ${input.workspaceName} snapshot`,
|
|
536
|
-
namespace: WORKSPACE_SNAPSHOT_NAMESPACE,
|
|
537
|
-
supportedEvents: [
|
|
538
|
-
WORKSPACE_SNAPSHOT_READY_EVENT,
|
|
539
|
-
WORKSPACE_SNAPSHOT_FAILED_EVENT
|
|
540
|
-
],
|
|
541
|
-
suggestedEvents: [],
|
|
542
|
-
type: WORKSPACE_SNAPSHOT_RESOURCE_TYPE
|
|
543
|
-
};
|
|
544
|
-
}
|
|
545
|
-
function workspaceSnapshotFinishedEvent(input) {
|
|
546
|
-
const occurredAtMs = input.occurredAtMs ?? Date.now();
|
|
547
|
-
const eventType = input.status === "ready" ? WORKSPACE_SNAPSHOT_READY_EVENT : WORKSPACE_SNAPSHOT_FAILED_EVENT;
|
|
548
|
-
const trustedSummary = input.status === "ready" ? "Workspace snapshot is ready." : "Workspace snapshot build failed.";
|
|
549
|
-
const event = {
|
|
550
|
-
eventKey: `${WORKSPACE_SNAPSHOT_NAMESPACE}:${input.workspaceId}:${input.resultId}:${input.status}`,
|
|
551
|
-
eventType,
|
|
552
|
-
identifier: input.workspaceId,
|
|
553
|
-
namespace: WORKSPACE_SNAPSHOT_NAMESPACE,
|
|
554
|
-
occurredAtMs,
|
|
555
|
-
terminal: true,
|
|
556
|
-
trustedSummary,
|
|
557
|
-
data: {
|
|
558
|
-
workspaceId: input.workspaceId,
|
|
559
|
-
status: input.status
|
|
560
|
-
}
|
|
561
|
-
};
|
|
562
|
-
if (input.status === "failed" && input.error) {
|
|
563
|
-
event.untrustedText = input.error;
|
|
564
|
-
}
|
|
565
|
-
return event;
|
|
566
|
-
}
|
|
567
|
-
|
|
568
|
-
// src/chat/resource-events/runtime-catalog.ts
|
|
569
|
-
function getResourceEventCatalog() {
|
|
570
|
-
const catalog = {
|
|
571
|
-
[CORE_RESOURCE_EVENT_NAMESPACE]: workspaceSnapshotResourceEvents()
|
|
572
|
-
};
|
|
573
|
-
for (const plugin of getPlugins()) {
|
|
574
|
-
const registration = plugin.resourceEvents;
|
|
575
|
-
if (!registration || registration.isEnabled?.() === false) {
|
|
576
|
-
continue;
|
|
577
|
-
}
|
|
578
|
-
if (plugin.manifest.name === CORE_RESOURCE_EVENT_NAMESPACE) {
|
|
579
|
-
throw new Error(
|
|
580
|
-
`Plugin "${CORE_RESOURCE_EVENT_NAMESPACE}" cannot register resource events; that namespace is reserved for core Workspace snapshots`
|
|
581
|
-
);
|
|
582
|
-
}
|
|
583
|
-
catalog[plugin.manifest.name] = registration;
|
|
584
|
-
}
|
|
585
|
-
return catalog;
|
|
586
|
-
}
|
|
587
|
-
|
|
588
|
-
// src/chat/event-tasks/tool-support.ts
|
|
589
|
-
import { z as z4 } from "zod";
|
|
590
|
-
var compactEventTaskResultSchema = z4.object({
|
|
591
|
-
id: z4.string().min(1),
|
|
592
|
-
task: z4.string().min(1),
|
|
593
|
-
namespace: z4.string().min(1),
|
|
594
|
-
identifier: z4.string().min(1),
|
|
595
|
-
resourceType: z4.string().min(1),
|
|
596
|
-
label: z4.string().min(1),
|
|
597
|
-
events: z4.array(z4.string().min(1)).min(1),
|
|
598
|
-
match: z4.record(z4.string(), z4.unknown()).optional(),
|
|
599
|
-
credentialMode: z4.enum(["system", "creator"]),
|
|
600
|
-
createdBy: eventTaskPrincipalSchema,
|
|
601
|
-
triggerAvailable: z4.boolean()
|
|
602
|
-
}).strict();
|
|
603
|
-
var eventTaskToolResultSchema = juniorToolOutputSchema.extend({
|
|
604
|
-
task: compactEventTaskResultSchema
|
|
605
|
-
}).strict();
|
|
606
|
-
var eventTaskListToolResultSchema = juniorToolOutputSchema.extend({
|
|
607
|
-
tasks: z4.array(compactEventTaskResultSchema),
|
|
608
|
-
truncated: z4.boolean()
|
|
609
|
-
}).strict();
|
|
610
|
-
function registeredEventTaskTriggerSchema(catalog) {
|
|
611
|
-
const plugins = pluginResourceEventCatalog(catalog);
|
|
612
|
-
return z4.object({
|
|
613
|
-
namespace: eventNamespaceSchema(plugins),
|
|
614
|
-
identifier: z4.string().trim().min(1).max(EVENT_TASK_IDENTIFIER_MAX_LENGTH),
|
|
615
|
-
resourceType: registeredResourceTypeSchema(plugins),
|
|
616
|
-
label: z4.string().trim().min(1).max(500),
|
|
617
|
-
events: z4.array(registeredEventTypeSchema(plugins)).min(1),
|
|
618
|
-
match: registeredResourceEventMatchSchema()
|
|
619
|
-
}).strict();
|
|
620
|
-
}
|
|
621
|
-
function requireSupportedEventTaskTrigger(catalog, trigger) {
|
|
622
|
-
const plugins = pluginResourceEventCatalog(catalog);
|
|
623
|
-
for (const eventType of trigger.events) {
|
|
624
|
-
if (!pluginSupportsEvent(
|
|
625
|
-
plugins,
|
|
626
|
-
trigger.namespace,
|
|
627
|
-
trigger.resourceType,
|
|
628
|
-
eventType
|
|
629
|
-
)) {
|
|
630
|
-
throw new ToolInputError(
|
|
631
|
-
`Resource type "${trigger.namespace}:${trigger.resourceType}" does not support event "${eventType}".`
|
|
632
|
-
);
|
|
633
|
-
}
|
|
634
|
-
}
|
|
635
|
-
return requireSupportedResourceEventMatch(plugins, {
|
|
636
|
-
match: trigger.match,
|
|
637
|
-
namespace: trigger.namespace,
|
|
638
|
-
resourceType: trigger.resourceType
|
|
639
|
-
});
|
|
640
|
-
}
|
|
641
|
-
function requireEventTaskSlackContext(context) {
|
|
642
|
-
if (context.source.kind !== "slack" || context.destination.platform !== "slack" || context.actor?.platform !== "slack" || context.actor.teamId !== context.source.teamId) {
|
|
643
|
-
throw new ToolInputError(
|
|
644
|
-
"Event tasks require an active Slack channel or DM and actor."
|
|
645
|
-
);
|
|
646
|
-
}
|
|
647
|
-
return {
|
|
648
|
-
actor: context.actor,
|
|
649
|
-
destination: {
|
|
650
|
-
platform: "slack",
|
|
651
|
-
teamId: context.source.teamId,
|
|
652
|
-
channelId: context.source.channelId
|
|
653
|
-
},
|
|
654
|
-
source: context.source
|
|
655
|
-
};
|
|
656
|
-
}
|
|
657
|
-
function eventTaskMatchesDestination(task, destination) {
|
|
658
|
-
return task.destination.teamId === destination.teamId && task.destination.channelId === destination.channelId;
|
|
659
|
-
}
|
|
660
|
-
function eventTaskIsWritableFrom(task, destination) {
|
|
661
|
-
if (task.destination.teamId !== destination.teamId) {
|
|
662
|
-
return false;
|
|
663
|
-
}
|
|
664
|
-
return task.destination.channelId === destination.channelId || task.destinationVisibility === "public";
|
|
665
|
-
}
|
|
666
|
-
async function writableEventTask(context, id) {
|
|
667
|
-
const { destination } = requireEventTaskSlackContext(context);
|
|
668
|
-
const task = await getEventTask(getDb(), id);
|
|
669
|
-
if (!task || task.status === "deleted" || !eventTaskIsWritableFrom(task, destination)) {
|
|
670
|
-
throw new ToolInputError("Event task was not found.");
|
|
671
|
-
}
|
|
672
|
-
return task;
|
|
673
|
-
}
|
|
674
|
-
function eventTaskTriggerAvailable(task, catalog) {
|
|
675
|
-
const registration = pluginResourceEventCatalog(catalog)[task.trigger.namespace];
|
|
676
|
-
return Boolean(
|
|
677
|
-
registration && task.trigger.events.every(
|
|
678
|
-
(eventType) => registration.resourceTypes.some(
|
|
679
|
-
(resourceType) => resourceType.supportedEvents.includes(eventType)
|
|
680
|
-
)
|
|
681
|
-
)
|
|
682
|
-
);
|
|
683
|
-
}
|
|
684
|
-
function compactEventTask(task, catalog) {
|
|
685
|
-
return compactEventTaskResultSchema.parse({
|
|
686
|
-
id: task.id,
|
|
687
|
-
task: task.task.text,
|
|
688
|
-
namespace: task.trigger.namespace,
|
|
689
|
-
identifier: task.trigger.identifier,
|
|
690
|
-
resourceType: task.trigger.resourceType,
|
|
691
|
-
label: task.trigger.label,
|
|
692
|
-
events: task.trigger.events,
|
|
693
|
-
...task.trigger.match ? { match: task.trigger.match } : void 0,
|
|
694
|
-
credentialMode: task.credentialMode,
|
|
695
|
-
createdBy: task.createdBy,
|
|
696
|
-
triggerAvailable: eventTaskTriggerAvailable(task, catalog)
|
|
697
|
-
});
|
|
698
|
-
}
|
|
699
|
-
function eventTaskSuccess(task, catalog) {
|
|
700
|
-
return {
|
|
701
|
-
task: compactEventTask(task, catalog)
|
|
702
|
-
};
|
|
703
|
-
}
|
|
704
|
-
|
|
705
|
-
// src/chat/scheduled-tasks/tasks.ts
|
|
706
|
-
import { and as and3, asc as asc3, desc as desc2, eq as eq2, inArray as inArray2, notInArray, sql as sql3 } from "drizzle-orm";
|
|
707
|
-
import { z as z6 } from "zod";
|
|
708
|
-
|
|
709
|
-
// src/chat/scheduled-tasks/types.ts
|
|
710
|
-
import {
|
|
711
|
-
actorUserIdSchema as actorUserIdSchema2,
|
|
712
|
-
slackDestinationSchema as slackDestinationSchema2
|
|
713
|
-
} from "@sentry/junior-plugin-api";
|
|
714
|
-
import { z as z5 } from "zod";
|
|
715
|
-
var scheduledTaskStatusSchema = z5.enum([
|
|
716
|
-
"active",
|
|
717
|
-
"blocked",
|
|
718
|
-
"completed",
|
|
719
|
-
"deleted"
|
|
720
|
-
]);
|
|
721
|
-
var scheduledTaskCredentialModeSchema = z5.enum(["system", "creator"]);
|
|
722
|
-
var scheduledRunStatusSchema = z5.enum([
|
|
723
|
-
"pending",
|
|
724
|
-
"running",
|
|
725
|
-
"completed",
|
|
726
|
-
"failed",
|
|
727
|
-
"blocked",
|
|
728
|
-
"skipped"
|
|
729
|
-
]);
|
|
730
|
-
var scheduledTaskPrincipalSchema = z5.object({
|
|
731
|
-
slackUserId: actorUserIdSchema2,
|
|
732
|
-
fullName: z5.string().optional(),
|
|
733
|
-
userName: z5.string().optional()
|
|
734
|
-
}).strict();
|
|
735
|
-
var scheduledTaskRecurrenceSchema = z5.object({
|
|
736
|
-
dayOfMonth: z5.number().optional(),
|
|
737
|
-
frequency: z5.enum(["daily", "weekly", "monthly", "yearly"]),
|
|
738
|
-
interval: z5.number(),
|
|
739
|
-
month: z5.number().optional(),
|
|
740
|
-
startDate: z5.string(),
|
|
741
|
-
time: z5.object({
|
|
742
|
-
hour: z5.number(),
|
|
743
|
-
minute: z5.number()
|
|
744
|
-
}).strict(),
|
|
745
|
-
weekdays: z5.array(z5.number()).optional()
|
|
746
|
-
}).strict();
|
|
747
|
-
var scheduledTaskScheduleSchema = z5.object({
|
|
748
|
-
description: z5.string(),
|
|
749
|
-
kind: z5.enum(["one_off", "recurring"]),
|
|
750
|
-
recurrence: scheduledTaskRecurrenceSchema.optional(),
|
|
751
|
-
timezone: z5.string()
|
|
752
|
-
}).strict();
|
|
753
|
-
var scheduledTaskExecutionActorSchema = z5.object({
|
|
754
|
-
platform: z5.literal("system"),
|
|
755
|
-
name: z5.string()
|
|
756
|
-
}).strict();
|
|
757
|
-
var scheduledTaskSchema = z5.object({
|
|
758
|
-
id: z5.string(),
|
|
759
|
-
conversationAccess: z5.object({
|
|
760
|
-
audience: z5.enum(["direct", "group", "channel"]),
|
|
761
|
-
visibility: z5.enum(["private", "public"])
|
|
762
|
-
}).strict(),
|
|
763
|
-
createdAtMs: z5.number(),
|
|
764
|
-
createdBy: scheduledTaskPrincipalSchema,
|
|
765
|
-
/** Authoritative provider identity that created the task. */
|
|
766
|
-
creatorIdentityId: z5.string(),
|
|
767
|
-
/** Selects system credentials or task-bound creator credential delegation. */
|
|
768
|
-
credentialMode: scheduledTaskCredentialModeSchema,
|
|
769
|
-
destination: slackDestinationSchema2,
|
|
770
|
-
executionActor: scheduledTaskExecutionActorSchema.optional(),
|
|
771
|
-
lastRunAtMs: z5.number().optional(),
|
|
772
|
-
nextRunAtMs: z5.number().optional(),
|
|
773
|
-
originalRequest: z5.string().optional(),
|
|
774
|
-
runNowAtMs: z5.number().optional(),
|
|
775
|
-
schedule: scheduledTaskScheduleSchema,
|
|
776
|
-
status: scheduledTaskStatusSchema,
|
|
777
|
-
statusReason: z5.string().optional(),
|
|
778
|
-
task: z5.object({ text: z5.string() }).strict(),
|
|
779
|
-
/** SQL-backed short display title generated from the task instruction. */
|
|
780
|
-
title: z5.string().optional(),
|
|
781
|
-
updatedAtMs: z5.number()
|
|
782
|
-
}).strict();
|
|
783
|
-
var scheduledRunSchema = z5.object({
|
|
784
|
-
id: z5.string(),
|
|
785
|
-
attempt: z5.number(),
|
|
786
|
-
claimedAtMs: z5.number(),
|
|
787
|
-
completedAtMs: z5.number().optional(),
|
|
788
|
-
dispatchId: z5.string().optional(),
|
|
789
|
-
errorMessage: z5.string().optional(),
|
|
790
|
-
resultMessageTs: z5.string().optional(),
|
|
791
|
-
scheduledForMs: z5.number(),
|
|
792
|
-
startedAtMs: z5.number().optional(),
|
|
793
|
-
status: scheduledRunStatusSchema,
|
|
794
|
-
taskId: z5.string()
|
|
795
|
-
}).strict();
|
|
796
|
-
var SCHEDULED_TASK_SYSTEM_ACTOR = Object.freeze({
|
|
797
|
-
platform: "system",
|
|
798
|
-
name: "scheduled-task"
|
|
799
|
-
});
|
|
800
|
-
|
|
801
|
-
// src/chat/scheduled-tasks/tasks.ts
|
|
802
|
-
var SCHEDULER_KEY_PREFIX = "junior:scheduler";
|
|
803
|
-
var retainedScheduledTaskSchema = scheduledTaskSchema.omit({ creatorIdentityId: true, title: true }).extend({
|
|
804
|
-
// TODO(dcramer): Remove paused decoding and SQL list filtering after
|
|
805
|
-
// v0.129.x workers are unsupported and cannot overlap an upgrade.
|
|
806
|
-
status: z6.enum(["active", "blocked", "completed", "deleted", "paused"]),
|
|
807
|
-
version: z6.number().optional()
|
|
808
|
-
}).strict();
|
|
809
|
-
function parseScheduledTaskRow(row) {
|
|
810
|
-
const record = z6.record(z6.string(), z6.unknown()).safeParse(row.record);
|
|
811
|
-
if (!record.success) return void 0;
|
|
812
|
-
const {
|
|
813
|
-
creatorIdentityId: legacyCreatorIdentityId,
|
|
814
|
-
title: legacyTitle,
|
|
815
|
-
...retained
|
|
816
|
-
} = record.data;
|
|
817
|
-
const parsed = retainedScheduledTaskSchema.safeParse(retained);
|
|
818
|
-
if (!parsed.success) return void 0;
|
|
819
|
-
const { status, version: _version, ...task } = parsed.data;
|
|
820
|
-
const fallbackIdentity = row.creatorIdentityId === null ? z6.string().safeParse(legacyCreatorIdentityId) : void 0;
|
|
821
|
-
const creatorIdentityId = row.creatorIdentityId ?? (fallbackIdentity?.success ? fallbackIdentity.data : void 0);
|
|
822
|
-
if (creatorIdentityId === void 0) return void 0;
|
|
823
|
-
const fallbackTitle = row.title === null ? z6.string().safeParse(legacyTitle) : void 0;
|
|
824
|
-
const titleSource = row.title !== null ? row.title : fallbackTitle?.success ? fallbackTitle.data : void 0;
|
|
825
|
-
const title = titleSource?.trim() || void 0;
|
|
826
|
-
if (status === "paused") {
|
|
827
|
-
const {
|
|
828
|
-
nextRunAtMs: _nextRunAtMs,
|
|
829
|
-
runNowAtMs: _runNowAtMs,
|
|
830
|
-
...retained2
|
|
831
|
-
} = task;
|
|
832
|
-
return {
|
|
833
|
-
...retained2,
|
|
834
|
-
creatorIdentityId,
|
|
835
|
-
status: "deleted",
|
|
836
|
-
...title ? { title } : void 0
|
|
837
|
-
};
|
|
838
|
-
}
|
|
839
|
-
return {
|
|
840
|
-
...task,
|
|
841
|
-
creatorIdentityId,
|
|
842
|
-
status,
|
|
843
|
-
...title ? { title } : void 0
|
|
844
|
-
};
|
|
845
|
-
}
|
|
846
|
-
function isListedScheduledTask(task) {
|
|
847
|
-
return task !== void 0 && task.status !== "deleted";
|
|
848
|
-
}
|
|
849
|
-
function scheduledRunId(taskId, scheduledForMs) {
|
|
850
|
-
return `${taskId}:${scheduledForMs}`;
|
|
851
|
-
}
|
|
852
|
-
function withScheduledTaskLock(db, taskId, callback) {
|
|
853
|
-
return db.transaction(async (tx) => {
|
|
854
|
-
await tx.execute(
|
|
855
|
-
sql3`SELECT pg_advisory_xact_lock(hashtext(${`${SCHEDULER_KEY_PREFIX}:task:${taskId}:lock`}))`
|
|
856
|
-
);
|
|
857
|
-
return await callback(tx);
|
|
858
|
-
});
|
|
859
|
-
}
|
|
860
|
-
function requireStoredTask(task) {
|
|
861
|
-
const parsed = scheduledTaskSchema.safeParse(task);
|
|
862
|
-
if (!parsed.success) {
|
|
863
|
-
throw new Error("Scheduled task routing context is invalid.");
|
|
864
|
-
}
|
|
865
|
-
const { title, ...current } = parsed.data;
|
|
866
|
-
const normalizedTitle = title?.trim() || void 0;
|
|
867
|
-
return {
|
|
868
|
-
...current,
|
|
869
|
-
...normalizedTitle ? { title: normalizedTitle } : void 0
|
|
870
|
-
};
|
|
871
|
-
}
|
|
872
|
-
function scheduledTaskJsonRecord(task) {
|
|
873
|
-
const { title: _title, ...record } = task;
|
|
874
|
-
return record;
|
|
875
|
-
}
|
|
876
|
-
async function upsertScheduledTask(db, task) {
|
|
877
|
-
const title = task.title?.trim() || null;
|
|
878
|
-
await db.insert(juniorSchedulerTasks).values({
|
|
879
|
-
createdAtMs: task.createdAtMs,
|
|
880
|
-
creatorIdentityId: task.creatorIdentityId,
|
|
881
|
-
// TODO(dcramer): Stop writing this column after v0.127.x runtimes are
|
|
882
|
-
// unsupported and no supported runtime reads it.
|
|
883
|
-
creatorSlackUserId: task.createdBy.slackUserId,
|
|
884
|
-
id: task.id,
|
|
885
|
-
nextRunAtMs: task.nextRunAtMs,
|
|
886
|
-
record: scheduledTaskJsonRecord(task),
|
|
887
|
-
runNowAtMs: task.runNowAtMs,
|
|
888
|
-
status: task.status,
|
|
889
|
-
teamId: task.destination.teamId,
|
|
890
|
-
title
|
|
891
|
-
}).onConflictDoUpdate({
|
|
892
|
-
target: juniorSchedulerTasks.id,
|
|
893
|
-
set: {
|
|
894
|
-
createdAtMs: sql3`excluded.created_at_ms`,
|
|
895
|
-
creatorIdentityId: sql3`excluded.creator_identity_id`,
|
|
896
|
-
creatorSlackUserId: sql3`excluded.creator_slack_user_id`,
|
|
897
|
-
nextRunAtMs: sql3`excluded.next_run_at_ms`,
|
|
898
|
-
record: sql3`excluded.record`,
|
|
899
|
-
runNowAtMs: sql3`excluded.run_now_at_ms`,
|
|
900
|
-
status: sql3`excluded.status`,
|
|
901
|
-
teamId: sql3`excluded.team_id`,
|
|
902
|
-
title: sql3`excluded.title`
|
|
903
|
-
}
|
|
904
|
-
});
|
|
905
|
-
}
|
|
906
|
-
async function readScheduledTask(db, taskId) {
|
|
907
|
-
const rows = await db.select({
|
|
908
|
-
creatorIdentityId: juniorSchedulerTasks.creatorIdentityId,
|
|
909
|
-
record: juniorSchedulerTasks.record,
|
|
910
|
-
title: juniorSchedulerTasks.title
|
|
911
|
-
}).from(juniorSchedulerTasks).where(eq2(juniorSchedulerTasks.id, taskId)).limit(1);
|
|
912
|
-
return rows[0] ? parseScheduledTaskRow(rows[0]) : void 0;
|
|
913
|
-
}
|
|
914
|
-
async function readListedScheduledTasks(db, teamId) {
|
|
915
|
-
const rows = await db.select({
|
|
916
|
-
creatorIdentityId: juniorSchedulerTasks.creatorIdentityId,
|
|
917
|
-
record: juniorSchedulerTasks.record,
|
|
918
|
-
title: juniorSchedulerTasks.title
|
|
919
|
-
}).from(juniorSchedulerTasks).where(
|
|
920
|
-
and3(
|
|
921
|
-
notInArray(juniorSchedulerTasks.status, ["deleted", "paused"]),
|
|
922
|
-
teamId === void 0 ? void 0 : eq2(juniorSchedulerTasks.teamId, teamId)
|
|
923
|
-
)
|
|
924
|
-
).orderBy(
|
|
925
|
-
asc3(juniorSchedulerTasks.createdAtMs),
|
|
926
|
-
asc3(juniorSchedulerTasks.id)
|
|
927
|
-
);
|
|
928
|
-
return rows.map(parseScheduledTaskRow).filter(isListedScheduledTask);
|
|
929
|
-
}
|
|
930
|
-
async function listScheduledTasks(db) {
|
|
931
|
-
return await readListedScheduledTasks(db);
|
|
932
|
-
}
|
|
933
|
-
async function listScheduledTasksForTeam(db, teamId) {
|
|
934
|
-
return await readListedScheduledTasks(db, teamId);
|
|
935
|
-
}
|
|
936
|
-
async function writeScheduledTask(db, task, current) {
|
|
937
|
-
if (current?.status === "blocked" && task.status === "active" && typeof task.nextRunAtMs === "number" && Number.isFinite(task.nextRunAtMs)) {
|
|
938
|
-
await db.delete(juniorSchedulerRuns).where(
|
|
939
|
-
and3(
|
|
940
|
-
eq2(juniorSchedulerRuns.id, scheduledRunId(task.id, task.nextRunAtMs)),
|
|
941
|
-
eq2(juniorSchedulerRuns.status, "blocked")
|
|
942
|
-
)
|
|
943
|
-
);
|
|
944
|
-
}
|
|
945
|
-
if (task.status === "deleted" && current?.status !== "deleted") {
|
|
946
|
-
await skipPendingRunsForDeletedTask(db, task);
|
|
947
|
-
}
|
|
948
|
-
await upsertScheduledTask(db, task);
|
|
949
|
-
}
|
|
950
|
-
async function skipPendingRunsForDeletedTask(db, task) {
|
|
951
|
-
const errorMessage = `Scheduled task ${task.id} was deleted before the run started.`;
|
|
952
|
-
const rows = await db.select({ record: juniorSchedulerRuns.record }).from(juniorSchedulerRuns).where(
|
|
953
|
-
and3(
|
|
954
|
-
eq2(juniorSchedulerRuns.taskId, task.id),
|
|
955
|
-
eq2(juniorSchedulerRuns.status, "pending")
|
|
956
|
-
)
|
|
957
|
-
);
|
|
958
|
-
for (const row of rows) {
|
|
959
|
-
const parsed = scheduledRunSchema.safeParse(row.record);
|
|
960
|
-
if (!parsed.success || parsed.data.status !== "pending") continue;
|
|
961
|
-
const next = scheduledRunSchema.parse({
|
|
962
|
-
...parsed.data,
|
|
963
|
-
completedAtMs: task.updatedAtMs,
|
|
964
|
-
errorMessage,
|
|
965
|
-
status: "skipped"
|
|
966
|
-
});
|
|
967
|
-
await db.update(juniorSchedulerRuns).set({
|
|
968
|
-
record: next,
|
|
969
|
-
status: next.status
|
|
970
|
-
}).where(
|
|
971
|
-
and3(
|
|
972
|
-
eq2(juniorSchedulerRuns.id, next.id),
|
|
973
|
-
eq2(juniorSchedulerRuns.status, "pending")
|
|
974
|
-
)
|
|
975
|
-
);
|
|
976
|
-
}
|
|
977
|
-
}
|
|
978
|
-
async function createScheduledTask(db, task) {
|
|
979
|
-
const next = requireStoredTask(task);
|
|
980
|
-
return await withScheduledTaskLock(db, task.id, async (tx) => {
|
|
981
|
-
const current = await readScheduledTask(tx, task.id);
|
|
982
|
-
if (current) return current;
|
|
983
|
-
await writeScheduledTask(tx, next, void 0);
|
|
984
|
-
return next;
|
|
985
|
-
});
|
|
986
|
-
}
|
|
987
|
-
async function saveScheduledTask(db, task) {
|
|
988
|
-
const next = requireStoredTask(task);
|
|
989
|
-
await withScheduledTaskLock(db, task.id, async (tx) => {
|
|
990
|
-
const current = await readScheduledTask(tx, task.id);
|
|
991
|
-
await writeScheduledTask(tx, next, current);
|
|
992
|
-
});
|
|
993
|
-
return next;
|
|
994
|
-
}
|
|
995
|
-
async function saveScheduledTaskInLock(db, task, current) {
|
|
996
|
-
await writeScheduledTask(db, requireStoredTask(task), current);
|
|
997
|
-
}
|
|
998
|
-
async function listPublicScheduledTasksForTeams(db, teamIds, limit) {
|
|
999
|
-
if (teamIds.length === 0) return [];
|
|
1000
|
-
const rows = await db.select({
|
|
1001
|
-
creatorIdentityId: juniorSchedulerTasks.creatorIdentityId,
|
|
1002
|
-
record: juniorSchedulerTasks.record,
|
|
1003
|
-
title: juniorSchedulerTasks.title
|
|
1004
|
-
}).from(juniorSchedulerTasks).innerJoin(
|
|
1005
|
-
juniorDestinations,
|
|
1006
|
-
and3(
|
|
1007
|
-
eq2(juniorDestinations.provider, "slack"),
|
|
1008
|
-
eq2(juniorDestinations.providerTenantId, juniorSchedulerTasks.teamId),
|
|
1009
|
-
sql3`${juniorDestinations.providerDestinationId} = ${juniorSchedulerTasks.record}->'destination'->>'channelId'`
|
|
1010
|
-
)
|
|
1011
|
-
).where(
|
|
1012
|
-
and3(
|
|
1013
|
-
inArray2(juniorSchedulerTasks.teamId, teamIds),
|
|
1014
|
-
notInArray(juniorSchedulerTasks.status, [
|
|
1015
|
-
"completed",
|
|
1016
|
-
"deleted",
|
|
1017
|
-
"paused"
|
|
1018
|
-
]),
|
|
1019
|
-
eq2(juniorDestinations.visibility, "public")
|
|
1020
|
-
)
|
|
1021
|
-
).orderBy(
|
|
1022
|
-
desc2(juniorSchedulerTasks.createdAtMs),
|
|
1023
|
-
desc2(juniorSchedulerTasks.id)
|
|
1024
|
-
).limit(limit);
|
|
1025
|
-
return rows.map(parseScheduledTaskRow).filter(isListedScheduledTask);
|
|
1026
|
-
}
|
|
1027
|
-
|
|
1028
|
-
// src/chat/attachments/store.ts
|
|
1029
|
-
import { createHash, randomUUID } from "crypto";
|
|
1030
|
-
import { and as and4, asc as asc4, eq as eq3, inArray as inArray3, isNotNull, isNull, lte as lte2, or as or2 } from "drizzle-orm";
|
|
1031
|
-
var DAY_MS = 24 * 60 * 60 * 1e3;
|
|
1032
|
-
var ATTACHMENT_GC_BATCH_LIMIT = 200;
|
|
1033
|
-
var ATTACHMENT_RETENTION_MS = 30 * DAY_MS;
|
|
1034
|
-
function attachmentDigest(file) {
|
|
1035
|
-
return createHash("sha256").update(file.data).digest("hex");
|
|
1036
|
-
}
|
|
1037
|
-
function attachmentId(args) {
|
|
1038
|
-
const identity = [
|
|
1039
|
-
args.conversationId,
|
|
1040
|
-
args.storageProvider,
|
|
1041
|
-
args.sha256,
|
|
1042
|
-
args.filename,
|
|
1043
|
-
args.contentType
|
|
1044
|
-
];
|
|
1045
|
-
if (args.source) {
|
|
1046
|
-
identity.push(args.source.provider, args.source.id);
|
|
1047
|
-
}
|
|
1048
|
-
return createHash("sha256").update(identity.join("\0")).digest("hex");
|
|
1049
|
-
}
|
|
1050
|
-
function attachmentKey(args) {
|
|
1051
|
-
return `conversations/${args.conversationId}/attachments/${args.attachmentId}/${args.writeId}/${args.filename}`;
|
|
1052
|
-
}
|
|
1053
|
-
async function bestEffortDelete(storage, key) {
|
|
1054
|
-
try {
|
|
1055
|
-
await storage.delete([key]);
|
|
1056
|
-
} catch {
|
|
1057
|
-
}
|
|
1058
|
-
}
|
|
1059
|
-
async function isConversationPurged(db, conversationId) {
|
|
1060
|
-
const [row] = await db.db().select({
|
|
1061
|
-
transcriptPurgedAt: juniorConversations.transcriptPurgedAt
|
|
1062
|
-
}).from(juniorConversations).where(eq3(juniorConversations.conversationId, conversationId));
|
|
1063
|
-
return row?.transcriptPurgedAt != null;
|
|
1064
|
-
}
|
|
1065
|
-
function purgedConversationError(conversationId) {
|
|
1066
|
-
return new Error(
|
|
1067
|
-
`Cannot store attachment for purged conversation ${conversationId}`
|
|
1068
|
-
);
|
|
1069
|
-
}
|
|
1070
|
-
async function assertConversationNotPurged(db, conversationId) {
|
|
1071
|
-
if (await isConversationPurged(db, conversationId)) {
|
|
1072
|
-
throw purgedConversationError(conversationId);
|
|
1073
|
-
}
|
|
1074
|
-
}
|
|
1075
|
-
async function rejectPurgedAttachmentWrite(args) {
|
|
1076
|
-
await args.db.db().update(juniorAttachments).set({ deleteRequestedAt: args.now }).where(eq3(juniorAttachments.id, args.id));
|
|
1077
|
-
await bestEffortDelete(args.storage, args.storageKey);
|
|
1078
|
-
throw purgedConversationError(args.conversationId);
|
|
1079
|
-
}
|
|
1080
|
-
async function reuseLiveAttachment(args) {
|
|
1081
|
-
await bestEffortDelete(args.storage, args.storageKey);
|
|
1082
|
-
await assertConversationNotPurged(args.db, args.conversationId);
|
|
1083
|
-
return { id: args.id };
|
|
1084
|
-
}
|
|
1085
|
-
async function storeAttachment(args) {
|
|
1086
|
-
const now = new Date(args.nowMs ?? Date.now());
|
|
1087
|
-
const sha256 = attachmentDigest(args.file);
|
|
1088
|
-
const id = attachmentId({
|
|
1089
|
-
conversationId: args.conversationId,
|
|
1090
|
-
contentType: args.file.mimeType,
|
|
1091
|
-
filename: args.file.filename,
|
|
1092
|
-
source: args.source,
|
|
1093
|
-
storageProvider: args.storage.provider,
|
|
1094
|
-
sha256
|
|
1095
|
-
});
|
|
1096
|
-
const [existing] = await args.db.db().select({
|
|
1097
|
-
contentType: juniorAttachments.contentType,
|
|
1098
|
-
conversationId: juniorAttachments.conversationId,
|
|
1099
|
-
deleteRequestedAt: juniorAttachments.deleteRequestedAt,
|
|
1100
|
-
filename: juniorAttachments.filename,
|
|
1101
|
-
id: juniorAttachments.id,
|
|
1102
|
-
provider: juniorAttachments.provider,
|
|
1103
|
-
providerId: juniorAttachments.providerId,
|
|
1104
|
-
storageProvider: juniorAttachments.storageProvider,
|
|
1105
|
-
sha256: juniorAttachments.sha256,
|
|
1106
|
-
storageKey: juniorAttachments.storageKey
|
|
1107
|
-
}).from(juniorAttachments).where(eq3(juniorAttachments.id, id));
|
|
1108
|
-
if (existing) {
|
|
1109
|
-
if (existing.contentType !== args.file.mimeType || existing.conversationId !== args.conversationId || existing.filename !== args.file.filename || existing.provider !== (args.source?.provider ?? null) || existing.providerId !== (args.source?.id ?? null) || existing.storageProvider !== args.storage.provider || existing.sha256 !== sha256) {
|
|
1110
|
-
throw new Error(`Attachment write conflicts with ${existing.id}`);
|
|
1111
|
-
}
|
|
1112
|
-
if (existing.deleteRequestedAt === null) {
|
|
1113
|
-
await assertConversationNotPurged(args.db, args.conversationId);
|
|
1114
|
-
return { id: existing.id };
|
|
1115
|
-
}
|
|
1116
|
-
}
|
|
1117
|
-
await assertConversationNotPurged(args.db, args.conversationId);
|
|
1118
|
-
const storageKey = attachmentKey({
|
|
1119
|
-
attachmentId: id,
|
|
1120
|
-
conversationId: args.conversationId,
|
|
1121
|
-
filename: args.file.filename,
|
|
1122
|
-
writeId: randomUUID()
|
|
1123
|
-
});
|
|
1124
|
-
await args.storage.put({
|
|
1125
|
-
body: args.file.data,
|
|
1126
|
-
contentType: args.file.mimeType,
|
|
1127
|
-
key: storageKey
|
|
1128
|
-
});
|
|
1129
|
-
if (existing?.deleteRequestedAt != null) {
|
|
1130
|
-
if (await isConversationPurged(args.db, args.conversationId)) {
|
|
1131
|
-
await bestEffortDelete(args.storage, storageKey);
|
|
1132
|
-
throw purgedConversationError(args.conversationId);
|
|
1133
|
-
}
|
|
1134
|
-
const oldStorageKey = existing.storageKey;
|
|
1135
|
-
const revived2 = await args.db.db().update(juniorAttachments).set({
|
|
1136
|
-
deleteRequestedAt: null,
|
|
1137
|
-
storageKey
|
|
1138
|
-
}).where(
|
|
1139
|
-
and4(
|
|
1140
|
-
eq3(juniorAttachments.id, id),
|
|
1141
|
-
isNotNull(juniorAttachments.deleteRequestedAt)
|
|
1142
|
-
)
|
|
1143
|
-
).returning({ id: juniorAttachments.id });
|
|
1144
|
-
if (revived2.length > 0) {
|
|
1145
|
-
if (await isConversationPurged(args.db, args.conversationId)) {
|
|
1146
|
-
await rejectPurgedAttachmentWrite({
|
|
1147
|
-
conversationId: args.conversationId,
|
|
1148
|
-
db: args.db,
|
|
1149
|
-
id,
|
|
1150
|
-
now,
|
|
1151
|
-
storage: args.storage,
|
|
1152
|
-
storageKey
|
|
1153
|
-
});
|
|
1154
|
-
}
|
|
1155
|
-
if (oldStorageKey !== storageKey) {
|
|
1156
|
-
await bestEffortDelete(args.storage, oldStorageKey);
|
|
1157
|
-
}
|
|
1158
|
-
return { id };
|
|
1159
|
-
}
|
|
1160
|
-
const [afterRace2] = await args.db.db().select({
|
|
1161
|
-
deleteRequestedAt: juniorAttachments.deleteRequestedAt,
|
|
1162
|
-
id: juniorAttachments.id,
|
|
1163
|
-
storageKey: juniorAttachments.storageKey
|
|
1164
|
-
}).from(juniorAttachments).where(eq3(juniorAttachments.id, id));
|
|
1165
|
-
if (afterRace2?.deleteRequestedAt === null) {
|
|
1166
|
-
return reuseLiveAttachment({
|
|
1167
|
-
conversationId: args.conversationId,
|
|
1168
|
-
db: args.db,
|
|
1169
|
-
id: afterRace2.id,
|
|
1170
|
-
storage: args.storage,
|
|
1171
|
-
storageKey
|
|
1172
|
-
});
|
|
1173
|
-
}
|
|
1174
|
-
}
|
|
1175
|
-
let inserted = [];
|
|
1176
|
-
try {
|
|
1177
|
-
inserted = await args.db.db().insert(juniorAttachments).values({
|
|
1178
|
-
id,
|
|
1179
|
-
conversationId: args.conversationId,
|
|
1180
|
-
storageProvider: args.storage.provider,
|
|
1181
|
-
storageKey,
|
|
1182
|
-
provider: args.source?.provider,
|
|
1183
|
-
providerId: args.source?.id,
|
|
1184
|
-
filename: args.file.filename,
|
|
1185
|
-
contentType: args.file.mimeType,
|
|
1186
|
-
bytes: args.file.bytes,
|
|
1187
|
-
sha256,
|
|
1188
|
-
createdAt: now
|
|
1189
|
-
}).onConflictDoNothing({ target: juniorAttachments.id }).returning({ id: juniorAttachments.id });
|
|
1190
|
-
} catch (error) {
|
|
1191
|
-
await bestEffortDelete(args.storage, storageKey);
|
|
1192
|
-
throw error;
|
|
1193
|
-
}
|
|
1194
|
-
if (inserted.length > 0) {
|
|
1195
|
-
if (await isConversationPurged(args.db, args.conversationId)) {
|
|
1196
|
-
await rejectPurgedAttachmentWrite({
|
|
1197
|
-
conversationId: args.conversationId,
|
|
1198
|
-
db: args.db,
|
|
1199
|
-
id,
|
|
1200
|
-
now,
|
|
1201
|
-
storage: args.storage,
|
|
1202
|
-
storageKey
|
|
1203
|
-
});
|
|
1204
|
-
}
|
|
1205
|
-
return { id };
|
|
1206
|
-
}
|
|
1207
|
-
const [row] = await args.db.db().select({
|
|
1208
|
-
deleteRequestedAt: juniorAttachments.deleteRequestedAt,
|
|
1209
|
-
id: juniorAttachments.id,
|
|
1210
|
-
storageKey: juniorAttachments.storageKey
|
|
1211
|
-
}).from(juniorAttachments).where(eq3(juniorAttachments.id, id));
|
|
1212
|
-
if (!row) {
|
|
1213
|
-
await bestEffortDelete(args.storage, storageKey);
|
|
1214
|
-
throw new Error(`Attachment row missing after write for ${id}`);
|
|
1215
|
-
}
|
|
1216
|
-
if (row.deleteRequestedAt === null) {
|
|
1217
|
-
return reuseLiveAttachment({
|
|
1218
|
-
conversationId: args.conversationId,
|
|
1219
|
-
db: args.db,
|
|
1220
|
-
id: row.id,
|
|
1221
|
-
storage: args.storage,
|
|
1222
|
-
storageKey
|
|
1223
|
-
});
|
|
1224
|
-
}
|
|
1225
|
-
if (await isConversationPurged(args.db, args.conversationId)) {
|
|
1226
|
-
await bestEffortDelete(args.storage, storageKey);
|
|
1227
|
-
throw purgedConversationError(args.conversationId);
|
|
1228
|
-
}
|
|
1229
|
-
const revived = await args.db.db().update(juniorAttachments).set({
|
|
1230
|
-
deleteRequestedAt: null,
|
|
1231
|
-
storageKey
|
|
1232
|
-
}).where(
|
|
1233
|
-
and4(
|
|
1234
|
-
eq3(juniorAttachments.id, id),
|
|
1235
|
-
isNotNull(juniorAttachments.deleteRequestedAt)
|
|
1236
|
-
)
|
|
1237
|
-
).returning({ id: juniorAttachments.id });
|
|
1238
|
-
if (revived.length > 0) {
|
|
1239
|
-
if (await isConversationPurged(args.db, args.conversationId)) {
|
|
1240
|
-
await rejectPurgedAttachmentWrite({
|
|
1241
|
-
conversationId: args.conversationId,
|
|
1242
|
-
db: args.db,
|
|
1243
|
-
id,
|
|
1244
|
-
now,
|
|
1245
|
-
storage: args.storage,
|
|
1246
|
-
storageKey
|
|
1247
|
-
});
|
|
1248
|
-
}
|
|
1249
|
-
if (row.storageKey !== storageKey) {
|
|
1250
|
-
await bestEffortDelete(args.storage, row.storageKey);
|
|
1251
|
-
}
|
|
1252
|
-
return { id };
|
|
1253
|
-
}
|
|
1254
|
-
const [afterRace] = await args.db.db().select({
|
|
1255
|
-
deleteRequestedAt: juniorAttachments.deleteRequestedAt,
|
|
1256
|
-
id: juniorAttachments.id
|
|
1257
|
-
}).from(juniorAttachments).where(eq3(juniorAttachments.id, id));
|
|
1258
|
-
if (afterRace?.deleteRequestedAt === null) {
|
|
1259
|
-
return reuseLiveAttachment({
|
|
1260
|
-
conversationId: args.conversationId,
|
|
1261
|
-
db: args.db,
|
|
1262
|
-
id: afterRace.id,
|
|
1263
|
-
storage: args.storage,
|
|
1264
|
-
storageKey
|
|
1265
|
-
});
|
|
1266
|
-
}
|
|
1267
|
-
await bestEffortDelete(args.storage, storageKey);
|
|
1268
|
-
throw new Error(`Attachment ${id} disappeared while reviving`);
|
|
1269
|
-
}
|
|
1270
|
-
async function readLiveAttachment(args) {
|
|
1271
|
-
const [row] = await args.db.db().select({
|
|
1272
|
-
bytes: juniorAttachments.bytes,
|
|
1273
|
-
contentType: juniorAttachments.contentType,
|
|
1274
|
-
conversationId: juniorAttachments.conversationId,
|
|
1275
|
-
deleteRequestedAt: juniorAttachments.deleteRequestedAt,
|
|
1276
|
-
filename: juniorAttachments.filename,
|
|
1277
|
-
id: juniorAttachments.id,
|
|
1278
|
-
storageProvider: juniorAttachments.storageProvider,
|
|
1279
|
-
storageKey: juniorAttachments.storageKey
|
|
1280
|
-
}).from(juniorAttachments).where(
|
|
1281
|
-
and4(
|
|
1282
|
-
eq3(juniorAttachments.id, args.attachmentId),
|
|
1283
|
-
eq3(juniorAttachments.conversationId, args.conversationId),
|
|
1284
|
-
isNull(juniorAttachments.deleteRequestedAt)
|
|
1285
|
-
)
|
|
1286
|
-
);
|
|
1287
|
-
if (!row) return null;
|
|
1288
|
-
if (await isConversationPurged(args.db, args.conversationId)) {
|
|
1289
|
-
return null;
|
|
1290
|
-
}
|
|
1291
|
-
return {
|
|
1292
|
-
bytes: row.bytes,
|
|
1293
|
-
contentType: row.contentType,
|
|
1294
|
-
conversationId: row.conversationId,
|
|
1295
|
-
filename: row.filename,
|
|
1296
|
-
id: row.id,
|
|
1297
|
-
storageKey: row.storageKey,
|
|
1298
|
-
storageProvider: row.storageProvider
|
|
1299
|
-
};
|
|
1300
|
-
}
|
|
1301
|
-
async function storeAttachments(args) {
|
|
1302
|
-
const stored = [];
|
|
1303
|
-
for (const file of args.files) {
|
|
1304
|
-
stored.push(
|
|
1305
|
-
await storeAttachment({
|
|
1306
|
-
conversationId: args.conversationId,
|
|
1307
|
-
db: args.db,
|
|
1308
|
-
file,
|
|
1309
|
-
...args.nowMs !== void 0 ? { nowMs: args.nowMs } : void 0,
|
|
1310
|
-
storage: args.storage
|
|
1311
|
-
})
|
|
1312
|
-
);
|
|
1313
|
-
}
|
|
1314
|
-
return stored;
|
|
1315
|
-
}
|
|
1316
|
-
async function requestAttachmentDeletion(db, conversationIds, nowMs) {
|
|
1317
|
-
if (conversationIds.length === 0) return;
|
|
1318
|
-
await db.db().update(juniorAttachments).set({ deleteRequestedAt: new Date(nowMs) }).where(
|
|
1319
|
-
and4(
|
|
1320
|
-
inArray3(juniorAttachments.conversationId, conversationIds),
|
|
1321
|
-
isNull(juniorAttachments.deleteRequestedAt)
|
|
1322
|
-
)
|
|
1323
|
-
);
|
|
1324
|
-
}
|
|
1325
|
-
async function collectAttachmentGarbage(args) {
|
|
1326
|
-
const expiresBefore = new Date(args.nowMs - ATTACHMENT_RETENTION_MS);
|
|
1327
|
-
const rows = await args.db.db().select({
|
|
1328
|
-
id: juniorAttachments.id,
|
|
1329
|
-
storageKey: juniorAttachments.storageKey
|
|
1330
|
-
}).from(juniorAttachments).innerJoin(
|
|
1331
|
-
juniorConversations,
|
|
1332
|
-
eq3(juniorAttachments.conversationId, juniorConversations.conversationId)
|
|
1333
|
-
).where(
|
|
1334
|
-
and4(
|
|
1335
|
-
eq3(juniorAttachments.storageProvider, args.storage.provider),
|
|
1336
|
-
or2(
|
|
1337
|
-
lte2(juniorAttachments.createdAt, expiresBefore),
|
|
1338
|
-
isNotNull(juniorAttachments.deleteRequestedAt),
|
|
1339
|
-
isNotNull(juniorConversations.transcriptPurgedAt)
|
|
1340
|
-
)
|
|
1341
|
-
)
|
|
1342
|
-
).orderBy(asc4(juniorAttachments.createdAt), asc4(juniorAttachments.id)).limit(args.limit ?? ATTACHMENT_GC_BATCH_LIMIT);
|
|
1343
|
-
if (rows.length === 0) return { deleted: 0 };
|
|
1344
|
-
await args.storage.delete(rows.map((row) => row.storageKey));
|
|
1345
|
-
const stillEligible = await args.db.db().select({ id: juniorAttachments.id }).from(juniorAttachments).innerJoin(
|
|
1346
|
-
juniorConversations,
|
|
1347
|
-
eq3(juniorAttachments.conversationId, juniorConversations.conversationId)
|
|
1348
|
-
).where(
|
|
1349
|
-
and4(
|
|
1350
|
-
inArray3(
|
|
1351
|
-
juniorAttachments.id,
|
|
1352
|
-
rows.map((row) => row.id)
|
|
1353
|
-
),
|
|
1354
|
-
or2(
|
|
1355
|
-
lte2(juniorAttachments.createdAt, expiresBefore),
|
|
1356
|
-
isNotNull(juniorAttachments.deleteRequestedAt),
|
|
1357
|
-
isNotNull(juniorConversations.transcriptPurgedAt)
|
|
1358
|
-
)
|
|
1359
|
-
)
|
|
1360
|
-
);
|
|
1361
|
-
if (stillEligible.length === 0) return { deleted: 0 };
|
|
1362
|
-
const removed = await args.db.db().delete(juniorAttachments).where(
|
|
1363
|
-
inArray3(
|
|
1364
|
-
juniorAttachments.id,
|
|
1365
|
-
stillEligible.map((row) => row.id)
|
|
1366
|
-
)
|
|
1367
|
-
).returning({
|
|
1368
|
-
id: juniorAttachments.id
|
|
1369
|
-
});
|
|
1370
|
-
return { deleted: removed.length };
|
|
1371
|
-
}
|
|
1372
|
-
|
|
1373
|
-
export {
|
|
1374
|
-
juniorToolOutputSchema,
|
|
1375
|
-
injectContinuationToolName,
|
|
1376
|
-
makeStructuredToolOutput,
|
|
1377
|
-
hasResourceEventCatalogEntries,
|
|
1378
|
-
hasPluginResourceEventCatalogEntries,
|
|
1379
|
-
eventNamespaceSchema,
|
|
1380
|
-
registeredEventTypeSchema,
|
|
1381
|
-
registeredResourceTypeSchema,
|
|
1382
|
-
pluginSupportsEvent,
|
|
1383
|
-
resourceEventGuidance,
|
|
1384
|
-
registeredResourceEventMatchSchema,
|
|
1385
|
-
requireSupportedResourceEventMatch,
|
|
1386
|
-
normalizeEventIdentifier,
|
|
1387
|
-
WORKSPACE_SNAPSHOT_READY_EVENT,
|
|
1388
|
-
WORKSPACE_SNAPSHOT_FAILED_EVENT,
|
|
1389
|
-
workspaceSnapshotWatch,
|
|
1390
|
-
workspaceSnapshotFinishedEvent,
|
|
1391
|
-
getResourceEventCatalog,
|
|
1392
|
-
getEventTask,
|
|
1393
|
-
createEventTask,
|
|
1394
|
-
saveEventTask,
|
|
1395
|
-
deleteEventTask,
|
|
1396
|
-
listEventTasksForTeam,
|
|
1397
|
-
eventTaskBelongsToUser,
|
|
1398
|
-
listEventTasksCreatedBy,
|
|
1399
|
-
listDeletedEventTasksCreatedBy,
|
|
1400
|
-
listPublicEventTasksForTeams,
|
|
1401
|
-
collectEventTaskMatchKeys,
|
|
1402
|
-
findMatchingEventTasks,
|
|
1403
|
-
eventTaskToolResultSchema,
|
|
1404
|
-
eventTaskListToolResultSchema,
|
|
1405
|
-
registeredEventTaskTriggerSchema,
|
|
1406
|
-
requireSupportedEventTaskTrigger,
|
|
1407
|
-
requireEventTaskSlackContext,
|
|
1408
|
-
eventTaskMatchesDestination,
|
|
1409
|
-
writableEventTask,
|
|
1410
|
-
eventTaskTriggerAvailable,
|
|
1411
|
-
compactEventTask,
|
|
1412
|
-
eventTaskSuccess,
|
|
1413
|
-
scheduledRunSchema,
|
|
1414
|
-
SCHEDULED_TASK_SYSTEM_ACTOR,
|
|
1415
|
-
parseScheduledTaskRow,
|
|
1416
|
-
isListedScheduledTask,
|
|
1417
|
-
scheduledRunId,
|
|
1418
|
-
withScheduledTaskLock,
|
|
1419
|
-
readScheduledTask,
|
|
1420
|
-
listScheduledTasks,
|
|
1421
|
-
listScheduledTasksForTeam,
|
|
1422
|
-
createScheduledTask,
|
|
1423
|
-
saveScheduledTask,
|
|
1424
|
-
saveScheduledTaskInLock,
|
|
1425
|
-
listPublicScheduledTasksForTeams,
|
|
1426
|
-
storeAttachment,
|
|
1427
|
-
readLiveAttachment,
|
|
1428
|
-
storeAttachments,
|
|
1429
|
-
requestAttachmentDeletion,
|
|
1430
|
-
collectAttachmentGarbage,
|
|
1431
|
-
incrementStat,
|
|
1432
|
-
readStats
|
|
1433
|
-
};
|