@sentry/junior 0.162.0 → 0.164.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/README.md +2 -0
- package/dist/{agent-hooks-TL2SGC5W.js → agent-hooks-WTBPNRTC.js} +10 -10
- package/dist/api/acp/route.d.ts +13 -0
- package/dist/api/schema/workspace.d.ts +47 -0
- package/dist/api/schema.d.ts +2 -0
- package/dist/api/schema.js +15 -3
- package/dist/api/workspaces/routes.d.ts +4 -0
- package/dist/api.js +321 -408
- package/dist/app.d.ts +2 -2
- package/dist/app.js +482 -38
- package/dist/{catalog-runtime-IGSMIADJ.js → catalog-runtime-CZ2SQINY.js} +5 -5
- package/dist/chat/agent/sandbox.d.ts +4 -0
- package/dist/chat/api-turns/cancellation.d.ts +27 -0
- package/dist/chat/api-turns/routing.d.ts +31 -0
- package/dist/chat/api-turns/work.d.ts +12 -29
- package/dist/chat/app/conversation-work.d.ts +6 -1
- package/dist/chat/app/production.d.ts +2 -2
- package/dist/chat/conversations/history.d.ts +3 -0
- package/dist/chat/conversations/turn-lifecycle.d.ts +1 -1
- package/dist/chat/experimental.d.ts +1 -1
- package/dist/chat/plugins/agent-hooks.d.ts +4 -0
- package/dist/chat/repository-instructions.d.ts +1 -1
- package/dist/chat/sandbox/paths.d.ts +1 -0
- package/dist/chat/sandbox/prepare-workspace.d.ts +11 -0
- package/dist/chat/sandbox/ref.d.ts +1 -0
- package/dist/chat/sandbox/sandbox.d.ts +4 -0
- package/dist/chat/sandbox/session.d.ts +4 -0
- package/dist/chat/sandbox/snapshot/profile.d.ts +4 -3
- package/dist/chat/sandbox/snapshot/resolve.d.ts +4 -0
- package/dist/chat/sandbox/workspace.d.ts +4 -0
- package/dist/chat/tools/types.d.ts +5 -0
- package/dist/chat/workspaces/checkout-path.d.ts +4 -0
- package/dist/chat/workspaces/store.d.ts +37 -0
- package/dist/chat/workspaces/tools.d.ts +4 -0
- package/dist/chat/workspaces/types.d.ts +13 -0
- package/dist/chat/workspaces/validation.d.ts +21 -0
- package/dist/{chunk-THXMSSFZ.js → chunk-26JYGL2L.js} +1 -1
- package/dist/{chunk-OPNJRQDP.js → chunk-2K25MRZK.js} +9 -6
- package/dist/{chunk-C3KUKA27.js → chunk-3L23VTY7.js} +17 -17
- package/dist/{chunk-JJ3VXFDI.js → chunk-4GTOFUQF.js} +1 -1
- package/dist/{chunk-RDV4BQRZ.js → chunk-5FWQ73UZ.js} +1 -1
- package/dist/{chunk-BPZQWAZJ.js → chunk-5GLGQHO3.js} +4 -1
- package/dist/{chunk-K7IV6UV3.js → chunk-6BXV63YX.js} +5 -5
- package/dist/{chunk-4WYGQ47U.js → chunk-6ENAACWN.js} +5 -5
- package/dist/{chunk-EHQLTK3X.js → chunk-7WKWMMYQ.js} +323 -127
- package/dist/chunk-BVREIWBD.js +131 -0
- package/dist/{chunk-DZC3F2UI.js → chunk-BXBZDV3C.js} +1 -1
- package/dist/{chunk-HW4BW3PC.js → chunk-CGCNFEAU.js} +4 -4
- package/dist/{chunk-JRVURR3H.js → chunk-FKDY7NTC.js} +543 -164
- package/dist/{chunk-MNCEGXEH.js → chunk-FX4PX3SA.js} +2 -2
- package/dist/{chunk-OBFRMQBY.js → chunk-H36GJVJV.js} +9 -9
- package/dist/{chunk-V7QL4QMW.js → chunk-HHW6ZVIZ.js} +12 -7
- package/dist/{chunk-O6P7DZ6O.js → chunk-JW23XD4I.js} +6 -6
- package/dist/{chunk-DDQ43S5Q.js → chunk-LPLVNMVN.js} +4 -4
- package/dist/{chunk-5NEBOHRG.js → chunk-NAGI2XH3.js} +4 -4
- package/dist/{chunk-FEWT5KPP.js → chunk-NLTQRYNP.js} +2 -2
- package/dist/{chunk-SA6BEEAB.js → chunk-PU45BSBS.js} +108 -74
- package/dist/{chunk-7DTPFFQX.js → chunk-RBV4JHCP.js} +1 -1
- package/dist/{chunk-6BJFIWEV.js → chunk-RJHZNPDH.js} +46 -87
- package/dist/{chunk-FESUWAYU.js → chunk-RULIISEI.js} +1 -1
- package/dist/{chunk-MMBMDSRK.js → chunk-RYYEHWIL.js} +1 -1
- package/dist/{chunk-Y75ZDQB5.js → chunk-VHSEMJTS.js} +1 -1
- package/dist/{chunk-5IDFPCON.js → chunk-VRS6WDK5.js} +1 -1
- package/dist/{chunk-EBEHJPFF.js → chunk-VV2DN24T.js} +1 -1
- package/dist/{chunk-WHAJ3QVY.js → chunk-WJUKESU2.js} +1 -1
- package/dist/{chunk-H2KW7AZC.js → chunk-XAIRI54J.js} +1 -1
- package/dist/{chunk-AJ7J2HWY.js → chunk-Y2B6AXZQ.js} +6 -6
- package/dist/{chunk-QHCIZLIX.js → chunk-YHCTPDXS.js} +356 -13
- package/dist/{chunk-CZ6EHOU5.js → chunk-ZSNABC2E.js} +40 -2
- package/dist/{chunk-KQSO4DJ4.js → chunk-ZUTAGCPH.js} +9 -9
- package/dist/cli/chat.js +32 -32
- package/dist/cli/check.js +6 -6
- package/dist/cli/plugins.js +13 -13
- package/dist/cli/snapshot-create.js +10 -10
- package/dist/cli/upgrade.js +7 -7
- package/dist/db/schema/workspaces.d.ts +169 -0
- package/dist/db/schema.d.ts +169 -1
- package/dist/{db-66XZ4HHQ.js → db-32KRFS6L.js} +7 -7
- package/dist/{experimental-HH6OM6UH.js → experimental-XVDOSRBS.js} +1 -1
- package/dist/instrumentation.js +4 -4
- package/dist/nitro.js +2 -2
- package/dist/{oauth-callback-server-FMLKDUUF.js → oauth-callback-server-CGZP2Z6U.js} +24 -24
- package/dist/{runner-YZONUMNY.js → runner-S5N4Q2XL.js} +32 -34
- package/dist/{skills-3UDJJXSO.js → skills-KYPSHSQR.js} +6 -6
- package/dist/spawn-EGOB3CLV.js +37 -0
- package/dist/{validation-3HTDBONC.js → validation-SZBE7JNF.js} +6 -6
- package/dist/version.js +1 -1
- package/dist/{work-O4YTIS54.js → work-GCZKHEZX.js} +18 -18
- package/dist/worker-6MY5Q4A2.js +24 -0
- package/migrations/0029_dizzy_barracuda.sql +19 -0
- package/migrations/meta/0029_snapshot.json +2709 -0
- package/migrations/meta/_journal.json +8 -1
- package/package.json +7 -4
- package/dist/chunk-EM225ITJ.js +0 -56
- package/dist/spawn-TBPVA5S2.js +0 -37
- package/dist/worker-WHURUEYX.js +0 -24
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import {
|
|
2
2
|
createWebAuthorization,
|
|
3
3
|
deleteWebAuthorization
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-5FWQ73UZ.js";
|
|
5
5
|
import {
|
|
6
6
|
appendAndEnqueueInboundMessage
|
|
7
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-NAGI2XH3.js";
|
|
8
8
|
import {
|
|
9
9
|
buildDeliveredTurnStatePatch,
|
|
10
10
|
completeAuthPauseTurn,
|
|
@@ -12,25 +12,26 @@ import {
|
|
|
12
12
|
getTurnUserMessage,
|
|
13
13
|
processPluginTask,
|
|
14
14
|
scheduleSessionCompletedPluginTasks
|
|
15
|
-
} from "./chunk-
|
|
15
|
+
} from "./chunk-ZUTAGCPH.js";
|
|
16
16
|
import {
|
|
17
17
|
ConversationTurnLifecycleService,
|
|
18
18
|
coerceThreadConversationState,
|
|
19
19
|
getPersistedSandboxState,
|
|
20
20
|
getPersistedThreadState,
|
|
21
21
|
persistThreadStateById
|
|
22
|
-
} from "./chunk-
|
|
22
|
+
} from "./chunk-2K25MRZK.js";
|
|
23
23
|
import {
|
|
24
24
|
healthReportSchema
|
|
25
25
|
} from "./chunk-UTL3MHPC.js";
|
|
26
26
|
import {
|
|
27
27
|
parseSlackThreadId
|
|
28
|
-
} from "./chunk-
|
|
28
|
+
} from "./chunk-WJUKESU2.js";
|
|
29
29
|
import {
|
|
30
30
|
TurnInputCommitLostError,
|
|
31
31
|
abandonTurnRecord,
|
|
32
32
|
buildConversationContext,
|
|
33
33
|
buildDeterministicTurnId,
|
|
34
|
+
clearPendingAuth,
|
|
34
35
|
getAssistantReplyText,
|
|
35
36
|
getConversationTurnBoundaryError,
|
|
36
37
|
getTurnRecord,
|
|
@@ -48,17 +49,31 @@ import {
|
|
|
48
49
|
saveTurnCheckpoint,
|
|
49
50
|
startActiveTurn,
|
|
50
51
|
upsertConversationMessage
|
|
51
|
-
} from "./chunk-
|
|
52
|
+
} from "./chunk-HHW6ZVIZ.js";
|
|
52
53
|
import {
|
|
53
54
|
normalizeSlackConversationId
|
|
54
|
-
} from "./chunk-
|
|
55
|
+
} from "./chunk-YHCTPDXS.js";
|
|
55
56
|
import {
|
|
56
57
|
getConversationEventStore,
|
|
57
|
-
getConversationStore
|
|
58
|
-
|
|
58
|
+
getConversationStore,
|
|
59
|
+
getDb
|
|
60
|
+
} from "./chunk-LPLVNMVN.js";
|
|
61
|
+
import {
|
|
62
|
+
juniorApiTokens,
|
|
63
|
+
juniorConversationParticipants,
|
|
64
|
+
juniorConversations,
|
|
65
|
+
juniorIdentities,
|
|
66
|
+
juniorUsers
|
|
67
|
+
} from "./chunk-ZSNABC2E.js";
|
|
59
68
|
import {
|
|
60
69
|
getSlackBotToken
|
|
61
|
-
} from "./chunk-
|
|
70
|
+
} from "./chunk-FX4PX3SA.js";
|
|
71
|
+
import {
|
|
72
|
+
juniorDestinations
|
|
73
|
+
} from "./chunk-VFUK3X5B.js";
|
|
74
|
+
import {
|
|
75
|
+
canExposeConversationPayload
|
|
76
|
+
} from "./chunk-RBV4JHCP.js";
|
|
62
77
|
import {
|
|
63
78
|
credentialContextForActor
|
|
64
79
|
} from "./chunk-BDUENQD3.js";
|
|
@@ -67,7 +82,7 @@ import {
|
|
|
67
82
|
logWarn,
|
|
68
83
|
setTags,
|
|
69
84
|
withLogContext
|
|
70
|
-
} from "./chunk-
|
|
85
|
+
} from "./chunk-VHSEMJTS.js";
|
|
71
86
|
|
|
72
87
|
// src/handlers/health.ts
|
|
73
88
|
function readHealthReport() {
|
|
@@ -105,115 +120,254 @@ function createVercelAttachmentStorage() {
|
|
|
105
120
|
};
|
|
106
121
|
}
|
|
107
122
|
|
|
108
|
-
// src/
|
|
109
|
-
import {
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
real_name: z.string().nullish()
|
|
117
|
-
}).optional()
|
|
118
|
-
})
|
|
119
|
-
});
|
|
120
|
-
var USER_CACHE_TTL_MS = 5 * 60 * 1e3;
|
|
121
|
-
var userCache = /* @__PURE__ */ new Map();
|
|
122
|
-
function userCacheKey(teamId, userId) {
|
|
123
|
-
return `${teamId}:${userId}`;
|
|
123
|
+
// src/personal-tokens/store.ts
|
|
124
|
+
import { createHash, randomBytes, randomUUID } from "crypto";
|
|
125
|
+
import { and, desc, eq, gt, isNull } from "drizzle-orm";
|
|
126
|
+
var TOKEN_PREFIX = "jr_pat_";
|
|
127
|
+
var TOKEN_SUFFIX_LENGTH = 8;
|
|
128
|
+
var DEFAULT_TOKEN_LIFETIME_MS = 90 * 24 * 60 * 60 * 1e3;
|
|
129
|
+
function normalizeEmail(email) {
|
|
130
|
+
return email.trim().toLowerCase();
|
|
124
131
|
}
|
|
125
|
-
function
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
132
|
+
function hashToken(token) {
|
|
133
|
+
return createHash("sha256").update(token, "utf8").digest("hex");
|
|
134
|
+
}
|
|
135
|
+
function metadata(row) {
|
|
136
|
+
return {
|
|
137
|
+
id: row.id,
|
|
138
|
+
name: row.name,
|
|
139
|
+
tokenSuffix: row.tokenSuffix,
|
|
140
|
+
createdAt: row.createdAt.toISOString(),
|
|
141
|
+
expiresAt: row.expiresAt.toISOString(),
|
|
142
|
+
lastUsedAt: row.lastUsedAt?.toISOString() ?? null
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
async function createPersonalToken(args) {
|
|
146
|
+
const now = args.now ?? /* @__PURE__ */ new Date();
|
|
147
|
+
const token = `${TOKEN_PREFIX}${randomBytes(32).toString("base64url")}`;
|
|
148
|
+
const row = {
|
|
149
|
+
id: randomUUID(),
|
|
150
|
+
ownerEmailNormalized: normalizeEmail(args.email),
|
|
151
|
+
name: args.name.trim(),
|
|
152
|
+
tokenHash: hashToken(token),
|
|
153
|
+
tokenSuffix: token.slice(-TOKEN_SUFFIX_LENGTH),
|
|
154
|
+
createdAt: now,
|
|
155
|
+
expiresAt: new Date(now.getTime() + DEFAULT_TOKEN_LIFETIME_MS)
|
|
156
|
+
};
|
|
157
|
+
await getDb().insert(juniorApiTokens).values(row);
|
|
158
|
+
return { ...metadata({ ...row, lastUsedAt: null, revokedAt: null }), token };
|
|
159
|
+
}
|
|
160
|
+
async function listPersonalTokens(email, now = /* @__PURE__ */ new Date()) {
|
|
161
|
+
const rows = await getDb().select().from(juniorApiTokens).where(
|
|
162
|
+
and(
|
|
163
|
+
eq(juniorApiTokens.ownerEmailNormalized, normalizeEmail(email)),
|
|
164
|
+
isNull(juniorApiTokens.revokedAt),
|
|
165
|
+
gt(juniorApiTokens.expiresAt, now)
|
|
166
|
+
)
|
|
167
|
+
).orderBy(desc(juniorApiTokens.createdAt));
|
|
168
|
+
return rows.map(metadata);
|
|
169
|
+
}
|
|
170
|
+
async function revokePersonalToken(args) {
|
|
171
|
+
const rows = await getDb().update(juniorApiTokens).set({ revokedAt: args.now ?? /* @__PURE__ */ new Date() }).where(
|
|
172
|
+
and(
|
|
173
|
+
eq(juniorApiTokens.id, args.id),
|
|
174
|
+
eq(juniorApiTokens.ownerEmailNormalized, normalizeEmail(args.email)),
|
|
175
|
+
isNull(juniorApiTokens.revokedAt)
|
|
176
|
+
)
|
|
177
|
+
).returning({ id: juniorApiTokens.id });
|
|
178
|
+
return rows.length > 0;
|
|
179
|
+
}
|
|
180
|
+
async function authenticatePersonalToken(token, now = /* @__PURE__ */ new Date()) {
|
|
181
|
+
if (!token.startsWith(TOKEN_PREFIX)) return void 0;
|
|
182
|
+
const rows = await getDb().select({
|
|
183
|
+
id: juniorApiTokens.id,
|
|
184
|
+
ownerEmailNormalized: juniorApiTokens.ownerEmailNormalized
|
|
185
|
+
}).from(juniorApiTokens).where(
|
|
186
|
+
and(
|
|
187
|
+
eq(juniorApiTokens.tokenHash, hashToken(token)),
|
|
188
|
+
isNull(juniorApiTokens.revokedAt),
|
|
189
|
+
gt(juniorApiTokens.expiresAt, now)
|
|
190
|
+
)
|
|
191
|
+
).limit(1);
|
|
192
|
+
const match = rows[0];
|
|
193
|
+
if (!match) return void 0;
|
|
194
|
+
await getDb().update(juniorApiTokens).set({ lastUsedAt: now }).where(eq(juniorApiTokens.id, match.id));
|
|
195
|
+
return match.ownerEmailNormalized;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// src/chat/api-turns/work.ts
|
|
199
|
+
import { createHash as createHash2 } from "crypto";
|
|
200
|
+
import {
|
|
201
|
+
createWebSource,
|
|
202
|
+
localDestinationSchema
|
|
203
|
+
} from "@sentry/junior-plugin-api";
|
|
204
|
+
|
|
205
|
+
// src/chat/api-turns/cancellation.ts
|
|
206
|
+
function createApiTurnCancellation() {
|
|
207
|
+
const active = /* @__PURE__ */ new Map();
|
|
208
|
+
return {
|
|
209
|
+
begin(conversationId) {
|
|
210
|
+
if (active.has(conversationId)) {
|
|
211
|
+
return void 0;
|
|
212
|
+
}
|
|
213
|
+
const controller = new AbortController();
|
|
214
|
+
active.set(conversationId, {
|
|
215
|
+
connected: true,
|
|
216
|
+
controller,
|
|
217
|
+
parked: false
|
|
218
|
+
});
|
|
219
|
+
return controller.signal;
|
|
220
|
+
},
|
|
221
|
+
cancel(conversationId) {
|
|
222
|
+
const entry = active.get(conversationId);
|
|
223
|
+
if (!entry) {
|
|
224
|
+
return false;
|
|
225
|
+
}
|
|
226
|
+
entry.controller.abort(new Error("API Turn cancelled"));
|
|
227
|
+
return true;
|
|
228
|
+
},
|
|
229
|
+
disconnect(conversationId, signal) {
|
|
230
|
+
const entry = active.get(conversationId);
|
|
231
|
+
if (entry?.controller.signal !== signal) {
|
|
232
|
+
return;
|
|
233
|
+
}
|
|
234
|
+
entry.connected = false;
|
|
235
|
+
if (entry.parked) {
|
|
236
|
+
active.delete(conversationId);
|
|
237
|
+
}
|
|
238
|
+
},
|
|
239
|
+
finish(conversationId, signal) {
|
|
240
|
+
const entry = active.get(conversationId);
|
|
241
|
+
if (entry?.controller.signal === signal) {
|
|
242
|
+
active.delete(conversationId);
|
|
243
|
+
}
|
|
244
|
+
},
|
|
245
|
+
park(conversationId, signal) {
|
|
246
|
+
const entry = active.get(conversationId);
|
|
247
|
+
if (entry?.controller.signal !== signal) {
|
|
248
|
+
return;
|
|
249
|
+
}
|
|
250
|
+
entry.parked = true;
|
|
251
|
+
if (!entry.connected) {
|
|
252
|
+
active.delete(conversationId);
|
|
253
|
+
}
|
|
254
|
+
},
|
|
255
|
+
signal(conversationId) {
|
|
256
|
+
return active.get(conversationId)?.controller.signal;
|
|
257
|
+
}
|
|
258
|
+
};
|
|
259
|
+
}
|
|
260
|
+
async function completeCancelledApiTurn(args) {
|
|
261
|
+
try {
|
|
262
|
+
await abandonTurnRecord({
|
|
263
|
+
conversationId: args.conversationId,
|
|
264
|
+
turnId: args.turnId,
|
|
265
|
+
errorMessage: "API Turn cancelled"
|
|
266
|
+
});
|
|
267
|
+
clearPendingAuth(args.conversation, args.turnId);
|
|
268
|
+
markConversationMessage(args.conversation, args.userMessageId, {
|
|
269
|
+
replied: false,
|
|
270
|
+
skippedReason: "turn cancelled"
|
|
271
|
+
});
|
|
272
|
+
markTurnClosed({
|
|
273
|
+
conversation: args.conversation,
|
|
274
|
+
nowMs: Date.now(),
|
|
275
|
+
sessionId: args.turnId
|
|
276
|
+
});
|
|
277
|
+
await deleteWebAuthorization({
|
|
278
|
+
actorId: args.actorId,
|
|
279
|
+
conversationId: args.conversationId
|
|
280
|
+
});
|
|
281
|
+
await persistThreadStateById(args.conversationId, {
|
|
282
|
+
conversation: args.conversation,
|
|
283
|
+
sandboxRef: args.sandboxRef
|
|
284
|
+
});
|
|
285
|
+
await args.lifecycle.complete({
|
|
286
|
+
conversationId: args.conversationId,
|
|
287
|
+
createdAtMs: Date.now(),
|
|
288
|
+
outcome: "cancelled",
|
|
289
|
+
turnId: args.turnId
|
|
290
|
+
});
|
|
291
|
+
} finally {
|
|
292
|
+
if (args.signal) {
|
|
293
|
+
args.cancellation?.finish(args.conversationId, args.signal);
|
|
294
|
+
}
|
|
131
295
|
}
|
|
132
|
-
|
|
296
|
+
await args.acknowledge();
|
|
133
297
|
}
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
298
|
+
|
|
299
|
+
// src/chat/api-turns/routing.ts
|
|
300
|
+
import { z } from "zod";
|
|
301
|
+
var apiTurnMailboxMetadataSchema = z.object({
|
|
302
|
+
authorEmail: z.string().email(),
|
|
303
|
+
authorFullName: z.string().min(1).optional(),
|
|
304
|
+
authorUserId: z.string().min(1),
|
|
305
|
+
authorUserName: z.string().min(1).optional(),
|
|
306
|
+
kind: z.literal("api_turn"),
|
|
307
|
+
messageId: z.string().min(1)
|
|
308
|
+
}).strict();
|
|
309
|
+
function parseApiTurnMessages(messages) {
|
|
310
|
+
if (messages.length === 0) {
|
|
311
|
+
return [];
|
|
312
|
+
}
|
|
313
|
+
const parsed = messages.map((message) => ({
|
|
314
|
+
message,
|
|
315
|
+
metadata: apiTurnMailboxMetadataSchema.safeParse(message.input.metadata)
|
|
316
|
+
}));
|
|
317
|
+
if (parsed.every((entry) => !entry.metadata.success)) {
|
|
318
|
+
return [];
|
|
319
|
+
}
|
|
320
|
+
if (parsed.some((entry) => !entry.metadata.success)) {
|
|
321
|
+
throw new Error("Conversation mailbox mixes web turns and other input");
|
|
322
|
+
}
|
|
323
|
+
return parsed.map((entry) => {
|
|
324
|
+
if (!entry.metadata.success) {
|
|
325
|
+
throw new Error("API turn mailbox metadata failed validation");
|
|
326
|
+
}
|
|
327
|
+
return { message: entry.message, metadata: entry.metadata.data };
|
|
138
328
|
});
|
|
139
329
|
}
|
|
140
|
-
async function
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
const cached = readFromCache(teamId, userId);
|
|
145
|
-
if (cached) {
|
|
146
|
-
return cached;
|
|
330
|
+
async function resolveApiTurnWork(context) {
|
|
331
|
+
const batch = parseApiTurnMessages(context.attempt.messages);
|
|
332
|
+
if (batch.length > 0) {
|
|
333
|
+
return { kind: "mailbox", batch };
|
|
147
334
|
}
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
return null;
|
|
335
|
+
if (context.attempt.messages.length > 0) {
|
|
336
|
+
return void 0;
|
|
151
337
|
}
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
}
|
|
338
|
+
const summaries = await listTurnSummaries(context.conversationId);
|
|
339
|
+
const active = summaries.filter(
|
|
340
|
+
(summary) => summary.surface === "api" && !summary.dispatchId && (summary.state === "paused" || summary.state === "running")
|
|
341
|
+
);
|
|
342
|
+
if (active.length > 1) {
|
|
343
|
+
throw new Error(
|
|
344
|
+
`Conversation ${context.conversationId} has multiple active web turns`
|
|
160
345
|
);
|
|
161
|
-
if (!response.ok) {
|
|
162
|
-
logWarn("slack.user.lookup.failed", {
|
|
163
|
-
"enduser.id": userId,
|
|
164
|
-
"app.slack.team_id": teamId,
|
|
165
|
-
"http.response.status_code": response.status
|
|
166
|
-
});
|
|
167
|
-
return null;
|
|
168
|
-
}
|
|
169
|
-
const parsed = slackUserInfoSchema.safeParse(await response.json());
|
|
170
|
-
if (!parsed.success) {
|
|
171
|
-
return null;
|
|
172
|
-
}
|
|
173
|
-
const payload = parsed.data;
|
|
174
|
-
const userName = payload.user.name?.trim() || void 0;
|
|
175
|
-
const fullName = payload.user.profile?.real_name?.trim() || void 0;
|
|
176
|
-
const result = {
|
|
177
|
-
userName,
|
|
178
|
-
fullName,
|
|
179
|
-
email: payload.user.profile?.email?.trim() || void 0
|
|
180
|
-
};
|
|
181
|
-
writeToCache(teamId, userId, result);
|
|
182
|
-
return result;
|
|
183
|
-
} catch (error) {
|
|
184
|
-
logWarn("slack.user.lookup.failed", {
|
|
185
|
-
"enduser.id": userId,
|
|
186
|
-
"app.slack.team_id": teamId,
|
|
187
|
-
"exception.message": error instanceof Error ? error.message : String(error)
|
|
188
|
-
});
|
|
189
|
-
return null;
|
|
190
346
|
}
|
|
347
|
+
const turnId = active[0]?.turnId;
|
|
348
|
+
if (!turnId) {
|
|
349
|
+
return void 0;
|
|
350
|
+
}
|
|
351
|
+
const record = await getTurnRecord(context.conversationId, turnId);
|
|
352
|
+
if (!record || record.surface !== "api" || Boolean(record.dispatchId) || record.state !== "paused" && record.state !== "running") {
|
|
353
|
+
return void 0;
|
|
354
|
+
}
|
|
355
|
+
return { kind: "resume", turnId };
|
|
356
|
+
}
|
|
357
|
+
async function isApiTurnWork(context) {
|
|
358
|
+
return await resolveApiTurnWork(context) !== void 0;
|
|
191
359
|
}
|
|
192
360
|
|
|
193
361
|
// src/chat/api-turns/work.ts
|
|
194
|
-
|
|
195
|
-
import { z as z2 } from "zod";
|
|
196
|
-
import {
|
|
197
|
-
createWebSource,
|
|
198
|
-
localDestinationSchema
|
|
199
|
-
} from "@sentry/junior-plugin-api";
|
|
200
|
-
var apiTurnMailboxMetadataSchema = z2.object({
|
|
201
|
-
authorEmail: z2.string().email(),
|
|
202
|
-
authorFullName: z2.string().min(1).optional(),
|
|
203
|
-
authorUserId: z2.string().min(1),
|
|
204
|
-
authorUserName: z2.string().min(1).optional(),
|
|
205
|
-
kind: z2.literal("api_turn"),
|
|
206
|
-
messageId: z2.string().min(1)
|
|
207
|
-
}).strict();
|
|
208
|
-
function normalizeEmail(email) {
|
|
362
|
+
function normalizeEmail2(email) {
|
|
209
363
|
return email.trim().toLowerCase();
|
|
210
364
|
}
|
|
211
365
|
function stableHex(...parts) {
|
|
212
|
-
return
|
|
366
|
+
return createHash2("sha256").update(parts.join("\0")).digest("hex").slice(0, 24);
|
|
213
367
|
}
|
|
214
368
|
function createApiConversationId(args) {
|
|
215
369
|
return `local:web:${stableHex(
|
|
216
|
-
|
|
370
|
+
normalizeEmail2(args.actorEmail),
|
|
217
371
|
args.idempotencyKey
|
|
218
372
|
)}`;
|
|
219
373
|
}
|
|
@@ -245,23 +399,23 @@ function webSourceForConversation(args) {
|
|
|
245
399
|
args.visibility === "private" ? "private" : "public"
|
|
246
400
|
);
|
|
247
401
|
}
|
|
248
|
-
function actorFromMetadata(
|
|
402
|
+
function actorFromMetadata(metadata2) {
|
|
249
403
|
return {
|
|
250
404
|
platform: "web",
|
|
251
|
-
userId:
|
|
252
|
-
email:
|
|
253
|
-
...
|
|
254
|
-
...
|
|
405
|
+
userId: metadata2.authorUserId,
|
|
406
|
+
email: normalizeEmail2(metadata2.authorEmail),
|
|
407
|
+
...metadata2.authorFullName ? { fullName: metadata2.authorFullName } : {},
|
|
408
|
+
...metadata2.authorUserName ? { userName: metadata2.authorUserName } : {}
|
|
255
409
|
};
|
|
256
410
|
}
|
|
257
411
|
function storedActorFromApi(actor) {
|
|
258
412
|
return {
|
|
259
|
-
...actor.email ? { email:
|
|
413
|
+
...actor.email ? { email: normalizeEmail2(actor.email) } : {},
|
|
260
414
|
...actor.fullName ? { fullName: actor.fullName } : {}
|
|
261
415
|
};
|
|
262
416
|
}
|
|
263
417
|
function webActorFromEmail(email, profile) {
|
|
264
|
-
const normalized =
|
|
418
|
+
const normalized = normalizeEmail2(email);
|
|
265
419
|
return {
|
|
266
420
|
platform: "web",
|
|
267
421
|
userId: `dashboard:${stableHex(normalized)}`,
|
|
@@ -303,7 +457,7 @@ function buildApiTurnInboundMessage(args) {
|
|
|
303
457
|
authorId: args.actor.userId,
|
|
304
458
|
text,
|
|
305
459
|
metadata: {
|
|
306
|
-
authorEmail:
|
|
460
|
+
authorEmail: normalizeEmail2(args.actor.email),
|
|
307
461
|
...args.actor.fullName ? { authorFullName: args.actor.fullName } : {},
|
|
308
462
|
authorUserId: args.actor.userId,
|
|
309
463
|
...args.actor.userName ? { authorUserName: args.actor.userName } : {},
|
|
@@ -401,57 +555,6 @@ async function appendAndEnqueueApiConversationMessage(input, options) {
|
|
|
401
555
|
status: result.status === "duplicate" ? "duplicate" : "accepted"
|
|
402
556
|
};
|
|
403
557
|
}
|
|
404
|
-
function parseApiTurnMessages(messages) {
|
|
405
|
-
if (messages.length === 0) {
|
|
406
|
-
return [];
|
|
407
|
-
}
|
|
408
|
-
const parsed = messages.map((message) => ({
|
|
409
|
-
message,
|
|
410
|
-
metadata: apiTurnMailboxMetadataSchema.safeParse(message.input.metadata)
|
|
411
|
-
}));
|
|
412
|
-
if (parsed.every((entry) => !entry.metadata.success)) {
|
|
413
|
-
return [];
|
|
414
|
-
}
|
|
415
|
-
if (parsed.some((entry) => !entry.metadata.success)) {
|
|
416
|
-
throw new Error("Conversation mailbox mixes web turns and other input");
|
|
417
|
-
}
|
|
418
|
-
return parsed.map((entry) => {
|
|
419
|
-
if (!entry.metadata.success) {
|
|
420
|
-
throw new Error("API turn mailbox metadata failed validation");
|
|
421
|
-
}
|
|
422
|
-
return { message: entry.message, metadata: entry.metadata.data };
|
|
423
|
-
});
|
|
424
|
-
}
|
|
425
|
-
async function resolveApiTurnWork(context) {
|
|
426
|
-
const batch = parseApiTurnMessages(context.attempt.messages);
|
|
427
|
-
if (batch.length > 0) {
|
|
428
|
-
return { kind: "mailbox", batch };
|
|
429
|
-
}
|
|
430
|
-
if (context.attempt.messages.length > 0) {
|
|
431
|
-
return void 0;
|
|
432
|
-
}
|
|
433
|
-
const summaries = await listTurnSummaries(context.conversationId);
|
|
434
|
-
const active = summaries.filter(
|
|
435
|
-
(summary) => summary.surface === "api" && !summary.dispatchId && (summary.state === "paused" || summary.state === "running")
|
|
436
|
-
);
|
|
437
|
-
if (active.length > 1) {
|
|
438
|
-
throw new Error(
|
|
439
|
-
`Conversation ${context.conversationId} has multiple active web turns`
|
|
440
|
-
);
|
|
441
|
-
}
|
|
442
|
-
const turnId = active[0]?.turnId;
|
|
443
|
-
if (!turnId) {
|
|
444
|
-
return void 0;
|
|
445
|
-
}
|
|
446
|
-
const record = await getTurnRecord(context.conversationId, turnId);
|
|
447
|
-
if (!record || record.surface !== "api" || Boolean(record.dispatchId) || record.state !== "paused" && record.state !== "running") {
|
|
448
|
-
return void 0;
|
|
449
|
-
}
|
|
450
|
-
return { kind: "resume", turnId };
|
|
451
|
-
}
|
|
452
|
-
async function isApiTurnWork(context) {
|
|
453
|
-
return await resolveApiTurnWork(context) !== void 0;
|
|
454
|
-
}
|
|
455
558
|
function captureApiBoundaryFailure(args) {
|
|
456
559
|
const eventId = logException(args.error, `api.turn.${args.failureCode}`, {
|
|
457
560
|
conversationId: args.conversationId,
|
|
@@ -460,6 +563,10 @@ function captureApiBoundaryFailure(args) {
|
|
|
460
563
|
});
|
|
461
564
|
return typeof eventId === "string" ? eventId : void 0;
|
|
462
565
|
}
|
|
566
|
+
function hasLostTurnInputCommit(error) {
|
|
567
|
+
const cause = getConversationTurnBoundaryError(error)?.cause ?? error;
|
|
568
|
+
return isTurnInputCommitLostError(error) || isTurnInputCommitLostError(cause);
|
|
569
|
+
}
|
|
463
570
|
function createApiTurnWorker(options) {
|
|
464
571
|
return async (context) => {
|
|
465
572
|
const resolved = await resolveApiTurnWork(context);
|
|
@@ -618,6 +725,39 @@ function createApiTurnWorker(options) {
|
|
|
618
725
|
let modelFailureEventId;
|
|
619
726
|
let modelFailureCaptureAttempted = false;
|
|
620
727
|
let reply;
|
|
728
|
+
const cancellationSignal = options.cancellation?.signal(
|
|
729
|
+
context.conversationId
|
|
730
|
+
);
|
|
731
|
+
const finishCancellation = () => {
|
|
732
|
+
if (cancellationSignal) {
|
|
733
|
+
options.cancellation?.finish(
|
|
734
|
+
context.conversationId,
|
|
735
|
+
cancellationSignal
|
|
736
|
+
);
|
|
737
|
+
}
|
|
738
|
+
};
|
|
739
|
+
const completeCancelledTurn = async () => {
|
|
740
|
+
try {
|
|
741
|
+
await completeCancelledApiTurn({
|
|
742
|
+
acknowledge,
|
|
743
|
+
actorId: actor.userId,
|
|
744
|
+
cancellation: options.cancellation,
|
|
745
|
+
conversation,
|
|
746
|
+
conversationId: context.conversationId,
|
|
747
|
+
lifecycle,
|
|
748
|
+
sandboxRef,
|
|
749
|
+
signal: cancellationSignal,
|
|
750
|
+
turnId,
|
|
751
|
+
userMessageId
|
|
752
|
+
});
|
|
753
|
+
} catch (error) {
|
|
754
|
+
if (hasLostTurnInputCommit(error)) {
|
|
755
|
+
return { status: "lost_lease" };
|
|
756
|
+
}
|
|
757
|
+
throw error;
|
|
758
|
+
}
|
|
759
|
+
return { status: "completed" };
|
|
760
|
+
};
|
|
621
761
|
const deliverAssistantMessage = async (value) => {
|
|
622
762
|
const replyText = typeof value === "string" ? value : getAssistantReplyText(value);
|
|
623
763
|
if (!replyText?.trim()) {
|
|
@@ -654,6 +794,9 @@ function createApiTurnWorker(options) {
|
|
|
654
794
|
await persistThreadStateById(context.conversationId, {
|
|
655
795
|
conversation
|
|
656
796
|
});
|
|
797
|
+
if (cancellationSignal?.aborted) {
|
|
798
|
+
return await completeCancelledTurn();
|
|
799
|
+
}
|
|
657
800
|
const piMessages = await loadProjection({
|
|
658
801
|
conversationId: context.conversationId
|
|
659
802
|
});
|
|
@@ -677,6 +820,7 @@ function createApiTurnWorker(options) {
|
|
|
677
820
|
publishExternally: false,
|
|
678
821
|
source,
|
|
679
822
|
surface: "api",
|
|
823
|
+
...cancellationSignal ? { signal: cancellationSignal } : {},
|
|
680
824
|
authorization: createWebAuthorization({
|
|
681
825
|
actorId: actor.userId,
|
|
682
826
|
conversationId: context.conversationId
|
|
@@ -705,6 +849,9 @@ function createApiTurnWorker(options) {
|
|
|
705
849
|
}
|
|
706
850
|
}
|
|
707
851
|
});
|
|
852
|
+
if (cancellationSignal?.aborted) {
|
|
853
|
+
return await completeCancelledTurn();
|
|
854
|
+
}
|
|
708
855
|
if (outcome.status === "suspended") {
|
|
709
856
|
return { status: "yielded" };
|
|
710
857
|
}
|
|
@@ -717,9 +864,16 @@ function createApiTurnWorker(options) {
|
|
|
717
864
|
conversation,
|
|
718
865
|
sandboxRef
|
|
719
866
|
});
|
|
867
|
+
if (cancellationSignal) {
|
|
868
|
+
options.cancellation?.park(
|
|
869
|
+
context.conversationId,
|
|
870
|
+
cancellationSignal
|
|
871
|
+
);
|
|
872
|
+
}
|
|
720
873
|
await acknowledge();
|
|
721
874
|
return { status: "completed" };
|
|
722
875
|
}
|
|
876
|
+
finishCancellation();
|
|
723
877
|
reply = outcome.result;
|
|
724
878
|
modelFailureCaptureAttempted = reply.diagnostics.outcome !== "success";
|
|
725
879
|
const finalized = finalizeFailedTurnReplyWithEvent({
|
|
@@ -808,10 +962,12 @@ function createApiTurnWorker(options) {
|
|
|
808
962
|
await acknowledge();
|
|
809
963
|
return { status: "completed" };
|
|
810
964
|
} catch (error) {
|
|
811
|
-
|
|
812
|
-
if (isTurnInputCommitLostError(error) || isTurnInputCommitLostError(cause)) {
|
|
965
|
+
if (hasLostTurnInputCommit(error)) {
|
|
813
966
|
return { status: "lost_lease" };
|
|
814
967
|
}
|
|
968
|
+
if (cancellationSignal?.aborted) {
|
|
969
|
+
return await completeCancelledTurn();
|
|
970
|
+
}
|
|
815
971
|
if (!context.attempt.isFinalAttempt) {
|
|
816
972
|
throw error;
|
|
817
973
|
}
|
|
@@ -850,6 +1006,7 @@ function createApiTurnWorker(options) {
|
|
|
850
1006
|
failureCode,
|
|
851
1007
|
turnId
|
|
852
1008
|
});
|
|
1009
|
+
finishCancellation();
|
|
853
1010
|
await acknowledge();
|
|
854
1011
|
return { status: "completed" };
|
|
855
1012
|
}
|
|
@@ -863,6 +1020,217 @@ function routeApiTurnWork(options) {
|
|
|
863
1020
|
};
|
|
864
1021
|
}
|
|
865
1022
|
|
|
1023
|
+
// src/api/conversations/access.ts
|
|
1024
|
+
import { eq as eq3, inArray as inArray2 } from "drizzle-orm";
|
|
1025
|
+
import { alias } from "drizzle-orm/pg-core";
|
|
1026
|
+
|
|
1027
|
+
// src/api/conversations/membership.ts
|
|
1028
|
+
import { and as and2, eq as eq2, exists, inArray, or } from "drizzle-orm";
|
|
1029
|
+
function conversationHasParticipantUser(userId) {
|
|
1030
|
+
const participant = getDb().select({
|
|
1031
|
+
rootConversationId: juniorConversationParticipants.rootConversationId
|
|
1032
|
+
}).from(juniorConversationParticipants).where(
|
|
1033
|
+
and2(
|
|
1034
|
+
eq2(
|
|
1035
|
+
juniorConversationParticipants.rootConversationId,
|
|
1036
|
+
juniorConversations.conversationId
|
|
1037
|
+
),
|
|
1038
|
+
eq2(juniorConversationParticipants.userId, userId)
|
|
1039
|
+
)
|
|
1040
|
+
);
|
|
1041
|
+
return exists(participant);
|
|
1042
|
+
}
|
|
1043
|
+
function conversationHasParticipantEmail(email) {
|
|
1044
|
+
const participant = getDb().select({
|
|
1045
|
+
rootConversationId: juniorConversationParticipants.rootConversationId
|
|
1046
|
+
}).from(juniorConversationParticipants).innerJoin(
|
|
1047
|
+
juniorUsers,
|
|
1048
|
+
eq2(juniorUsers.id, juniorConversationParticipants.userId)
|
|
1049
|
+
).where(
|
|
1050
|
+
and2(
|
|
1051
|
+
eq2(
|
|
1052
|
+
juniorConversationParticipants.rootConversationId,
|
|
1053
|
+
juniorConversations.conversationId
|
|
1054
|
+
),
|
|
1055
|
+
eq2(juniorUsers.primaryEmailNormalized, email)
|
|
1056
|
+
)
|
|
1057
|
+
);
|
|
1058
|
+
return exists(participant);
|
|
1059
|
+
}
|
|
1060
|
+
function viewerConversationMembership(args) {
|
|
1061
|
+
if (args.actorMatch && args.participantMatch) {
|
|
1062
|
+
return or(args.actorMatch, args.participantMatch);
|
|
1063
|
+
}
|
|
1064
|
+
return args.actorMatch ?? args.participantMatch;
|
|
1065
|
+
}
|
|
1066
|
+
async function conversationIdsWithParticipantUser(db, conversationIds, userId) {
|
|
1067
|
+
if (conversationIds.length === 0) {
|
|
1068
|
+
return /* @__PURE__ */ new Set();
|
|
1069
|
+
}
|
|
1070
|
+
const rows = await db.select({
|
|
1071
|
+
rootConversationId: juniorConversationParticipants.rootConversationId
|
|
1072
|
+
}).from(juniorConversationParticipants).where(
|
|
1073
|
+
and2(
|
|
1074
|
+
eq2(juniorConversationParticipants.userId, userId),
|
|
1075
|
+
inArray(juniorConversationParticipants.rootConversationId, [
|
|
1076
|
+
...conversationIds
|
|
1077
|
+
])
|
|
1078
|
+
)
|
|
1079
|
+
);
|
|
1080
|
+
return new Set(rows.map((row) => row.rootConversationId));
|
|
1081
|
+
}
|
|
1082
|
+
|
|
1083
|
+
// src/api/conversations/access.ts
|
|
1084
|
+
var rootConversation = alias(juniorConversations, "access_root_conversation");
|
|
1085
|
+
var rootDestination = alias(juniorDestinations, "access_root_destination");
|
|
1086
|
+
var rootIdentity = alias(juniorIdentities, "access_root_identity");
|
|
1087
|
+
async function readConversationAccessFromSql(db, conversationIds, viewer) {
|
|
1088
|
+
if (conversationIds.length === 0) return /* @__PURE__ */ new Map();
|
|
1089
|
+
const normalizedViewerEmail = viewer?.email.trim().toLowerCase() || void 0;
|
|
1090
|
+
const rows = await db.select({
|
|
1091
|
+
conversationId: juniorConversations.conversationId,
|
|
1092
|
+
parentConversationId: juniorConversations.parentConversationId,
|
|
1093
|
+
storedRootConversationId: juniorConversations.rootConversationId,
|
|
1094
|
+
rootConversationId: rootConversation.conversationId,
|
|
1095
|
+
rootParentConversationId: rootConversation.parentConversationId,
|
|
1096
|
+
rootRootConversationId: rootConversation.rootConversationId,
|
|
1097
|
+
visibility: rootDestination.visibility,
|
|
1098
|
+
rootEmailNormalized: rootIdentity.emailNormalized,
|
|
1099
|
+
rootEmailVerified: rootIdentity.emailVerified,
|
|
1100
|
+
rootUserId: rootIdentity.userId
|
|
1101
|
+
}).from(juniorConversations).leftJoin(
|
|
1102
|
+
rootConversation,
|
|
1103
|
+
eq3(
|
|
1104
|
+
rootConversation.conversationId,
|
|
1105
|
+
juniorConversations.rootConversationId
|
|
1106
|
+
)
|
|
1107
|
+
).leftJoin(
|
|
1108
|
+
rootDestination,
|
|
1109
|
+
eq3(rootDestination.id, rootConversation.destinationId)
|
|
1110
|
+
).leftJoin(
|
|
1111
|
+
rootIdentity,
|
|
1112
|
+
eq3(rootIdentity.id, rootConversation.actorIdentityId)
|
|
1113
|
+
).where(inArray2(juniorConversations.conversationId, [...conversationIds]));
|
|
1114
|
+
const membershipConversationIds = /* @__PURE__ */ new Set();
|
|
1115
|
+
for (const row of rows) {
|
|
1116
|
+
membershipConversationIds.add(row.conversationId);
|
|
1117
|
+
if (row.storedRootConversationId) {
|
|
1118
|
+
membershipConversationIds.add(row.storedRootConversationId);
|
|
1119
|
+
}
|
|
1120
|
+
}
|
|
1121
|
+
const participantRootIds = viewer !== void 0 ? await conversationIdsWithParticipantUser(
|
|
1122
|
+
db,
|
|
1123
|
+
[...membershipConversationIds],
|
|
1124
|
+
viewer.id
|
|
1125
|
+
) : /* @__PURE__ */ new Set();
|
|
1126
|
+
return new Map(
|
|
1127
|
+
rows.map((row) => {
|
|
1128
|
+
const hasValidRoot = row.rootConversationId !== null && row.rootConversationId === row.storedRootConversationId && row.rootRootConversationId === row.rootConversationId && row.rootParentConversationId === null && (row.parentConversationId !== null || row.storedRootConversationId === row.conversationId);
|
|
1129
|
+
const validRootConversationId = hasValidRoot ? row.rootConversationId ?? void 0 : void 0;
|
|
1130
|
+
const visibility = validRootConversationId === void 0 ? null : row.visibility;
|
|
1131
|
+
const isRootActor = validRootConversationId !== void 0 && viewer !== void 0 && (row.rootUserId === viewer.id || normalizedViewerEmail !== void 0 && row.rootEmailVerified === true && row.rootEmailNormalized === normalizedViewerEmail);
|
|
1132
|
+
const isMaterializedParticipant = participantRootIds.has(row.conversationId) || validRootConversationId !== void 0 && participantRootIds.has(validRootConversationId);
|
|
1133
|
+
const isParticipant = isRootActor || isMaterializedParticipant;
|
|
1134
|
+
const canViewPrivateContent = isParticipant || validRootConversationId !== void 0 && canExposeConversationPayload({
|
|
1135
|
+
visibility: visibility ?? void 0
|
|
1136
|
+
});
|
|
1137
|
+
return [
|
|
1138
|
+
row.conversationId,
|
|
1139
|
+
{
|
|
1140
|
+
canViewPrivateContent,
|
|
1141
|
+
isParticipant,
|
|
1142
|
+
visibility
|
|
1143
|
+
}
|
|
1144
|
+
];
|
|
1145
|
+
})
|
|
1146
|
+
);
|
|
1147
|
+
}
|
|
1148
|
+
|
|
1149
|
+
// src/chat/slack/user.ts
|
|
1150
|
+
import { z as z2 } from "zod";
|
|
1151
|
+
var slackUserInfoSchema = z2.object({
|
|
1152
|
+
ok: z2.literal(true),
|
|
1153
|
+
user: z2.object({
|
|
1154
|
+
name: z2.string().nullish(),
|
|
1155
|
+
profile: z2.object({
|
|
1156
|
+
email: z2.string().nullish(),
|
|
1157
|
+
real_name: z2.string().nullish()
|
|
1158
|
+
}).optional()
|
|
1159
|
+
})
|
|
1160
|
+
});
|
|
1161
|
+
var USER_CACHE_TTL_MS = 5 * 60 * 1e3;
|
|
1162
|
+
var userCache = /* @__PURE__ */ new Map();
|
|
1163
|
+
function userCacheKey(teamId, userId) {
|
|
1164
|
+
return `${teamId}:${userId}`;
|
|
1165
|
+
}
|
|
1166
|
+
function readFromCache(teamId, userId) {
|
|
1167
|
+
const hit = userCache.get(userCacheKey(teamId, userId));
|
|
1168
|
+
if (!hit) return null;
|
|
1169
|
+
if (hit.expiresAt < Date.now()) {
|
|
1170
|
+
userCache.delete(userCacheKey(teamId, userId));
|
|
1171
|
+
return null;
|
|
1172
|
+
}
|
|
1173
|
+
return hit.value;
|
|
1174
|
+
}
|
|
1175
|
+
function writeToCache(teamId, userId, value) {
|
|
1176
|
+
userCache.set(userCacheKey(teamId, userId), {
|
|
1177
|
+
value,
|
|
1178
|
+
expiresAt: Date.now() + USER_CACHE_TTL_MS
|
|
1179
|
+
});
|
|
1180
|
+
}
|
|
1181
|
+
async function lookupSlackUser(teamId, userId) {
|
|
1182
|
+
if (!teamId || !userId) {
|
|
1183
|
+
return null;
|
|
1184
|
+
}
|
|
1185
|
+
const cached = readFromCache(teamId, userId);
|
|
1186
|
+
if (cached) {
|
|
1187
|
+
return cached;
|
|
1188
|
+
}
|
|
1189
|
+
const token = getSlackBotToken();
|
|
1190
|
+
if (!token) {
|
|
1191
|
+
return null;
|
|
1192
|
+
}
|
|
1193
|
+
try {
|
|
1194
|
+
const response = await fetch(
|
|
1195
|
+
`https://slack.com/api/users.info?user=${encodeURIComponent(userId)}`,
|
|
1196
|
+
{
|
|
1197
|
+
headers: {
|
|
1198
|
+
authorization: `Bearer ${token}`
|
|
1199
|
+
}
|
|
1200
|
+
}
|
|
1201
|
+
);
|
|
1202
|
+
if (!response.ok) {
|
|
1203
|
+
logWarn("slack.user.lookup.failed", {
|
|
1204
|
+
"enduser.id": userId,
|
|
1205
|
+
"app.slack.team_id": teamId,
|
|
1206
|
+
"http.response.status_code": response.status
|
|
1207
|
+
});
|
|
1208
|
+
return null;
|
|
1209
|
+
}
|
|
1210
|
+
const parsed = slackUserInfoSchema.safeParse(await response.json());
|
|
1211
|
+
if (!parsed.success) {
|
|
1212
|
+
return null;
|
|
1213
|
+
}
|
|
1214
|
+
const payload = parsed.data;
|
|
1215
|
+
const userName = payload.user.name?.trim() || void 0;
|
|
1216
|
+
const fullName = payload.user.profile?.real_name?.trim() || void 0;
|
|
1217
|
+
const result = {
|
|
1218
|
+
userName,
|
|
1219
|
+
fullName,
|
|
1220
|
+
email: payload.user.profile?.email?.trim() || void 0
|
|
1221
|
+
};
|
|
1222
|
+
writeToCache(teamId, userId, result);
|
|
1223
|
+
return result;
|
|
1224
|
+
} catch (error) {
|
|
1225
|
+
logWarn("slack.user.lookup.failed", {
|
|
1226
|
+
"enduser.id": userId,
|
|
1227
|
+
"app.slack.team_id": teamId,
|
|
1228
|
+
"exception.message": error instanceof Error ? error.message : String(error)
|
|
1229
|
+
});
|
|
1230
|
+
return null;
|
|
1231
|
+
}
|
|
1232
|
+
}
|
|
1233
|
+
|
|
866
1234
|
// src/chat/slack/conversation-context.ts
|
|
867
1235
|
function normalizeConversationName(type, channelName) {
|
|
868
1236
|
const trimmed = channelName?.trim();
|
|
@@ -957,9 +1325,20 @@ export {
|
|
|
957
1325
|
resolveSlackConversationContext,
|
|
958
1326
|
resolveSlackConversationContextFromThreadId,
|
|
959
1327
|
formatSlackConversationRedactedLabel,
|
|
1328
|
+
createApiTurnCancellation,
|
|
1329
|
+
apiTurnIdForMessage,
|
|
960
1330
|
webActorFromEmail,
|
|
1331
|
+
recordApiConversationActivity,
|
|
961
1332
|
createAndEnqueueApiConversation,
|
|
962
1333
|
appendAndEnqueueApiConversationMessage,
|
|
963
1334
|
createApiTurnWorker,
|
|
964
|
-
routeApiTurnWork
|
|
1335
|
+
routeApiTurnWork,
|
|
1336
|
+
conversationHasParticipantUser,
|
|
1337
|
+
conversationHasParticipantEmail,
|
|
1338
|
+
viewerConversationMembership,
|
|
1339
|
+
readConversationAccessFromSql,
|
|
1340
|
+
createPersonalToken,
|
|
1341
|
+
listPersonalTokens,
|
|
1342
|
+
revokePersonalToken,
|
|
1343
|
+
authenticatePersonalToken
|
|
965
1344
|
};
|