@sentry/junior 0.188.0 → 0.189.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{agent-hooks-5RQEVLQD.js → agent-hooks-6QRYH6PX.js} +11 -11
- package/dist/api/acp/conversations.d.ts +2 -2
- package/dist/api/conversations/list.d.ts +7 -0
- package/dist/api/people/shared.d.ts +54 -0
- package/dist/api.js +41 -52
- package/dist/app.d.ts +1 -1
- package/dist/app.js +1003 -346
- package/dist/{catalog-runtime-L4A6BJ3U.js → catalog-runtime-IETJLJFG.js} +4 -4
- package/dist/chat/agent/resume.d.ts +0 -2
- package/dist/chat/agent/types.d.ts +8 -9
- package/dist/chat/agent-dispatch/work.d.ts +1 -10
- package/dist/chat/agent-invocations/spawn.d.ts +1 -1
- package/dist/chat/agent-invocations/types.d.ts +296 -96
- package/dist/chat/app/conversation-work.d.ts +16 -8
- package/dist/chat/conversation-privacy.d.ts +7 -7
- package/dist/chat/conversations/destination.d.ts +12 -0
- package/dist/chat/conversations/projection.d.ts +2 -2
- package/dist/chat/conversations/sql/location.d.ts +13 -4
- package/dist/chat/{api-turns → conversations}/stop.d.ts +2 -2
- package/dist/chat/conversations/store.d.ts +4 -4
- package/dist/chat/conversations/structured-events.d.ts +1 -1
- package/dist/chat/{api-turns/authorization.d.ts → conversations/web-authorization.d.ts} +1 -0
- package/dist/chat/conversations/web-input.d.ts +98 -0
- package/dist/chat/conversations/web-mailbox.d.ts +12 -0
- package/dist/chat/event-tasks/tool-support.d.ts +0 -6
- package/dist/chat/providers/slack/runtime.d.ts +1 -0
- package/dist/chat/providers/slack/system-turn.d.ts +8 -0
- package/dist/chat/resource-events/actor.d.ts +13 -12
- package/dist/chat/resource-events/notification.d.ts +1 -1
- package/dist/chat/services/guardian-action-policy.d.ts +1 -1
- package/dist/chat/services/turn-session-routing.d.ts +2 -2
- package/dist/chat/task-execution/assistant-message.d.ts +32 -0
- package/dist/chat/task-execution/conversation-turn.d.ts +8 -0
- package/dist/chat/{api-turns → task-execution}/mailbox-input.d.ts +1 -2
- package/dist/chat/task-execution/mailbox-turn.d.ts +42 -0
- package/dist/chat/task-execution/paused-turn.d.ts +1 -1
- package/dist/chat/task-execution/slack-work.d.ts +0 -2
- package/dist/{chunk-AW77HH54.js → chunk-4RHJ3CXR.js} +14 -295
- package/dist/{chunk-ZX7RSBQ6.js → chunk-5CXCGDIL.js} +1 -1
- package/dist/{chunk-QVH6FW3T.js → chunk-5VWAY27R.js} +7 -3
- package/dist/{chunk-XJIPMHKI.js → chunk-6AUR6PWR.js} +3 -3
- package/dist/{chunk-QDHJTKPE.js → chunk-7R5JRPCY.js} +8 -6
- package/dist/{chunk-6JZGXLL2.js → chunk-AC7MDOJW.js} +5 -7
- package/dist/{chunk-Q7O74WOO.js → chunk-BYBSI5TI.js} +2 -2
- package/dist/{chunk-VFZAUQ5V.js → chunk-CMWIMFM6.js} +4 -182
- package/dist/{chunk-IUUQWIG2.js → chunk-CUYFCI4G.js} +59 -11
- package/dist/chunk-DA2KGZZI.js +13 -0
- package/dist/{chunk-FIUCHYT6.js → chunk-DYXVAO2E.js} +1 -1
- package/dist/{chunk-NZLZ4KYZ.js → chunk-E3PDZPGV.js} +1 -1
- package/dist/{chunk-Z5SKZE5B.js → chunk-GP32KD2J.js} +4 -12
- package/dist/{chunk-XI4PVY2T.js → chunk-KN32OWL2.js} +5 -5
- package/dist/chunk-LHKZKAZ4.js +285 -0
- package/dist/{chunk-6YGFBU3A.js → chunk-MFSET7Q7.js} +44 -334
- package/dist/{chunk-QCGNHSMA.js → chunk-MI6ZBDTS.js} +6 -6
- package/dist/{chunk-CIG6WI7Q.js → chunk-MRXMFB4L.js} +5 -5
- package/dist/{chunk-CVBDVIF4.js → chunk-MVPLEPSI.js} +1 -1
- package/dist/{chunk-32XSSFZV.js → chunk-NY2F7BZA.js} +1 -1
- package/dist/chunk-O35GZCKL.js +311 -0
- package/dist/{chunk-HCIVBCP5.js → chunk-OEIITG5F.js} +1 -1
- package/dist/{chunk-UVUXFRYN.js → chunk-PCPSX4ES.js} +209 -7
- package/dist/{chunk-QIMN3FXL.js → chunk-PY3EIEAF.js} +1 -1
- package/dist/{chunk-RMB7CERI.js → chunk-QNIOBNOY.js} +282 -121
- package/dist/{chunk-WGZJ5SU4.js → chunk-S3SGZUOI.js} +1 -1
- package/dist/chunk-SR6OMO5C.js +703 -0
- package/dist/{chunk-CDSLOYOR.js → chunk-UCWO5MKP.js} +6 -6
- package/dist/{chunk-PSHMPRIU.js → chunk-VKQWU4PO.js} +2 -2
- package/dist/{chunk-X55FOVZO.js → chunk-WDHPN2GH.js} +3 -3
- package/dist/{chunk-WW2Y7JIA.js → chunk-WMUAKAIF.js} +43 -44
- package/dist/{chunk-MQDIIFZM.js → chunk-WN6VLHOU.js} +12 -5
- package/dist/{chunk-W42ENMJW.js → chunk-WWYUYKKV.js} +1 -1
- package/dist/{chunk-ERZMACYA.js → chunk-WYDF6PGK.js} +3 -3
- package/dist/{chunk-SC67CH3C.js → chunk-YNZPOFF3.js} +14 -17
- package/dist/{chunk-3HEZWDS5.js → chunk-YYSEH2WC.js} +2 -2
- package/dist/{chunk-W2DDJCPS.js → chunk-YYZNESC3.js} +8 -8
- package/dist/{chunk-CZRWU4ZM.js → chunk-ZUPDSJUE.js} +2 -5
- package/dist/{chunk-QIOUTXTZ.js → chunk-ZV6PFUA4.js} +1 -1
- package/dist/cli/chat.js +34 -33
- package/dist/cli/check.js +5 -5
- package/dist/cli/plugins.js +12 -12
- package/dist/cli/snapshot-create.js +9 -9
- package/dist/cli/upgrade.js +6 -6
- package/dist/db/schema/agent-invocations.d.ts +0 -36
- package/dist/db/schema/conversations.d.ts +62 -0
- package/dist/db/schema.d.ts +31 -36
- package/dist/{db-YPRZAA2T.js → db-LPJKR2EO.js} +6 -6
- package/dist/instrumentation.js +3 -3
- package/dist/nitro.js +6 -6
- package/dist/{oauth-callback-server-DBBIR3XG.js → oauth-callback-server-DMXF4TTQ.js} +30 -28
- package/dist/{runner-FKVFFPLO.js → runner-L5DPVYCN.js} +25 -21
- package/dist/{skills-4RBAZEMV.js → skills-NXG3LBCK.js} +5 -5
- package/dist/spawn-VRUIGKWW.js +42 -0
- package/dist/task-runner-7YTBSPXW.js +36 -0
- package/dist/{validation-E6Z5LHZM.js → validation-CH36LTOS.js} +5 -5
- package/dist/version.js +1 -1
- package/dist/{work-OFTXWOK4.js → work-HBC5Y5VZ.js} +22 -21
- package/dist/{worker-OMWNOKTJ.js → worker-5RDZHLXH.js} +10 -10
- package/migrations/0037_conversation_location.sql +23 -0
- package/migrations/meta/0037_snapshot.json +3192 -0
- package/migrations/meta/_journal.json +7 -0
- package/package.json +5 -5
- package/dist/chat/api-turns/assistant-message.d.ts +0 -12
- package/dist/chat/api-turns/cancellation.d.ts +0 -24
- package/dist/chat/api-turns/routing.d.ts +0 -51
- package/dist/chat/api-turns/work.d.ts +0 -99
- package/dist/chat/task-execution/slack-resource-event.d.ts +0 -55
- package/dist/chunk-6FGN7ZEG.js +0 -221
- package/dist/chunk-LODJKINF.js +0 -1502
- package/dist/spawn-CO7QFCNJ.js +0 -41
- package/dist/task-runner-DUGZGI7V.js +0 -34
package/dist/chunk-LODJKINF.js
DELETED
|
@@ -1,1502 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
createWebAuthorization,
|
|
3
|
-
deleteWebAuthorization
|
|
4
|
-
} from "./chunk-PSHMPRIU.js";
|
|
5
|
-
import {
|
|
6
|
-
AgentRunError,
|
|
7
|
-
executeTurn
|
|
8
|
-
} from "./chunk-ERZMACYA.js";
|
|
9
|
-
import {
|
|
10
|
-
buildDeliveredTurnStatePatch,
|
|
11
|
-
completeAuthPauseTurn,
|
|
12
|
-
finalizeFailedTurnReplyWithEvent,
|
|
13
|
-
getTurnUserMessage
|
|
14
|
-
} from "./chunk-QDHJTKPE.js";
|
|
15
|
-
import {
|
|
16
|
-
processPluginTask,
|
|
17
|
-
scheduleSessionCompletedPluginTasks
|
|
18
|
-
} from "./chunk-AW77HH54.js";
|
|
19
|
-
import {
|
|
20
|
-
ConversationTurnLifecycleService
|
|
21
|
-
} from "./chunk-W42ENMJW.js";
|
|
22
|
-
import {
|
|
23
|
-
coerceThreadConversationState,
|
|
24
|
-
getPersistedSandboxState,
|
|
25
|
-
getPersistedThreadState,
|
|
26
|
-
persistThreadStateById
|
|
27
|
-
} from "./chunk-CIG6WI7Q.js";
|
|
28
|
-
import {
|
|
29
|
-
healthReportSchema
|
|
30
|
-
} from "./chunk-UTL3MHPC.js";
|
|
31
|
-
import {
|
|
32
|
-
RESOURCE_EVENT_MESSAGE_AUTHOR,
|
|
33
|
-
RESOURCE_EVENT_SYSTEM_ACTOR,
|
|
34
|
-
isResourceEventConversationMessage,
|
|
35
|
-
isResourceEventMailboxMetadata
|
|
36
|
-
} from "./chunk-VFZAUQ5V.js";
|
|
37
|
-
import {
|
|
38
|
-
parseSlackThreadId
|
|
39
|
-
} from "./chunk-CVBDVIF4.js";
|
|
40
|
-
import {
|
|
41
|
-
appendAndEnqueueExclusiveInboundMessage,
|
|
42
|
-
appendAndEnqueueInboundMessage
|
|
43
|
-
} from "./chunk-MQDIIFZM.js";
|
|
44
|
-
import {
|
|
45
|
-
TurnInputCommitLostError,
|
|
46
|
-
buildDeterministicTurnId,
|
|
47
|
-
clearPendingAuth,
|
|
48
|
-
getAssistantReplyText,
|
|
49
|
-
getConversationTurnBoundaryError,
|
|
50
|
-
isTurnInputCommitLostError,
|
|
51
|
-
markTurnClosed,
|
|
52
|
-
markTurnFailed,
|
|
53
|
-
startActiveTurn
|
|
54
|
-
} from "./chunk-Z5SKZE5B.js";
|
|
55
|
-
import {
|
|
56
|
-
abandonTurnRecord,
|
|
57
|
-
buildConversationContext,
|
|
58
|
-
commitAcceptedReply,
|
|
59
|
-
getTurnRecord,
|
|
60
|
-
hydrateConversationMessages,
|
|
61
|
-
listTurnSummaries,
|
|
62
|
-
loadProjection,
|
|
63
|
-
markConversationMessage,
|
|
64
|
-
normalizeConversationText,
|
|
65
|
-
persistConversationMessages,
|
|
66
|
-
persistWithRetry,
|
|
67
|
-
recordDeliveredAssistantMessage,
|
|
68
|
-
saveTurnCheckpoint,
|
|
69
|
-
upsertConversationMessage
|
|
70
|
-
} from "./chunk-6YGFBU3A.js";
|
|
71
|
-
import {
|
|
72
|
-
normalizeSlackConversationId
|
|
73
|
-
} from "./chunk-QCGNHSMA.js";
|
|
74
|
-
import {
|
|
75
|
-
getConversationEventStore,
|
|
76
|
-
getConversationStore,
|
|
77
|
-
getDb
|
|
78
|
-
} from "./chunk-IUUQWIG2.js";
|
|
79
|
-
import {
|
|
80
|
-
juniorConversationParticipants,
|
|
81
|
-
juniorConversations,
|
|
82
|
-
juniorIdentities,
|
|
83
|
-
juniorUsers
|
|
84
|
-
} from "./chunk-QVH6FW3T.js";
|
|
85
|
-
import {
|
|
86
|
-
juniorDestinations
|
|
87
|
-
} from "./chunk-VFUK3X5B.js";
|
|
88
|
-
import {
|
|
89
|
-
getSlackBotToken
|
|
90
|
-
} from "./chunk-Q7O74WOO.js";
|
|
91
|
-
import {
|
|
92
|
-
canExposeConversationPayload
|
|
93
|
-
} from "./chunk-FIUCHYT6.js";
|
|
94
|
-
import {
|
|
95
|
-
credentialContextForActor
|
|
96
|
-
} from "./chunk-CEMMLV5L.js";
|
|
97
|
-
import {
|
|
98
|
-
createActor
|
|
99
|
-
} from "./chunk-N7TMSOZH.js";
|
|
100
|
-
import {
|
|
101
|
-
logException,
|
|
102
|
-
logWarn,
|
|
103
|
-
setTags,
|
|
104
|
-
withLogContext
|
|
105
|
-
} from "./chunk-QIMN3FXL.js";
|
|
106
|
-
|
|
107
|
-
// src/handlers/health.ts
|
|
108
|
-
function readHealthReport() {
|
|
109
|
-
return healthReportSchema.parse({
|
|
110
|
-
status: "ok",
|
|
111
|
-
service: "junior",
|
|
112
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
113
|
-
});
|
|
114
|
-
}
|
|
115
|
-
function GET() {
|
|
116
|
-
return Response.json(readHealthReport());
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
// src/chat/attachments/vercel.ts
|
|
120
|
-
import { del, get, put } from "@vercel/blob";
|
|
121
|
-
function createVercelAttachmentStorage() {
|
|
122
|
-
return {
|
|
123
|
-
provider: "vercel-blob",
|
|
124
|
-
async put(input) {
|
|
125
|
-
await put(input.key, input.body, {
|
|
126
|
-
access: "private",
|
|
127
|
-
addRandomSuffix: false,
|
|
128
|
-
allowOverwrite: true,
|
|
129
|
-
contentType: input.contentType
|
|
130
|
-
});
|
|
131
|
-
},
|
|
132
|
-
async get(key) {
|
|
133
|
-
const result = await get(key, { access: "private" });
|
|
134
|
-
return result?.statusCode === 200 ? result.stream : null;
|
|
135
|
-
},
|
|
136
|
-
async delete(keys) {
|
|
137
|
-
if (keys.length === 0) return;
|
|
138
|
-
await del(keys);
|
|
139
|
-
}
|
|
140
|
-
};
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
// src/chat/api-turns/routing.ts
|
|
144
|
-
import { z } from "zod";
|
|
145
|
-
import {
|
|
146
|
-
createLocalSource,
|
|
147
|
-
createWebSource
|
|
148
|
-
} from "@sentry/junior-plugin-api";
|
|
149
|
-
var apiTurnMailboxMetadataSchema = z.object({
|
|
150
|
-
authorEmail: z.string().email(),
|
|
151
|
-
authorFullName: z.string().min(1).optional(),
|
|
152
|
-
authorUserId: z.string().min(1),
|
|
153
|
-
authorUserName: z.string().min(1).optional(),
|
|
154
|
-
kind: z.literal("api_turn"),
|
|
155
|
-
messageId: z.string().min(1)
|
|
156
|
-
}).strict();
|
|
157
|
-
function turnIdentityFromConversationMessage(message) {
|
|
158
|
-
if (isResourceEventConversationMessage(message)) {
|
|
159
|
-
return {
|
|
160
|
-
actor: RESOURCE_EVENT_SYSTEM_ACTOR,
|
|
161
|
-
author: message.author ?? RESOURCE_EVENT_MESSAGE_AUTHOR,
|
|
162
|
-
source: "resource_event"
|
|
163
|
-
};
|
|
164
|
-
}
|
|
165
|
-
const actor = createActor(message.author, {
|
|
166
|
-
platform: "web",
|
|
167
|
-
userId: message.author?.userId
|
|
168
|
-
});
|
|
169
|
-
if (actor?.platform !== "web") {
|
|
170
|
-
return void 0;
|
|
171
|
-
}
|
|
172
|
-
return {
|
|
173
|
-
actor,
|
|
174
|
-
author: message.author ?? { userId: actor.userId },
|
|
175
|
-
source: "web"
|
|
176
|
-
};
|
|
177
|
-
}
|
|
178
|
-
function sourceFromTurnInput(args) {
|
|
179
|
-
switch (args.source) {
|
|
180
|
-
case "resource_event":
|
|
181
|
-
return createLocalSource(args.conversationId);
|
|
182
|
-
case "web":
|
|
183
|
-
return createWebSource(
|
|
184
|
-
args.conversationId,
|
|
185
|
-
args.visibility === "private" ? "private" : "public"
|
|
186
|
-
);
|
|
187
|
-
}
|
|
188
|
-
}
|
|
189
|
-
async function getActiveApiTurnId(conversationId) {
|
|
190
|
-
const summaries = await listTurnSummaries(conversationId);
|
|
191
|
-
const active = summaries.filter(
|
|
192
|
-
(summary) => summary.surface === "api" && !summary.dispatchId && (summary.state === "paused" || summary.state === "running")
|
|
193
|
-
);
|
|
194
|
-
if (active.length > 1) {
|
|
195
|
-
throw new Error(
|
|
196
|
-
`Conversation ${conversationId} has multiple active web turns`
|
|
197
|
-
);
|
|
198
|
-
}
|
|
199
|
-
const turnId = active[0]?.turnId;
|
|
200
|
-
if (!turnId) return void 0;
|
|
201
|
-
const record = await getTurnRecord(conversationId, turnId);
|
|
202
|
-
return record && record.surface === "api" && !record.dispatchId && (record.state === "paused" || record.state === "running") ? turnId : void 0;
|
|
203
|
-
}
|
|
204
|
-
async function getAuthPausedApiTurnId(conversationId) {
|
|
205
|
-
const turnId = await getActiveApiTurnId(conversationId);
|
|
206
|
-
if (!turnId) return void 0;
|
|
207
|
-
const record = await getTurnRecord(conversationId, turnId);
|
|
208
|
-
return record?.state === "paused" && record.resumeReason === "auth" ? turnId : void 0;
|
|
209
|
-
}
|
|
210
|
-
function parseApiTurnMessages(messages) {
|
|
211
|
-
if (messages.length === 0) {
|
|
212
|
-
return [];
|
|
213
|
-
}
|
|
214
|
-
const parsed = messages.map((message) => ({
|
|
215
|
-
message,
|
|
216
|
-
metadata: apiTurnMailboxMetadataSchema.safeParse(message.input.metadata)
|
|
217
|
-
}));
|
|
218
|
-
if (parsed.every((entry) => !entry.metadata.success)) {
|
|
219
|
-
return [];
|
|
220
|
-
}
|
|
221
|
-
if (parsed.some((entry) => !entry.metadata.success)) {
|
|
222
|
-
throw new Error("A Turn cannot combine different kinds of input");
|
|
223
|
-
}
|
|
224
|
-
return parsed.map((entry) => {
|
|
225
|
-
if (!entry.metadata.success) {
|
|
226
|
-
throw new Error("User message has invalid metadata");
|
|
227
|
-
}
|
|
228
|
-
const metadata = entry.metadata.data;
|
|
229
|
-
const actor = {
|
|
230
|
-
platform: "web",
|
|
231
|
-
userId: metadata.authorUserId,
|
|
232
|
-
email: metadata.authorEmail.trim().toLowerCase(),
|
|
233
|
-
...metadata.authorFullName ? { fullName: metadata.authorFullName } : void 0,
|
|
234
|
-
...metadata.authorUserName ? { userName: metadata.authorUserName } : void 0
|
|
235
|
-
};
|
|
236
|
-
return {
|
|
237
|
-
actor,
|
|
238
|
-
author: {
|
|
239
|
-
email: actor.email,
|
|
240
|
-
...actor.fullName ? { fullName: actor.fullName } : void 0,
|
|
241
|
-
userId: actor.userId,
|
|
242
|
-
...actor.userName ? { userName: actor.userName } : void 0
|
|
243
|
-
},
|
|
244
|
-
message: entry.message,
|
|
245
|
-
source: "web"
|
|
246
|
-
};
|
|
247
|
-
});
|
|
248
|
-
}
|
|
249
|
-
function parseResourceEventMessages(messages) {
|
|
250
|
-
if (messages.length === 0) {
|
|
251
|
-
return [];
|
|
252
|
-
}
|
|
253
|
-
if (!messages.every(
|
|
254
|
-
(message) => message.source === "resource_event" && isResourceEventMailboxMetadata(message.input.metadata)
|
|
255
|
-
)) {
|
|
256
|
-
return [];
|
|
257
|
-
}
|
|
258
|
-
return messages.map((message) => {
|
|
259
|
-
if (!isResourceEventMailboxMetadata(message.input.metadata)) {
|
|
260
|
-
throw new Error("Resource event has invalid metadata");
|
|
261
|
-
}
|
|
262
|
-
return {
|
|
263
|
-
actor: RESOURCE_EVENT_SYSTEM_ACTOR,
|
|
264
|
-
author: RESOURCE_EVENT_MESSAGE_AUTHOR,
|
|
265
|
-
message,
|
|
266
|
-
source: "resource_event"
|
|
267
|
-
};
|
|
268
|
-
});
|
|
269
|
-
}
|
|
270
|
-
async function resolveApiTurnWork(context) {
|
|
271
|
-
const batch = parseApiTurnMessages(context.attempt.messages);
|
|
272
|
-
if (batch.length > 0) {
|
|
273
|
-
return { kind: "mailbox", batch };
|
|
274
|
-
}
|
|
275
|
-
if (context.destination?.platform === "local") {
|
|
276
|
-
const resourceBatch = parseResourceEventMessages(context.attempt.messages);
|
|
277
|
-
if (resourceBatch.length > 0) {
|
|
278
|
-
return { kind: "mailbox", batch: resourceBatch };
|
|
279
|
-
}
|
|
280
|
-
}
|
|
281
|
-
if (context.attempt.messages.length > 0) {
|
|
282
|
-
return void 0;
|
|
283
|
-
}
|
|
284
|
-
const turnId = await getActiveApiTurnId(context.conversationId);
|
|
285
|
-
return turnId ? { kind: "resume", turnId } : void 0;
|
|
286
|
-
}
|
|
287
|
-
async function isApiTurnWork(context) {
|
|
288
|
-
return await resolveApiTurnWork(context) !== void 0;
|
|
289
|
-
}
|
|
290
|
-
|
|
291
|
-
// src/chat/api-turns/work.ts
|
|
292
|
-
import { createHash } from "crypto";
|
|
293
|
-
import {
|
|
294
|
-
localDestinationSchema
|
|
295
|
-
} from "@sentry/junior-plugin-api";
|
|
296
|
-
|
|
297
|
-
// src/chat/api-turns/assistant-message.ts
|
|
298
|
-
async function commitAssistantMessage(args) {
|
|
299
|
-
const conversationMessageId = recordDeliveredAssistantMessage({
|
|
300
|
-
conversation: args.conversation,
|
|
301
|
-
sessionId: args.sessionId,
|
|
302
|
-
source: args.source,
|
|
303
|
-
text: args.text,
|
|
304
|
-
userMessageId: args.userMessageId
|
|
305
|
-
});
|
|
306
|
-
try {
|
|
307
|
-
await persistWithRetry(
|
|
308
|
-
() => commitAcceptedReply({
|
|
309
|
-
...args.agentMessage ? { agentMessage: args.agentMessage } : void 0,
|
|
310
|
-
conversation: args.conversation,
|
|
311
|
-
conversationMessageId,
|
|
312
|
-
conversationId: args.conversationId
|
|
313
|
-
})
|
|
314
|
-
);
|
|
315
|
-
} catch (error) {
|
|
316
|
-
logException(
|
|
317
|
-
new Error("Assistant message persistence failed"),
|
|
318
|
-
"conversation.assistant.message_persist.failed",
|
|
319
|
-
{
|
|
320
|
-
"error.type": error instanceof Error ? error.name : typeof error
|
|
321
|
-
}
|
|
322
|
-
);
|
|
323
|
-
}
|
|
324
|
-
}
|
|
325
|
-
|
|
326
|
-
// src/chat/api-turns/cancellation.ts
|
|
327
|
-
function createApiTurnCancellation() {
|
|
328
|
-
const active = /* @__PURE__ */ new Map();
|
|
329
|
-
return {
|
|
330
|
-
begin(conversationId) {
|
|
331
|
-
if (active.has(conversationId)) {
|
|
332
|
-
return void 0;
|
|
333
|
-
}
|
|
334
|
-
const controller = new AbortController();
|
|
335
|
-
active.set(conversationId, {
|
|
336
|
-
connected: true,
|
|
337
|
-
controller,
|
|
338
|
-
parked: false
|
|
339
|
-
});
|
|
340
|
-
return controller.signal;
|
|
341
|
-
},
|
|
342
|
-
cancel(conversationId) {
|
|
343
|
-
const entry = active.get(conversationId);
|
|
344
|
-
if (!entry) {
|
|
345
|
-
return false;
|
|
346
|
-
}
|
|
347
|
-
entry.controller.abort(new DOMException("Turn cancelled", "AbortError"));
|
|
348
|
-
return true;
|
|
349
|
-
},
|
|
350
|
-
disconnect(conversationId, signal) {
|
|
351
|
-
const entry = active.get(conversationId);
|
|
352
|
-
if (entry?.controller.signal !== signal) {
|
|
353
|
-
return;
|
|
354
|
-
}
|
|
355
|
-
entry.connected = false;
|
|
356
|
-
if (entry.parked) {
|
|
357
|
-
active.delete(conversationId);
|
|
358
|
-
}
|
|
359
|
-
},
|
|
360
|
-
finish(conversationId, signal) {
|
|
361
|
-
const entry = active.get(conversationId);
|
|
362
|
-
if (entry?.controller.signal === signal) {
|
|
363
|
-
active.delete(conversationId);
|
|
364
|
-
}
|
|
365
|
-
},
|
|
366
|
-
park(conversationId, signal) {
|
|
367
|
-
const entry = active.get(conversationId);
|
|
368
|
-
if (entry?.controller.signal !== signal) {
|
|
369
|
-
return;
|
|
370
|
-
}
|
|
371
|
-
entry.parked = true;
|
|
372
|
-
if (!entry.connected) {
|
|
373
|
-
active.delete(conversationId);
|
|
374
|
-
}
|
|
375
|
-
},
|
|
376
|
-
signal(conversationId) {
|
|
377
|
-
return active.get(conversationId)?.controller.signal;
|
|
378
|
-
}
|
|
379
|
-
};
|
|
380
|
-
}
|
|
381
|
-
async function completeCancelledApiTurn(args) {
|
|
382
|
-
try {
|
|
383
|
-
const pendingAuthorization = args.conversation.processing.pendingAuth?.sessionId === args.turnId ? args.conversation.processing.pendingAuth : void 0;
|
|
384
|
-
await abandonTurnRecord({
|
|
385
|
-
conversationId: args.conversationId,
|
|
386
|
-
turnId: args.turnId,
|
|
387
|
-
errorMessage: "Turn cancelled"
|
|
388
|
-
});
|
|
389
|
-
clearPendingAuth(args.conversation, args.turnId);
|
|
390
|
-
markConversationMessage(args.conversation, args.userMessageId, {
|
|
391
|
-
replied: false,
|
|
392
|
-
skippedReason: "turn cancelled"
|
|
393
|
-
});
|
|
394
|
-
markTurnClosed({
|
|
395
|
-
conversation: args.conversation,
|
|
396
|
-
nowMs: Date.now(),
|
|
397
|
-
sessionId: args.turnId
|
|
398
|
-
});
|
|
399
|
-
if (pendingAuthorization) {
|
|
400
|
-
await deleteWebAuthorization({
|
|
401
|
-
actorId: pendingAuthorization.actorId,
|
|
402
|
-
conversationId: args.conversationId
|
|
403
|
-
});
|
|
404
|
-
}
|
|
405
|
-
await persistThreadStateById(args.conversationId, {
|
|
406
|
-
conversation: args.conversation,
|
|
407
|
-
sandboxRef: args.sandboxRef
|
|
408
|
-
});
|
|
409
|
-
await new ConversationTurnLifecycleService(
|
|
410
|
-
getConversationEventStore()
|
|
411
|
-
).complete({
|
|
412
|
-
conversationId: args.conversationId,
|
|
413
|
-
createdAtMs: Date.now(),
|
|
414
|
-
outcome: "cancelled",
|
|
415
|
-
turnId: args.turnId
|
|
416
|
-
});
|
|
417
|
-
} finally {
|
|
418
|
-
if (args.signal) {
|
|
419
|
-
args.cancellation?.finish(args.conversationId, args.signal);
|
|
420
|
-
}
|
|
421
|
-
}
|
|
422
|
-
await args.acknowledge();
|
|
423
|
-
}
|
|
424
|
-
|
|
425
|
-
// src/chat/api-turns/mailbox-input.ts
|
|
426
|
-
function joinMailboxText(messages) {
|
|
427
|
-
return messages.map((message) => message.input.text.trim()).filter(Boolean).join("\n\n");
|
|
428
|
-
}
|
|
429
|
-
|
|
430
|
-
// src/chat/api-turns/work.ts
|
|
431
|
-
function normalizeEmail(email) {
|
|
432
|
-
return email.trim().toLowerCase();
|
|
433
|
-
}
|
|
434
|
-
function stableHex(...parts) {
|
|
435
|
-
return createHash("sha256").update(parts.join("\0")).digest("hex").slice(0, 24);
|
|
436
|
-
}
|
|
437
|
-
function createApiConversationId(args) {
|
|
438
|
-
return `local:web:${stableHex(
|
|
439
|
-
normalizeEmail(args.actorEmail),
|
|
440
|
-
args.idempotencyKey
|
|
441
|
-
)}`;
|
|
442
|
-
}
|
|
443
|
-
function apiConversationMessageId(args) {
|
|
444
|
-
return `api-msg:${stableHex(args.conversationId, args.idempotencyKey)}`;
|
|
445
|
-
}
|
|
446
|
-
function apiTurnIdForMessage(messageId) {
|
|
447
|
-
return buildDeterministicTurnId(messageId);
|
|
448
|
-
}
|
|
449
|
-
function requireLocalDestination(conversationId) {
|
|
450
|
-
const parsed = localDestinationSchema.safeParse({
|
|
451
|
-
platform: "local",
|
|
452
|
-
conversationId
|
|
453
|
-
});
|
|
454
|
-
if (!parsed.success) {
|
|
455
|
-
throw new Error(`Invalid local conversation id: ${conversationId}`);
|
|
456
|
-
}
|
|
457
|
-
return parsed.data;
|
|
458
|
-
}
|
|
459
|
-
function resolveApiTurnDestination(args) {
|
|
460
|
-
if (args.existing) {
|
|
461
|
-
return args.existing;
|
|
462
|
-
}
|
|
463
|
-
return requireLocalDestination(args.conversationId);
|
|
464
|
-
}
|
|
465
|
-
function storedActorFromApi(actor) {
|
|
466
|
-
return {
|
|
467
|
-
...actor.email ? { email: normalizeEmail(actor.email) } : void 0,
|
|
468
|
-
...actor.fullName ? { fullName: actor.fullName } : void 0
|
|
469
|
-
};
|
|
470
|
-
}
|
|
471
|
-
function webActorFromEmail(email, profile) {
|
|
472
|
-
const normalized = normalizeEmail(email);
|
|
473
|
-
return {
|
|
474
|
-
platform: "web",
|
|
475
|
-
userId: `dashboard:${stableHex(normalized)}`,
|
|
476
|
-
email: normalized,
|
|
477
|
-
...profile?.fullName ? { fullName: profile.fullName } : void 0,
|
|
478
|
-
...profile?.userName ? { userName: profile.userName } : void 0
|
|
479
|
-
};
|
|
480
|
-
}
|
|
481
|
-
function buildApiTurnInboundMessage(args) {
|
|
482
|
-
const text = args.message.trim();
|
|
483
|
-
if (!text) {
|
|
484
|
-
throw new Error("API conversation message must not be empty");
|
|
485
|
-
}
|
|
486
|
-
if (!args.actor.email) {
|
|
487
|
-
throw new Error("API conversation actor requires a verified email");
|
|
488
|
-
}
|
|
489
|
-
const destination = resolveApiTurnDestination({
|
|
490
|
-
conversationId: args.conversationId,
|
|
491
|
-
existing: args.destination
|
|
492
|
-
});
|
|
493
|
-
const nowMs = args.nowMs ?? Date.now();
|
|
494
|
-
return {
|
|
495
|
-
conversationId: args.conversationId,
|
|
496
|
-
createdAtMs: args.createdAtMs ?? nowMs,
|
|
497
|
-
delivery: "defer",
|
|
498
|
-
destination,
|
|
499
|
-
inboundMessageId: args.messageId,
|
|
500
|
-
input: {
|
|
501
|
-
authorId: args.actor.userId,
|
|
502
|
-
text,
|
|
503
|
-
metadata: {
|
|
504
|
-
authorEmail: normalizeEmail(args.actor.email),
|
|
505
|
-
...args.actor.fullName && { authorFullName: args.actor.fullName },
|
|
506
|
-
authorUserId: args.actor.userId,
|
|
507
|
-
...args.actor.userName && { authorUserName: args.actor.userName },
|
|
508
|
-
kind: "api_turn",
|
|
509
|
-
messageId: args.messageId
|
|
510
|
-
}
|
|
511
|
-
},
|
|
512
|
-
receivedAtMs: nowMs,
|
|
513
|
-
publishExternally: false,
|
|
514
|
-
source: "web"
|
|
515
|
-
};
|
|
516
|
-
}
|
|
517
|
-
async function recordApiConversationActivity(args) {
|
|
518
|
-
const store = args.conversationStore ?? getConversationStore();
|
|
519
|
-
const existing = await store.get({ conversationId: args.conversationId });
|
|
520
|
-
const destination = resolveApiTurnDestination({
|
|
521
|
-
conversationId: args.conversationId,
|
|
522
|
-
existing: existing?.destination
|
|
523
|
-
});
|
|
524
|
-
const isNewRoot = !existing;
|
|
525
|
-
const visibility = args.rootVisibility === "private" ? "private" : "public";
|
|
526
|
-
const source = isNewRoot ? sourceFromTurnInput({
|
|
527
|
-
conversationId: args.conversationId,
|
|
528
|
-
source: "web",
|
|
529
|
-
visibility
|
|
530
|
-
}) : void 0;
|
|
531
|
-
await store.recordActivity({
|
|
532
|
-
conversationId: args.conversationId,
|
|
533
|
-
destination,
|
|
534
|
-
nowMs: args.nowMs,
|
|
535
|
-
actor: storedActorFromApi(args.actor),
|
|
536
|
-
// Do not rewrite a Slack root's origin source when a dashboard participant
|
|
537
|
-
// continues it. Mailbox entries still carry source "web" per turn.
|
|
538
|
-
...isNewRoot ? { source: "web" } : void 0,
|
|
539
|
-
...source ? { sessionSource: source } : void 0,
|
|
540
|
-
...isNewRoot ? { visibility } : void 0
|
|
541
|
-
});
|
|
542
|
-
return destination;
|
|
543
|
-
}
|
|
544
|
-
async function createAndEnqueueApiConversation(input, options) {
|
|
545
|
-
if (!input.actor.email) {
|
|
546
|
-
throw new Error("API conversation actor requires a verified email");
|
|
547
|
-
}
|
|
548
|
-
const conversationId = createApiConversationId({
|
|
549
|
-
actorEmail: input.actor.email,
|
|
550
|
-
idempotencyKey: input.idempotencyKey
|
|
551
|
-
});
|
|
552
|
-
return await appendAndEnqueueApiConversationMessage(
|
|
553
|
-
{
|
|
554
|
-
actor: input.actor,
|
|
555
|
-
conversationId,
|
|
556
|
-
idempotencyKey: input.idempotencyKey,
|
|
557
|
-
message: input.message,
|
|
558
|
-
rootVisibility: input.visibility === "private" ? "private" : "public"
|
|
559
|
-
},
|
|
560
|
-
options
|
|
561
|
-
);
|
|
562
|
-
}
|
|
563
|
-
async function appendAndEnqueueApiConversationMessage(input, options) {
|
|
564
|
-
const text = input.message.trim();
|
|
565
|
-
if (!text) {
|
|
566
|
-
throw new Error("API conversation message must not be empty");
|
|
567
|
-
}
|
|
568
|
-
if (!input.actor.email) {
|
|
569
|
-
throw new Error("API conversation actor requires a verified email");
|
|
570
|
-
}
|
|
571
|
-
const nowMs = options.nowMs ?? Date.now();
|
|
572
|
-
const messageId = apiConversationMessageId({
|
|
573
|
-
conversationId: input.conversationId,
|
|
574
|
-
idempotencyKey: input.idempotencyKey
|
|
575
|
-
});
|
|
576
|
-
const destination = await recordApiConversationActivity({
|
|
577
|
-
actor: input.actor,
|
|
578
|
-
conversationId: input.conversationId,
|
|
579
|
-
conversationStore: options.conversationStore,
|
|
580
|
-
nowMs,
|
|
581
|
-
...input.rootVisibility && { rootVisibility: input.rootVisibility }
|
|
582
|
-
});
|
|
583
|
-
const enqueue = options.exclusive ? appendAndEnqueueExclusiveInboundMessage : appendAndEnqueueInboundMessage;
|
|
584
|
-
const result = await enqueue({
|
|
585
|
-
message: buildApiTurnInboundMessage({
|
|
586
|
-
actor: input.actor,
|
|
587
|
-
conversationId: input.conversationId,
|
|
588
|
-
destination,
|
|
589
|
-
message: text,
|
|
590
|
-
messageId,
|
|
591
|
-
nowMs
|
|
592
|
-
}),
|
|
593
|
-
conversationStore: options.conversationStore,
|
|
594
|
-
nowMs,
|
|
595
|
-
queue: options.queue,
|
|
596
|
-
state: options.state
|
|
597
|
-
});
|
|
598
|
-
const status = result.status === "appended" ? "accepted" : result.status;
|
|
599
|
-
return {
|
|
600
|
-
conversationId: input.conversationId,
|
|
601
|
-
messageId,
|
|
602
|
-
status
|
|
603
|
-
};
|
|
604
|
-
}
|
|
605
|
-
function captureApiBoundaryFailure(args) {
|
|
606
|
-
const eventId = logException(args.error, `api.turn.${args.failureCode}`, {
|
|
607
|
-
conversationId: args.conversationId,
|
|
608
|
-
...args.runId ? { runId: args.runId } : void 0,
|
|
609
|
-
turnId: args.turnId
|
|
610
|
-
});
|
|
611
|
-
return typeof eventId === "string" ? eventId : void 0;
|
|
612
|
-
}
|
|
613
|
-
function hasLostTurnInputCommit(error) {
|
|
614
|
-
const cause = getConversationTurnBoundaryError(error)?.cause ?? error;
|
|
615
|
-
return isTurnInputCommitLostError(error) || isTurnInputCommitLostError(cause);
|
|
616
|
-
}
|
|
617
|
-
function createApiTurnWorker(agentRunner, cancellation) {
|
|
618
|
-
return async (context) => {
|
|
619
|
-
const resolved = await resolveApiTurnWork(context);
|
|
620
|
-
if (!resolved) {
|
|
621
|
-
throw new Error(
|
|
622
|
-
`Unsupported input for Conversation ${context.conversationId}`
|
|
623
|
-
);
|
|
624
|
-
}
|
|
625
|
-
if (context.publishExternally) {
|
|
626
|
-
throw new Error(
|
|
627
|
-
`publishExternally must be false for Conversation ${context.conversationId}`
|
|
628
|
-
);
|
|
629
|
-
}
|
|
630
|
-
const lifecycle = new ConversationTurnLifecycleService(
|
|
631
|
-
getConversationEventStore()
|
|
632
|
-
);
|
|
633
|
-
const isResume = resolved.kind === "resume";
|
|
634
|
-
let turnIdentity;
|
|
635
|
-
let text = "";
|
|
636
|
-
let turnId = "";
|
|
637
|
-
let userMessageId = "";
|
|
638
|
-
let startedAtMs = Date.now();
|
|
639
|
-
let inputMessageIds = [];
|
|
640
|
-
const storedConversation = await getConversationStore().get({
|
|
641
|
-
conversationId: context.conversationId
|
|
642
|
-
});
|
|
643
|
-
const mailboxDestination = resolved.kind === "mailbox" ? resolved.batch[0]?.message.destination : void 0;
|
|
644
|
-
const destination = resolveApiTurnDestination({
|
|
645
|
-
conversationId: context.conversationId,
|
|
646
|
-
existing: context.destination ?? mailboxDestination ?? storedConversation?.destination
|
|
647
|
-
});
|
|
648
|
-
if (resolved.kind === "mailbox") {
|
|
649
|
-
const first = resolved.batch[0];
|
|
650
|
-
text = joinMailboxText(resolved.batch.map((entry) => entry.message));
|
|
651
|
-
startedAtMs = first.message.createdAtMs;
|
|
652
|
-
userMessageId = first.message.inboundMessageId;
|
|
653
|
-
turnId = apiTurnIdForMessage(userMessageId);
|
|
654
|
-
inputMessageIds = resolved.batch.map(
|
|
655
|
-
(entry) => entry.message.inboundMessageId
|
|
656
|
-
);
|
|
657
|
-
turnIdentity = first;
|
|
658
|
-
} else {
|
|
659
|
-
turnId = resolved.turnId;
|
|
660
|
-
}
|
|
661
|
-
const persisted = await getPersistedThreadState(context.conversationId);
|
|
662
|
-
const conversation = coerceThreadConversationState(persisted);
|
|
663
|
-
await hydrateConversationMessages({
|
|
664
|
-
conversation,
|
|
665
|
-
conversationId: context.conversationId
|
|
666
|
-
});
|
|
667
|
-
if (isResume) {
|
|
668
|
-
const userMessage = getTurnUserMessage(conversation, turnId);
|
|
669
|
-
if (!userMessage) {
|
|
670
|
-
throw new Error(
|
|
671
|
-
`Unable to locate the persisted user message for Turn "${turnId}"`
|
|
672
|
-
);
|
|
673
|
-
}
|
|
674
|
-
turnIdentity = turnIdentityFromConversationMessage(userMessage);
|
|
675
|
-
userMessageId = userMessage.id;
|
|
676
|
-
text = userMessage.text;
|
|
677
|
-
startedAtMs = userMessage.createdAtMs;
|
|
678
|
-
inputMessageIds = [userMessageId];
|
|
679
|
-
}
|
|
680
|
-
if (!turnIdentity) {
|
|
681
|
-
throw new Error(
|
|
682
|
-
`Resumed Turn is missing an Actor for Conversation ${context.conversationId}`
|
|
683
|
-
);
|
|
684
|
-
}
|
|
685
|
-
const { actor, author } = turnIdentity;
|
|
686
|
-
const source = sourceFromTurnInput({
|
|
687
|
-
conversationId: context.conversationId,
|
|
688
|
-
source: turnIdentity.source,
|
|
689
|
-
visibility: storedConversation?.visibility
|
|
690
|
-
});
|
|
691
|
-
const webActor = actor.platform === "web" ? actor : void 0;
|
|
692
|
-
return await withLogContext(
|
|
693
|
-
{
|
|
694
|
-
conversationId: context.conversationId,
|
|
695
|
-
platform: actor.platform,
|
|
696
|
-
...webActor ? {
|
|
697
|
-
userId: webActor.userId,
|
|
698
|
-
...webActor.userName ? { userName: webActor.userName } : void 0
|
|
699
|
-
} : void 0
|
|
700
|
-
},
|
|
701
|
-
async () => {
|
|
702
|
-
let acknowledged = isResume || context.attempt.messages.length === 0;
|
|
703
|
-
const acknowledge = async () => {
|
|
704
|
-
if (acknowledged) {
|
|
705
|
-
return;
|
|
706
|
-
}
|
|
707
|
-
try {
|
|
708
|
-
await context.attempt.ack();
|
|
709
|
-
} catch {
|
|
710
|
-
throw new TurnInputCommitLostError(
|
|
711
|
-
`Conversation work lease lost before Conversation API message acknowledgement for ${context.conversationId}`
|
|
712
|
-
);
|
|
713
|
-
}
|
|
714
|
-
acknowledged = true;
|
|
715
|
-
};
|
|
716
|
-
let sandboxRef = getPersistedSandboxState(persisted);
|
|
717
|
-
const initialSandboxRef = sandboxRef;
|
|
718
|
-
const appSignal = cancellation?.signal(context.conversationId);
|
|
719
|
-
const stopSignal = context.stopSignal?.();
|
|
720
|
-
const cancellationSignal = appSignal && stopSignal ? AbortSignal.any([appSignal, stopSignal]) : appSignal ?? stopSignal;
|
|
721
|
-
const finishCancellation = () => {
|
|
722
|
-
if (appSignal) {
|
|
723
|
-
cancellation?.finish(context.conversationId, appSignal);
|
|
724
|
-
}
|
|
725
|
-
};
|
|
726
|
-
const completeCancelledTurn = async () => {
|
|
727
|
-
try {
|
|
728
|
-
await completeCancelledApiTurn({
|
|
729
|
-
acknowledge,
|
|
730
|
-
cancellation,
|
|
731
|
-
conversation,
|
|
732
|
-
conversationId: context.conversationId,
|
|
733
|
-
sandboxRef,
|
|
734
|
-
signal: appSignal,
|
|
735
|
-
turnId,
|
|
736
|
-
userMessageId
|
|
737
|
-
});
|
|
738
|
-
} catch (error) {
|
|
739
|
-
if (hasLostTurnInputCommit(error)) {
|
|
740
|
-
return { status: "lost_lease" };
|
|
741
|
-
}
|
|
742
|
-
throw error;
|
|
743
|
-
}
|
|
744
|
-
return { status: "completed" };
|
|
745
|
-
};
|
|
746
|
-
if (isResume) {
|
|
747
|
-
if (cancellationSignal?.aborted) {
|
|
748
|
-
return await completeCancelledTurn();
|
|
749
|
-
}
|
|
750
|
-
} else {
|
|
751
|
-
upsertConversationMessage(conversation, {
|
|
752
|
-
id: userMessageId,
|
|
753
|
-
role: "user",
|
|
754
|
-
text: normalizeConversationText(text),
|
|
755
|
-
createdAtMs: startedAtMs,
|
|
756
|
-
author,
|
|
757
|
-
meta: {
|
|
758
|
-
explicitMention: true,
|
|
759
|
-
replied: false,
|
|
760
|
-
...turnIdentity.source === "web" ? { source: "web" } : void 0
|
|
761
|
-
}
|
|
762
|
-
});
|
|
763
|
-
await persistConversationMessages({
|
|
764
|
-
conversation,
|
|
765
|
-
conversationId: context.conversationId
|
|
766
|
-
});
|
|
767
|
-
await lifecycle.start({
|
|
768
|
-
conversationId: context.conversationId,
|
|
769
|
-
createdAtMs: Date.now(),
|
|
770
|
-
inputMessageIds,
|
|
771
|
-
surface: "api",
|
|
772
|
-
turnId
|
|
773
|
-
});
|
|
774
|
-
if (cancellationSignal?.aborted) {
|
|
775
|
-
return await completeCancelledTurn();
|
|
776
|
-
}
|
|
777
|
-
startActiveTurn({
|
|
778
|
-
conversation,
|
|
779
|
-
nextTurnId: turnId
|
|
780
|
-
});
|
|
781
|
-
}
|
|
782
|
-
let currentRunId;
|
|
783
|
-
let assistantMessageDelivered = false;
|
|
784
|
-
let failureCode = "persistence_failed";
|
|
785
|
-
let modelFailureEventId;
|
|
786
|
-
let modelFailureCaptureAttempted = false;
|
|
787
|
-
let completedSuccessfully = false;
|
|
788
|
-
const deliverAssistantMessage = async (value) => {
|
|
789
|
-
const agentMessage = typeof value === "string" ? void 0 : value;
|
|
790
|
-
const replyText = typeof value === "string" ? value : getAssistantReplyText(value);
|
|
791
|
-
if (!replyText?.trim()) {
|
|
792
|
-
return;
|
|
793
|
-
}
|
|
794
|
-
failureCode = "delivery_failed";
|
|
795
|
-
assistantMessageDelivered = true;
|
|
796
|
-
await commitAssistantMessage({
|
|
797
|
-
...agentMessage ? { agentMessage } : void 0,
|
|
798
|
-
conversation,
|
|
799
|
-
conversationId: context.conversationId,
|
|
800
|
-
sessionId: turnId,
|
|
801
|
-
...turnIdentity.source === "web" ? { source: "web" } : void 0,
|
|
802
|
-
text: replyText,
|
|
803
|
-
userMessageId
|
|
804
|
-
});
|
|
805
|
-
failureCode = "agent_run_failed";
|
|
806
|
-
};
|
|
807
|
-
try {
|
|
808
|
-
if (cancellationSignal?.aborted) {
|
|
809
|
-
return await completeCancelledTurn();
|
|
810
|
-
}
|
|
811
|
-
if (!isResume) {
|
|
812
|
-
const pendingAuthorizationActorId = conversation.processing.pendingAuth?.actorId;
|
|
813
|
-
const authParked = (await listTurnSummaries(context.conversationId)).filter(
|
|
814
|
-
(summary) => summary.surface === "api" && !summary.dispatchId && summary.state === "paused" && summary.resumeReason === "auth"
|
|
815
|
-
);
|
|
816
|
-
for (const parked of authParked) {
|
|
817
|
-
if (cancellationSignal?.aborted) {
|
|
818
|
-
return await completeCancelledTurn();
|
|
819
|
-
}
|
|
820
|
-
await abandonTurnRecord({
|
|
821
|
-
conversationId: context.conversationId,
|
|
822
|
-
turnId: parked.turnId,
|
|
823
|
-
errorMessage: "Auth-paused Turn superseded by new input"
|
|
824
|
-
});
|
|
825
|
-
markTurnClosed({
|
|
826
|
-
conversation,
|
|
827
|
-
nowMs: Date.now(),
|
|
828
|
-
sessionId: parked.turnId
|
|
829
|
-
});
|
|
830
|
-
}
|
|
831
|
-
if (authParked.length > 0 && pendingAuthorizationActorId) {
|
|
832
|
-
await deleteWebAuthorization({
|
|
833
|
-
actorId: pendingAuthorizationActorId,
|
|
834
|
-
conversationId: context.conversationId
|
|
835
|
-
});
|
|
836
|
-
}
|
|
837
|
-
}
|
|
838
|
-
await persistThreadStateById(context.conversationId, {
|
|
839
|
-
conversation
|
|
840
|
-
});
|
|
841
|
-
if (cancellationSignal?.aborted) {
|
|
842
|
-
return await completeCancelledTurn();
|
|
843
|
-
}
|
|
844
|
-
const piMessages = await loadProjection({
|
|
845
|
-
conversationId: context.conversationId
|
|
846
|
-
});
|
|
847
|
-
failureCode = "agent_run_failed";
|
|
848
|
-
currentRunId = `api-run:${stableHex(turnId, String(startedAtMs))}`;
|
|
849
|
-
setTags({ runId: currentRunId });
|
|
850
|
-
const outcome = await executeTurn(
|
|
851
|
-
agentRunner,
|
|
852
|
-
{
|
|
853
|
-
conversationId: context.conversationId,
|
|
854
|
-
turnId,
|
|
855
|
-
runId: currentRunId,
|
|
856
|
-
instruction: {
|
|
857
|
-
text,
|
|
858
|
-
context: buildConversationContext(conversation, {
|
|
859
|
-
excludeMessageId: userMessageId
|
|
860
|
-
})
|
|
861
|
-
},
|
|
862
|
-
history: piMessages,
|
|
863
|
-
actor,
|
|
864
|
-
credentialContext: credentialContextForActor(actor),
|
|
865
|
-
destination,
|
|
866
|
-
publishExternally: false,
|
|
867
|
-
source: storedConversation?.location ? { ...source, location: storedConversation.location } : source,
|
|
868
|
-
surface: "api",
|
|
869
|
-
...cancellationSignal ? { signal: cancellationSignal } : void 0,
|
|
870
|
-
...webActor ? {
|
|
871
|
-
authorization: createWebAuthorization({
|
|
872
|
-
actorId: webActor.userId,
|
|
873
|
-
conversationId: context.conversationId
|
|
874
|
-
})
|
|
875
|
-
} : { disabledFeatures: ["interactive-auth"] },
|
|
876
|
-
state: {
|
|
877
|
-
pendingAuth: conversation.processing.pendingAuth,
|
|
878
|
-
sandboxRef
|
|
879
|
-
},
|
|
880
|
-
delivery: { send: deliverAssistantMessage },
|
|
881
|
-
durability: {
|
|
882
|
-
onInputCommitted: acknowledge,
|
|
883
|
-
shouldYield: context.shouldYield,
|
|
884
|
-
onSandboxRefChanged: async (nextSandboxRef) => {
|
|
885
|
-
sandboxRef = nextSandboxRef;
|
|
886
|
-
await persistThreadStateById(context.conversationId, {
|
|
887
|
-
conversation,
|
|
888
|
-
sandboxRef
|
|
889
|
-
});
|
|
890
|
-
},
|
|
891
|
-
recordPendingAuth: async (pendingAuth) => {
|
|
892
|
-
conversation.processing.pendingAuth = pendingAuth;
|
|
893
|
-
await persistThreadStateById(context.conversationId, {
|
|
894
|
-
conversation,
|
|
895
|
-
sandboxRef
|
|
896
|
-
});
|
|
897
|
-
}
|
|
898
|
-
}
|
|
899
|
-
},
|
|
900
|
-
async (result) => {
|
|
901
|
-
if (cancellationSignal?.aborted) {
|
|
902
|
-
throw cancellationSignal.reason ?? new DOMException("Turn cancelled", "AbortError");
|
|
903
|
-
}
|
|
904
|
-
finishCancellation();
|
|
905
|
-
modelFailureCaptureAttempted = result.diagnostics.outcome !== "success";
|
|
906
|
-
const finalized = finalizeFailedTurnReplyWithEvent({
|
|
907
|
-
reply: result,
|
|
908
|
-
logException
|
|
909
|
-
});
|
|
910
|
-
const modelFailureReason = finalized.failureReason;
|
|
911
|
-
const reply = finalized.reply;
|
|
912
|
-
modelFailureEventId = finalized.eventId;
|
|
913
|
-
if (reply.diagnostics.outcome !== "success") {
|
|
914
|
-
await deliverAssistantMessage(reply.text);
|
|
915
|
-
}
|
|
916
|
-
const completedState = buildDeliveredTurnStatePatch({
|
|
917
|
-
conversation,
|
|
918
|
-
reply,
|
|
919
|
-
sessionId: turnId,
|
|
920
|
-
userMessageId
|
|
921
|
-
});
|
|
922
|
-
await persistThreadStateById(context.conversationId, {
|
|
923
|
-
conversation: completedState.conversation,
|
|
924
|
-
sandboxRef: reply.sandboxRef ?? sandboxRef
|
|
925
|
-
});
|
|
926
|
-
if (reply.piMessages?.length) {
|
|
927
|
-
const latest = await getTurnRecord(
|
|
928
|
-
context.conversationId,
|
|
929
|
-
turnId
|
|
930
|
-
);
|
|
931
|
-
await saveTurnCheckpoint({
|
|
932
|
-
mode: "completed",
|
|
933
|
-
conversationId: context.conversationId,
|
|
934
|
-
turnId,
|
|
935
|
-
sliceId: latest?.sliceId ?? 1,
|
|
936
|
-
messages: reply.piMessages,
|
|
937
|
-
durationMs: reply.diagnostics.durationMs,
|
|
938
|
-
usage: reply.diagnostics.usage,
|
|
939
|
-
destination,
|
|
940
|
-
publishExternally: false,
|
|
941
|
-
source,
|
|
942
|
-
actor,
|
|
943
|
-
surface: "api"
|
|
944
|
-
});
|
|
945
|
-
}
|
|
946
|
-
completedSuccessfully = reply.diagnostics.outcome === "success";
|
|
947
|
-
return completedSuccessfully ? {
|
|
948
|
-
outcome: assistantMessageDelivered ? "success" : "no_reply"
|
|
949
|
-
} : {
|
|
950
|
-
...modelFailureEventId ? { eventId: modelFailureEventId } : void 0,
|
|
951
|
-
failureCode: "model_execution_failed",
|
|
952
|
-
...modelFailureReason ? { failureReason: modelFailureReason } : void 0,
|
|
953
|
-
outcome: "failed"
|
|
954
|
-
};
|
|
955
|
-
}
|
|
956
|
-
);
|
|
957
|
-
if (outcome.status !== "completed" && cancellationSignal?.aborted) {
|
|
958
|
-
return await completeCancelledTurn();
|
|
959
|
-
}
|
|
960
|
-
if (outcome.status === "suspended") {
|
|
961
|
-
return { status: "yielded" };
|
|
962
|
-
}
|
|
963
|
-
if (outcome.status === "awaiting_auth") {
|
|
964
|
-
completeAuthPauseTurn({
|
|
965
|
-
conversation,
|
|
966
|
-
sessionId: turnId
|
|
967
|
-
});
|
|
968
|
-
await persistThreadStateById(context.conversationId, {
|
|
969
|
-
conversation,
|
|
970
|
-
sandboxRef
|
|
971
|
-
});
|
|
972
|
-
if (appSignal) {
|
|
973
|
-
cancellation?.park(context.conversationId, appSignal);
|
|
974
|
-
}
|
|
975
|
-
await acknowledge();
|
|
976
|
-
return { status: "paused" };
|
|
977
|
-
}
|
|
978
|
-
if (completedSuccessfully) {
|
|
979
|
-
try {
|
|
980
|
-
await scheduleSessionCompletedPluginTasks(
|
|
981
|
-
{
|
|
982
|
-
conversationId: context.conversationId,
|
|
983
|
-
sessionId: turnId
|
|
984
|
-
},
|
|
985
|
-
{
|
|
986
|
-
send: async (message) => {
|
|
987
|
-
try {
|
|
988
|
-
await processPluginTask(message);
|
|
989
|
-
} catch (error) {
|
|
990
|
-
logException(
|
|
991
|
-
error,
|
|
992
|
-
"api.plugin.session_completion_task.failed",
|
|
993
|
-
{
|
|
994
|
-
conversationId: context.conversationId,
|
|
995
|
-
pluginName: message.plugin,
|
|
996
|
-
taskName: message.name,
|
|
997
|
-
turnId
|
|
998
|
-
}
|
|
999
|
-
);
|
|
1000
|
-
}
|
|
1001
|
-
}
|
|
1002
|
-
}
|
|
1003
|
-
);
|
|
1004
|
-
} catch (error) {
|
|
1005
|
-
logException(error, "api.plugin.session_completion_task.failed", {
|
|
1006
|
-
conversationId: context.conversationId,
|
|
1007
|
-
turnId
|
|
1008
|
-
});
|
|
1009
|
-
}
|
|
1010
|
-
}
|
|
1011
|
-
await acknowledge();
|
|
1012
|
-
return { status: "completed" };
|
|
1013
|
-
} catch (error) {
|
|
1014
|
-
const failure = error instanceof AgentRunError ? error.cause : error;
|
|
1015
|
-
if (hasLostTurnInputCommit(failure)) {
|
|
1016
|
-
return { status: "lost_lease" };
|
|
1017
|
-
}
|
|
1018
|
-
if (cancellationSignal?.aborted) {
|
|
1019
|
-
return await completeCancelledTurn();
|
|
1020
|
-
}
|
|
1021
|
-
if (!context.attempt.isFinalAttempt) {
|
|
1022
|
-
throw failure;
|
|
1023
|
-
}
|
|
1024
|
-
const failureEventId = modelFailureCaptureAttempted && failureCode === "agent_run_failed" ? modelFailureEventId : captureApiBoundaryFailure({
|
|
1025
|
-
conversationId: context.conversationId,
|
|
1026
|
-
error: failure,
|
|
1027
|
-
failureCode,
|
|
1028
|
-
runId: currentRunId,
|
|
1029
|
-
turnId
|
|
1030
|
-
});
|
|
1031
|
-
try {
|
|
1032
|
-
markTurnFailed({
|
|
1033
|
-
conversation,
|
|
1034
|
-
nowMs: Date.now(),
|
|
1035
|
-
sessionId: turnId,
|
|
1036
|
-
userMessageId,
|
|
1037
|
-
markConversationMessage
|
|
1038
|
-
});
|
|
1039
|
-
await persistThreadStateById(context.conversationId, {
|
|
1040
|
-
conversation,
|
|
1041
|
-
sandboxRef: initialSandboxRef ?? null
|
|
1042
|
-
});
|
|
1043
|
-
} catch (persistenceError) {
|
|
1044
|
-
captureApiBoundaryFailure({
|
|
1045
|
-
conversationId: context.conversationId,
|
|
1046
|
-
error: persistenceError,
|
|
1047
|
-
failureCode: "persistence_failed",
|
|
1048
|
-
runId: currentRunId,
|
|
1049
|
-
turnId
|
|
1050
|
-
});
|
|
1051
|
-
}
|
|
1052
|
-
await lifecycle.fail({
|
|
1053
|
-
conversationId: context.conversationId,
|
|
1054
|
-
createdAtMs: Date.now(),
|
|
1055
|
-
...failureEventId ? { eventId: failureEventId } : void 0,
|
|
1056
|
-
failureCode,
|
|
1057
|
-
turnId
|
|
1058
|
-
});
|
|
1059
|
-
finishCancellation();
|
|
1060
|
-
await acknowledge();
|
|
1061
|
-
return { status: "completed" };
|
|
1062
|
-
}
|
|
1063
|
-
}
|
|
1064
|
-
);
|
|
1065
|
-
};
|
|
1066
|
-
}
|
|
1067
|
-
function routeApiTurnWork(options) {
|
|
1068
|
-
return async (context) => {
|
|
1069
|
-
return await isApiTurnWork(context) ? await options.apiTurnWorker(context) : await options.fallbackWorker(context);
|
|
1070
|
-
};
|
|
1071
|
-
}
|
|
1072
|
-
|
|
1073
|
-
// src/api/conversations/access.ts
|
|
1074
|
-
import { eq as eq2, inArray as inArray2 } from "drizzle-orm";
|
|
1075
|
-
import { alias } from "drizzle-orm/pg-core";
|
|
1076
|
-
|
|
1077
|
-
// src/api/conversations/membership.ts
|
|
1078
|
-
import {
|
|
1079
|
-
and,
|
|
1080
|
-
eq,
|
|
1081
|
-
exists,
|
|
1082
|
-
inArray,
|
|
1083
|
-
isNotNull,
|
|
1084
|
-
notExists,
|
|
1085
|
-
or
|
|
1086
|
-
} from "drizzle-orm";
|
|
1087
|
-
function conversationHasParticipantUser(userId) {
|
|
1088
|
-
const participant = getDb().select({
|
|
1089
|
-
rootConversationId: juniorConversationParticipants.rootConversationId
|
|
1090
|
-
}).from(juniorConversationParticipants).where(
|
|
1091
|
-
and(
|
|
1092
|
-
eq(
|
|
1093
|
-
juniorConversationParticipants.rootConversationId,
|
|
1094
|
-
juniorConversations.conversationId
|
|
1095
|
-
),
|
|
1096
|
-
eq(juniorConversationParticipants.userId, userId)
|
|
1097
|
-
)
|
|
1098
|
-
);
|
|
1099
|
-
return exists(participant);
|
|
1100
|
-
}
|
|
1101
|
-
function conversationHasParticipantEmail(email) {
|
|
1102
|
-
const participant = getDb().select({
|
|
1103
|
-
rootConversationId: juniorConversationParticipants.rootConversationId
|
|
1104
|
-
}).from(juniorConversationParticipants).innerJoin(
|
|
1105
|
-
juniorUsers,
|
|
1106
|
-
eq(juniorUsers.id, juniorConversationParticipants.userId)
|
|
1107
|
-
).where(
|
|
1108
|
-
and(
|
|
1109
|
-
eq(
|
|
1110
|
-
juniorConversationParticipants.rootConversationId,
|
|
1111
|
-
juniorConversations.conversationId
|
|
1112
|
-
),
|
|
1113
|
-
eq(juniorUsers.primaryEmailNormalized, email)
|
|
1114
|
-
)
|
|
1115
|
-
);
|
|
1116
|
-
return exists(participant);
|
|
1117
|
-
}
|
|
1118
|
-
function conversationArchivedForUser(userId) {
|
|
1119
|
-
const archive = getDb().select({
|
|
1120
|
-
rootConversationId: juniorConversationParticipants.rootConversationId
|
|
1121
|
-
}).from(juniorConversationParticipants).where(
|
|
1122
|
-
and(
|
|
1123
|
-
eq(
|
|
1124
|
-
juniorConversationParticipants.rootConversationId,
|
|
1125
|
-
juniorConversations.conversationId
|
|
1126
|
-
),
|
|
1127
|
-
eq(juniorConversationParticipants.userId, userId),
|
|
1128
|
-
isNotNull(juniorConversationParticipants.archivedAt)
|
|
1129
|
-
)
|
|
1130
|
-
);
|
|
1131
|
-
return exists(archive);
|
|
1132
|
-
}
|
|
1133
|
-
function conversationNotArchivedForUser(userId) {
|
|
1134
|
-
const archive = getDb().select({
|
|
1135
|
-
rootConversationId: juniorConversationParticipants.rootConversationId
|
|
1136
|
-
}).from(juniorConversationParticipants).where(
|
|
1137
|
-
and(
|
|
1138
|
-
eq(
|
|
1139
|
-
juniorConversationParticipants.rootConversationId,
|
|
1140
|
-
juniorConversations.conversationId
|
|
1141
|
-
),
|
|
1142
|
-
eq(juniorConversationParticipants.userId, userId),
|
|
1143
|
-
isNotNull(juniorConversationParticipants.archivedAt)
|
|
1144
|
-
)
|
|
1145
|
-
);
|
|
1146
|
-
return notExists(archive);
|
|
1147
|
-
}
|
|
1148
|
-
function conversationArchivedForEmail(email) {
|
|
1149
|
-
const archive = getDb().select({
|
|
1150
|
-
rootConversationId: juniorConversationParticipants.rootConversationId
|
|
1151
|
-
}).from(juniorConversationParticipants).innerJoin(
|
|
1152
|
-
juniorUsers,
|
|
1153
|
-
eq(juniorUsers.id, juniorConversationParticipants.userId)
|
|
1154
|
-
).where(
|
|
1155
|
-
and(
|
|
1156
|
-
eq(
|
|
1157
|
-
juniorConversationParticipants.rootConversationId,
|
|
1158
|
-
juniorConversations.conversationId
|
|
1159
|
-
),
|
|
1160
|
-
eq(juniorUsers.primaryEmailNormalized, email),
|
|
1161
|
-
isNotNull(juniorConversationParticipants.archivedAt)
|
|
1162
|
-
)
|
|
1163
|
-
);
|
|
1164
|
-
return exists(archive);
|
|
1165
|
-
}
|
|
1166
|
-
function conversationNotArchivedForEmail(email) {
|
|
1167
|
-
const archive = getDb().select({
|
|
1168
|
-
rootConversationId: juniorConversationParticipants.rootConversationId
|
|
1169
|
-
}).from(juniorConversationParticipants).innerJoin(
|
|
1170
|
-
juniorUsers,
|
|
1171
|
-
eq(juniorUsers.id, juniorConversationParticipants.userId)
|
|
1172
|
-
).where(
|
|
1173
|
-
and(
|
|
1174
|
-
eq(
|
|
1175
|
-
juniorConversationParticipants.rootConversationId,
|
|
1176
|
-
juniorConversations.conversationId
|
|
1177
|
-
),
|
|
1178
|
-
eq(juniorUsers.primaryEmailNormalized, email),
|
|
1179
|
-
isNotNull(juniorConversationParticipants.archivedAt)
|
|
1180
|
-
)
|
|
1181
|
-
);
|
|
1182
|
-
return notExists(archive);
|
|
1183
|
-
}
|
|
1184
|
-
function viewerConversationMembership(args) {
|
|
1185
|
-
if (args.actorMatch && args.participantMatch) {
|
|
1186
|
-
return or(args.actorMatch, args.participantMatch);
|
|
1187
|
-
}
|
|
1188
|
-
return args.actorMatch ?? args.participantMatch;
|
|
1189
|
-
}
|
|
1190
|
-
async function conversationArchiveTimes(db, conversationIds, subject) {
|
|
1191
|
-
if (!subject || conversationIds.length === 0) return /* @__PURE__ */ new Map();
|
|
1192
|
-
const query = db.select({
|
|
1193
|
-
archivedAt: juniorConversationParticipants.archivedAt,
|
|
1194
|
-
rootConversationId: juniorConversationParticipants.rootConversationId
|
|
1195
|
-
}).from(juniorConversationParticipants);
|
|
1196
|
-
const rows = "userId" in subject ? await query.where(
|
|
1197
|
-
and(
|
|
1198
|
-
eq(juniorConversationParticipants.userId, subject.userId),
|
|
1199
|
-
inArray(juniorConversationParticipants.rootConversationId, [
|
|
1200
|
-
...conversationIds
|
|
1201
|
-
]),
|
|
1202
|
-
isNotNull(juniorConversationParticipants.archivedAt)
|
|
1203
|
-
)
|
|
1204
|
-
) : await query.innerJoin(
|
|
1205
|
-
juniorUsers,
|
|
1206
|
-
eq(juniorUsers.id, juniorConversationParticipants.userId)
|
|
1207
|
-
).where(
|
|
1208
|
-
and(
|
|
1209
|
-
eq(juniorUsers.primaryEmailNormalized, subject.email),
|
|
1210
|
-
inArray(juniorConversationParticipants.rootConversationId, [
|
|
1211
|
-
...conversationIds
|
|
1212
|
-
]),
|
|
1213
|
-
isNotNull(juniorConversationParticipants.archivedAt)
|
|
1214
|
-
)
|
|
1215
|
-
);
|
|
1216
|
-
return new Map(
|
|
1217
|
-
rows.flatMap(
|
|
1218
|
-
(row) => row.archivedAt ? [[row.rootConversationId, row.archivedAt.getTime()]] : []
|
|
1219
|
-
)
|
|
1220
|
-
);
|
|
1221
|
-
}
|
|
1222
|
-
async function conversationIdsWithParticipantUser(db, conversationIds, userId) {
|
|
1223
|
-
if (conversationIds.length === 0) {
|
|
1224
|
-
return /* @__PURE__ */ new Set();
|
|
1225
|
-
}
|
|
1226
|
-
const rows = await db.select({
|
|
1227
|
-
rootConversationId: juniorConversationParticipants.rootConversationId
|
|
1228
|
-
}).from(juniorConversationParticipants).where(
|
|
1229
|
-
and(
|
|
1230
|
-
eq(juniorConversationParticipants.userId, userId),
|
|
1231
|
-
inArray(juniorConversationParticipants.rootConversationId, [
|
|
1232
|
-
...conversationIds
|
|
1233
|
-
])
|
|
1234
|
-
)
|
|
1235
|
-
);
|
|
1236
|
-
return new Set(rows.map((row) => row.rootConversationId));
|
|
1237
|
-
}
|
|
1238
|
-
|
|
1239
|
-
// src/api/conversations/access.ts
|
|
1240
|
-
var rootConversation = alias(juniorConversations, "access_root_conversation");
|
|
1241
|
-
var rootDestination = alias(juniorDestinations, "access_root_destination");
|
|
1242
|
-
var rootIdentity = alias(juniorIdentities, "access_root_identity");
|
|
1243
|
-
async function readConversationAccessFromSql(db, conversationIds, viewer) {
|
|
1244
|
-
if (conversationIds.length === 0) return /* @__PURE__ */ new Map();
|
|
1245
|
-
const normalizedViewerEmail = viewer?.email.trim().toLowerCase() || void 0;
|
|
1246
|
-
const rows = await db.select({
|
|
1247
|
-
conversationId: juniorConversations.conversationId,
|
|
1248
|
-
parentConversationId: juniorConversations.parentConversationId,
|
|
1249
|
-
storedRootConversationId: juniorConversations.rootConversationId,
|
|
1250
|
-
rootConversationId: rootConversation.conversationId,
|
|
1251
|
-
rootParentConversationId: rootConversation.parentConversationId,
|
|
1252
|
-
rootRootConversationId: rootConversation.rootConversationId,
|
|
1253
|
-
visibility: rootDestination.visibility,
|
|
1254
|
-
rootEmailNormalized: rootIdentity.emailNormalized,
|
|
1255
|
-
rootEmailVerified: rootIdentity.emailVerified,
|
|
1256
|
-
rootUserId: rootIdentity.userId
|
|
1257
|
-
}).from(juniorConversations).leftJoin(
|
|
1258
|
-
rootConversation,
|
|
1259
|
-
eq2(
|
|
1260
|
-
rootConversation.conversationId,
|
|
1261
|
-
juniorConversations.rootConversationId
|
|
1262
|
-
)
|
|
1263
|
-
).leftJoin(
|
|
1264
|
-
rootDestination,
|
|
1265
|
-
eq2(rootDestination.id, rootConversation.destinationId)
|
|
1266
|
-
).leftJoin(
|
|
1267
|
-
rootIdentity,
|
|
1268
|
-
eq2(rootIdentity.id, rootConversation.actorIdentityId)
|
|
1269
|
-
).where(inArray2(juniorConversations.conversationId, [...conversationIds]));
|
|
1270
|
-
const membershipConversationIds = /* @__PURE__ */ new Set();
|
|
1271
|
-
for (const row of rows) {
|
|
1272
|
-
membershipConversationIds.add(row.conversationId);
|
|
1273
|
-
if (row.storedRootConversationId) {
|
|
1274
|
-
membershipConversationIds.add(row.storedRootConversationId);
|
|
1275
|
-
}
|
|
1276
|
-
}
|
|
1277
|
-
const participantRootIds = viewer !== void 0 ? await conversationIdsWithParticipantUser(
|
|
1278
|
-
db,
|
|
1279
|
-
[...membershipConversationIds],
|
|
1280
|
-
viewer.id
|
|
1281
|
-
) : /* @__PURE__ */ new Set();
|
|
1282
|
-
return new Map(
|
|
1283
|
-
rows.map((row) => {
|
|
1284
|
-
const hasValidRoot = row.rootConversationId !== null && row.rootConversationId === row.storedRootConversationId && row.rootRootConversationId === row.rootConversationId && row.rootParentConversationId === null && (row.parentConversationId !== null || row.storedRootConversationId === row.conversationId);
|
|
1285
|
-
const validRootConversationId = hasValidRoot ? row.rootConversationId ?? void 0 : void 0;
|
|
1286
|
-
const visibility = validRootConversationId === void 0 ? null : row.visibility;
|
|
1287
|
-
const isRootActor = validRootConversationId !== void 0 && viewer !== void 0 && (row.rootUserId === viewer.id || normalizedViewerEmail !== void 0 && row.rootEmailVerified === true && row.rootEmailNormalized === normalizedViewerEmail);
|
|
1288
|
-
const isMaterializedParticipant = participantRootIds.has(row.conversationId) || validRootConversationId !== void 0 && participantRootIds.has(validRootConversationId);
|
|
1289
|
-
const isParticipant = isRootActor || isMaterializedParticipant;
|
|
1290
|
-
const canViewPrivateContent = isParticipant || validRootConversationId !== void 0 && canExposeConversationPayload({
|
|
1291
|
-
visibility: visibility ?? void 0
|
|
1292
|
-
});
|
|
1293
|
-
return [
|
|
1294
|
-
row.conversationId,
|
|
1295
|
-
{
|
|
1296
|
-
canViewPrivateContent,
|
|
1297
|
-
isParticipant,
|
|
1298
|
-
visibility
|
|
1299
|
-
}
|
|
1300
|
-
];
|
|
1301
|
-
})
|
|
1302
|
-
);
|
|
1303
|
-
}
|
|
1304
|
-
|
|
1305
|
-
// src/chat/slack/user.ts
|
|
1306
|
-
import { z as z2 } from "zod";
|
|
1307
|
-
var slackUserInfoSchema = z2.object({
|
|
1308
|
-
ok: z2.literal(true),
|
|
1309
|
-
user: z2.object({
|
|
1310
|
-
name: z2.string().nullish(),
|
|
1311
|
-
profile: z2.object({
|
|
1312
|
-
email: z2.string().nullish(),
|
|
1313
|
-
real_name: z2.string().nullish()
|
|
1314
|
-
}).optional()
|
|
1315
|
-
})
|
|
1316
|
-
});
|
|
1317
|
-
var USER_CACHE_TTL_MS = 5 * 60 * 1e3;
|
|
1318
|
-
var userCache = /* @__PURE__ */ new Map();
|
|
1319
|
-
function userCacheKey(teamId, userId) {
|
|
1320
|
-
return `${teamId}:${userId}`;
|
|
1321
|
-
}
|
|
1322
|
-
function readFromCache(teamId, userId) {
|
|
1323
|
-
const hit = userCache.get(userCacheKey(teamId, userId));
|
|
1324
|
-
if (!hit) return null;
|
|
1325
|
-
if (hit.expiresAt < Date.now()) {
|
|
1326
|
-
userCache.delete(userCacheKey(teamId, userId));
|
|
1327
|
-
return null;
|
|
1328
|
-
}
|
|
1329
|
-
return hit.value;
|
|
1330
|
-
}
|
|
1331
|
-
function writeToCache(teamId, userId, value) {
|
|
1332
|
-
userCache.set(userCacheKey(teamId, userId), {
|
|
1333
|
-
value,
|
|
1334
|
-
expiresAt: Date.now() + USER_CACHE_TTL_MS
|
|
1335
|
-
});
|
|
1336
|
-
}
|
|
1337
|
-
async function lookupSlackUser(teamId, userId) {
|
|
1338
|
-
if (!teamId || !userId) {
|
|
1339
|
-
return null;
|
|
1340
|
-
}
|
|
1341
|
-
const cached = readFromCache(teamId, userId);
|
|
1342
|
-
if (cached) {
|
|
1343
|
-
return cached;
|
|
1344
|
-
}
|
|
1345
|
-
const token = getSlackBotToken();
|
|
1346
|
-
if (!token) {
|
|
1347
|
-
return null;
|
|
1348
|
-
}
|
|
1349
|
-
try {
|
|
1350
|
-
const response = await fetch(
|
|
1351
|
-
`https://slack.com/api/users.info?user=${encodeURIComponent(userId)}`,
|
|
1352
|
-
{
|
|
1353
|
-
headers: {
|
|
1354
|
-
authorization: `Bearer ${token}`
|
|
1355
|
-
}
|
|
1356
|
-
}
|
|
1357
|
-
);
|
|
1358
|
-
if (!response.ok) {
|
|
1359
|
-
logWarn("slack.user.lookup.failed", {
|
|
1360
|
-
"enduser.id": userId,
|
|
1361
|
-
"app.slack.team_id": teamId,
|
|
1362
|
-
"http.response.status_code": response.status
|
|
1363
|
-
});
|
|
1364
|
-
return null;
|
|
1365
|
-
}
|
|
1366
|
-
const parsed = slackUserInfoSchema.safeParse(await response.json());
|
|
1367
|
-
if (!parsed.success) {
|
|
1368
|
-
return null;
|
|
1369
|
-
}
|
|
1370
|
-
const payload = parsed.data;
|
|
1371
|
-
const userName = payload.user.name?.trim() || void 0;
|
|
1372
|
-
const fullName = payload.user.profile?.real_name?.trim() || void 0;
|
|
1373
|
-
const result = {
|
|
1374
|
-
userName,
|
|
1375
|
-
fullName,
|
|
1376
|
-
email: payload.user.profile?.email?.trim() || void 0
|
|
1377
|
-
};
|
|
1378
|
-
writeToCache(teamId, userId, result);
|
|
1379
|
-
return result;
|
|
1380
|
-
} catch (error) {
|
|
1381
|
-
logWarn("slack.user.lookup.failed", {
|
|
1382
|
-
"enduser.id": userId,
|
|
1383
|
-
"app.slack.team_id": teamId,
|
|
1384
|
-
"exception.message": error instanceof Error ? error.message : String(error)
|
|
1385
|
-
});
|
|
1386
|
-
return null;
|
|
1387
|
-
}
|
|
1388
|
-
}
|
|
1389
|
-
|
|
1390
|
-
// src/chat/slack/conversation-context.ts
|
|
1391
|
-
function normalizeConversationName(type, channelName) {
|
|
1392
|
-
const trimmed = channelName?.trim();
|
|
1393
|
-
if (!trimmed) return void 0;
|
|
1394
|
-
if (type === "public_channel" || type === "private_channel" || type === "private_channel_or_group_dm") {
|
|
1395
|
-
return trimmed.startsWith("#") ? trimmed : `#${trimmed}`;
|
|
1396
|
-
}
|
|
1397
|
-
return trimmed;
|
|
1398
|
-
}
|
|
1399
|
-
function typeFromSlackChannelType(channelType) {
|
|
1400
|
-
if (channelType === "channel") return "public_channel";
|
|
1401
|
-
if (channelType === "group") return "private_channel";
|
|
1402
|
-
if (channelType === "mpim") return "group_dm";
|
|
1403
|
-
if (channelType === "im") return "direct_message";
|
|
1404
|
-
return void 0;
|
|
1405
|
-
}
|
|
1406
|
-
function typeFromChannelId(channelId, channelName) {
|
|
1407
|
-
const normalized = normalizeSlackConversationId(channelId);
|
|
1408
|
-
if (!normalized) return void 0;
|
|
1409
|
-
if (normalized.startsWith("C")) return "public_channel";
|
|
1410
|
-
if (normalized.startsWith("D")) return "direct_message";
|
|
1411
|
-
if (normalized.startsWith("G")) {
|
|
1412
|
-
return channelName?.trim().startsWith("mpdm-") ? "group_dm" : "private_channel_or_group_dm";
|
|
1413
|
-
}
|
|
1414
|
-
return void 0;
|
|
1415
|
-
}
|
|
1416
|
-
function toSlackEventChannelType(channelType) {
|
|
1417
|
-
if (channelType === "channel" || channelType === "group" || channelType === "mpim" || channelType === "im") {
|
|
1418
|
-
return channelType;
|
|
1419
|
-
}
|
|
1420
|
-
return void 0;
|
|
1421
|
-
}
|
|
1422
|
-
function conversationVisibilityFromSlackChannelType(channelType) {
|
|
1423
|
-
if (!channelType) return void 0;
|
|
1424
|
-
return channelType === "channel" ? "public" : "private";
|
|
1425
|
-
}
|
|
1426
|
-
function resolveSlackChannelTypeFromMessage(message) {
|
|
1427
|
-
const raw = message.raw;
|
|
1428
|
-
if (!raw || typeof raw !== "object") {
|
|
1429
|
-
return void 0;
|
|
1430
|
-
}
|
|
1431
|
-
const channelType = raw.channel_type;
|
|
1432
|
-
return typeof channelType === "string" ? toSlackEventChannelType(channelType.trim()) : void 0;
|
|
1433
|
-
}
|
|
1434
|
-
function resolveSlackConversationContext(input) {
|
|
1435
|
-
const type = typeFromSlackChannelType(input.channelType) ?? typeFromChannelId(input.channelId, input.channelName);
|
|
1436
|
-
if (!type) return void 0;
|
|
1437
|
-
const name = normalizeConversationName(type, input.channelName);
|
|
1438
|
-
const visibility = conversationVisibilityFromSlackChannelType(
|
|
1439
|
-
input.channelType
|
|
1440
|
-
);
|
|
1441
|
-
return {
|
|
1442
|
-
type,
|
|
1443
|
-
...name ? { name } : void 0,
|
|
1444
|
-
...visibility ? { visibility } : void 0
|
|
1445
|
-
};
|
|
1446
|
-
}
|
|
1447
|
-
function resolveSlackConversationContextFromThreadId(input) {
|
|
1448
|
-
const slackThread = parseSlackThreadId(input.threadId);
|
|
1449
|
-
const context = resolveSlackConversationContext({
|
|
1450
|
-
channelId: slackThread?.channelId,
|
|
1451
|
-
channelName: input.channelName
|
|
1452
|
-
});
|
|
1453
|
-
if (!context || !input.visibility) {
|
|
1454
|
-
return context;
|
|
1455
|
-
}
|
|
1456
|
-
return {
|
|
1457
|
-
...context,
|
|
1458
|
-
type: input.visibility === "private" && context.type === "public_channel" ? "private_channel" : context.type,
|
|
1459
|
-
visibility: input.visibility
|
|
1460
|
-
};
|
|
1461
|
-
}
|
|
1462
|
-
function formatSlackConversationTypeLabel(type) {
|
|
1463
|
-
if (type === "public_channel") return "Public Channel";
|
|
1464
|
-
if (type === "private_channel") return "Private Channel";
|
|
1465
|
-
if (type === "group_dm") return "Group DM";
|
|
1466
|
-
if (type === "direct_message") return "Direct Message";
|
|
1467
|
-
return "Private Channel or Group DM";
|
|
1468
|
-
}
|
|
1469
|
-
function formatSlackConversationRedactedLabel(context) {
|
|
1470
|
-
if (!context) return void 0;
|
|
1471
|
-
return formatSlackConversationTypeLabel(context.type);
|
|
1472
|
-
}
|
|
1473
|
-
|
|
1474
|
-
export {
|
|
1475
|
-
readHealthReport,
|
|
1476
|
-
GET,
|
|
1477
|
-
createVercelAttachmentStorage,
|
|
1478
|
-
lookupSlackUser,
|
|
1479
|
-
conversationVisibilityFromSlackChannelType,
|
|
1480
|
-
resolveSlackChannelTypeFromMessage,
|
|
1481
|
-
resolveSlackConversationContext,
|
|
1482
|
-
resolveSlackConversationContextFromThreadId,
|
|
1483
|
-
formatSlackConversationRedactedLabel,
|
|
1484
|
-
createApiTurnCancellation,
|
|
1485
|
-
getAuthPausedApiTurnId,
|
|
1486
|
-
apiTurnIdForMessage,
|
|
1487
|
-
webActorFromEmail,
|
|
1488
|
-
recordApiConversationActivity,
|
|
1489
|
-
createAndEnqueueApiConversation,
|
|
1490
|
-
appendAndEnqueueApiConversationMessage,
|
|
1491
|
-
createApiTurnWorker,
|
|
1492
|
-
routeApiTurnWork,
|
|
1493
|
-
conversationHasParticipantUser,
|
|
1494
|
-
conversationHasParticipantEmail,
|
|
1495
|
-
conversationArchivedForUser,
|
|
1496
|
-
conversationNotArchivedForUser,
|
|
1497
|
-
conversationArchivedForEmail,
|
|
1498
|
-
conversationNotArchivedForEmail,
|
|
1499
|
-
viewerConversationMembership,
|
|
1500
|
-
conversationArchiveTimes,
|
|
1501
|
-
readConversationAccessFromSql
|
|
1502
|
-
};
|