@sentry/junior 0.66.3 → 0.67.1
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/app.js +408 -818
- package/dist/chat/config.d.ts +3 -0
- package/dist/chat/ingress/slash-command.d.ts +1 -1
- package/dist/chat/plugins/agent-hooks.d.ts +3 -1
- package/dist/chat/respond.d.ts +2 -0
- package/dist/chat/services/message-actor-identity.d.ts +8 -0
- package/dist/chat/services/requester-identity.d.ts +19 -0
- package/dist/chat/services/turn-session-record.d.ts +6 -1
- package/dist/chat/slack/user.d.ts +3 -0
- package/dist/chat/state/turn-session.d.ts +4 -0
- package/dist/chat/task-execution/slack-work.d.ts +2 -0
- package/dist/{chunk-DG2I6GXC.js → chunk-5UIDU7XR.js} +1805 -1016
- package/dist/{chunk-SAYCFF7O.js → chunk-MT23VNOH.js} +50 -2
- package/dist/{chunk-6QWWMZCK.js → chunk-OIIXZOOC.js} +101 -6
- package/dist/{chunk-YL5G5YC4.js → chunk-V47RLIO2.js} +1 -1
- package/dist/{chunk-JA5QR3N4.js → chunk-YGGH2742.js} +2 -2
- package/dist/cli/check.js +2 -2
- package/dist/cli/init.js +1 -0
- package/dist/cli/snapshot-warmup.js +3 -3
- package/dist/reporting.d.ts +45 -2
- package/dist/reporting.js +303 -13
- package/package.json +3 -3
package/dist/app.js
CHANGED
|
@@ -4,14 +4,22 @@ import {
|
|
|
4
4
|
SlackActionError,
|
|
5
5
|
TURN_CONTEXT_TAG,
|
|
6
6
|
abandonAgentTurnSessionRecord,
|
|
7
|
+
bindSlackDirectCredentialSubject,
|
|
7
8
|
buildSentryConversationUrl,
|
|
8
9
|
buildSlackOutputMessage,
|
|
9
10
|
buildSystemPrompt,
|
|
10
11
|
buildTurnContextPrompt,
|
|
11
12
|
commitMessages,
|
|
13
|
+
createAgentPluginHookRunner,
|
|
14
|
+
createAgentPluginLogger,
|
|
15
|
+
createPluginState,
|
|
12
16
|
downloadPrivateSlackFile,
|
|
13
17
|
escapeXml,
|
|
14
18
|
failAgentTurnSessionRecord,
|
|
19
|
+
getAgentPluginRoutes,
|
|
20
|
+
getAgentPluginSlackConversationLink,
|
|
21
|
+
getAgentPluginTools,
|
|
22
|
+
getAgentPlugins,
|
|
15
23
|
getAgentTurnSessionRecord,
|
|
16
24
|
getFilePermalink,
|
|
17
25
|
getHeaderString,
|
|
@@ -32,17 +40,20 @@ import {
|
|
|
32
40
|
recordMcpProviderConnected,
|
|
33
41
|
resolveSlackChannelTypeFromMessage,
|
|
34
42
|
resolveSlackConversationContext,
|
|
43
|
+
setAgentPlugins,
|
|
35
44
|
splitSlackReplyText,
|
|
36
45
|
truncateStatusText,
|
|
37
46
|
upsertAgentTurnSessionRecord,
|
|
47
|
+
validateAgentPlugins,
|
|
48
|
+
verifySlackDirectCredentialSubject,
|
|
38
49
|
withSlackRetries
|
|
39
|
-
} from "./chunk-
|
|
50
|
+
} from "./chunk-5UIDU7XR.js";
|
|
40
51
|
import {
|
|
41
52
|
discoverSkills,
|
|
42
53
|
findSkillByName,
|
|
43
54
|
loadSkillsByName,
|
|
44
55
|
parseSkillInvocation
|
|
45
|
-
} from "./chunk-
|
|
56
|
+
} from "./chunk-V47RLIO2.js";
|
|
46
57
|
import {
|
|
47
58
|
buildNonInteractiveShellScript,
|
|
48
59
|
createSandboxInstance,
|
|
@@ -51,7 +62,7 @@ import {
|
|
|
51
62
|
isSnapshotMissingError,
|
|
52
63
|
resolveRuntimeDependencySnapshot,
|
|
53
64
|
runNonInteractiveCommand
|
|
54
|
-
} from "./chunk-
|
|
65
|
+
} from "./chunk-YGGH2742.js";
|
|
55
66
|
import {
|
|
56
67
|
ACTIVE_LOCK_TTL_MS,
|
|
57
68
|
FUNCTION_TIMEOUT_BUFFER_SECONDS,
|
|
@@ -74,6 +85,7 @@ import {
|
|
|
74
85
|
getSlackClientSecret,
|
|
75
86
|
getSlackSigningSecret,
|
|
76
87
|
getStateAdapter,
|
|
88
|
+
normalizeSlackEmojiName,
|
|
77
89
|
parseSlackThreadId,
|
|
78
90
|
resolveConversationPrivacy,
|
|
79
91
|
resolveGatewayModel,
|
|
@@ -86,9 +98,10 @@ import {
|
|
|
86
98
|
toGenAiPayloadMetadata,
|
|
87
99
|
toGenAiPayloadTraceAttributes,
|
|
88
100
|
toGenAiTextMetadata
|
|
89
|
-
} from "./chunk-
|
|
101
|
+
} from "./chunk-MT23VNOH.js";
|
|
90
102
|
import {
|
|
91
103
|
CredentialUnavailableError,
|
|
104
|
+
buildActorIdentity,
|
|
92
105
|
buildOAuthTokenRequest,
|
|
93
106
|
buildTurnFailureResponse,
|
|
94
107
|
createChatSdkLogger,
|
|
@@ -105,6 +118,7 @@ import {
|
|
|
105
118
|
getPluginOAuthConfig,
|
|
106
119
|
getPluginProviders,
|
|
107
120
|
hasRequiredOAuthScope,
|
|
121
|
+
isActorUserId,
|
|
108
122
|
isPluginConfigKey,
|
|
109
123
|
isPluginProvider,
|
|
110
124
|
isRecord,
|
|
@@ -113,6 +127,7 @@ import {
|
|
|
113
127
|
logInfo,
|
|
114
128
|
logWarn,
|
|
115
129
|
normalizeGenAiFinishReason,
|
|
130
|
+
parseActorUserId,
|
|
116
131
|
parseCredentialContext,
|
|
117
132
|
parseOAuthTokenResponse,
|
|
118
133
|
resolveAuthTokenPlaceholder,
|
|
@@ -123,11 +138,12 @@ import {
|
|
|
123
138
|
setSpanAttributes,
|
|
124
139
|
setSpanStatus,
|
|
125
140
|
setTags,
|
|
141
|
+
slackActorIdentity,
|
|
126
142
|
toOptionalNumber,
|
|
127
143
|
toOptionalString,
|
|
128
144
|
withContext,
|
|
129
145
|
withSpan
|
|
130
|
-
} from "./chunk-
|
|
146
|
+
} from "./chunk-OIIXZOOC.js";
|
|
131
147
|
import {
|
|
132
148
|
sentry_exports
|
|
133
149
|
} from "./chunk-Z3YD6NHK.js";
|
|
@@ -178,588 +194,6 @@ function getConfigDefaults() {
|
|
|
178
194
|
return cloneDefaults(installDefaults);
|
|
179
195
|
}
|
|
180
196
|
|
|
181
|
-
// src/chat/plugins/logging.ts
|
|
182
|
-
function createAgentPluginLogger(plugin) {
|
|
183
|
-
return {
|
|
184
|
-
info(message, metadata) {
|
|
185
|
-
logInfo(
|
|
186
|
-
"agent_plugin_log_info",
|
|
187
|
-
{},
|
|
188
|
-
{ "app.plugin.name": plugin, ...metadata },
|
|
189
|
-
message
|
|
190
|
-
);
|
|
191
|
-
},
|
|
192
|
-
warn(message, metadata) {
|
|
193
|
-
logWarn(
|
|
194
|
-
"agent_plugin_log_warn",
|
|
195
|
-
{},
|
|
196
|
-
{ "app.plugin.name": plugin, ...metadata },
|
|
197
|
-
message
|
|
198
|
-
);
|
|
199
|
-
},
|
|
200
|
-
error(message, metadata) {
|
|
201
|
-
logException(
|
|
202
|
-
new Error(message),
|
|
203
|
-
"agent_plugin_log_error",
|
|
204
|
-
{},
|
|
205
|
-
{ "app.plugin.name": plugin, ...metadata },
|
|
206
|
-
message
|
|
207
|
-
);
|
|
208
|
-
}
|
|
209
|
-
};
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
// src/chat/plugins/state.ts
|
|
213
|
-
import { createHash } from "crypto";
|
|
214
|
-
var MAX_PLUGIN_STATE_KEY_LENGTH = 512;
|
|
215
|
-
function hashKeyPart(value) {
|
|
216
|
-
return createHash("sha256").update(value).digest("hex").slice(0, 32);
|
|
217
|
-
}
|
|
218
|
-
function pluginStateKey(plugin, key) {
|
|
219
|
-
return `junior:plugin_state:${hashKeyPart(plugin)}:${hashKeyPart(key)}`;
|
|
220
|
-
}
|
|
221
|
-
function validatePluginStateKey(key) {
|
|
222
|
-
if (!key.trim()) {
|
|
223
|
-
throw new Error("Plugin state key is required");
|
|
224
|
-
}
|
|
225
|
-
if (key.length > MAX_PLUGIN_STATE_KEY_LENGTH) {
|
|
226
|
-
throw new Error("Plugin state key exceeds the maximum length");
|
|
227
|
-
}
|
|
228
|
-
}
|
|
229
|
-
function legacyStateKey(key, options) {
|
|
230
|
-
for (const prefix of options?.legacyStatePrefixes ?? []) {
|
|
231
|
-
const trimmed = prefix.trim();
|
|
232
|
-
if (!trimmed) {
|
|
233
|
-
continue;
|
|
234
|
-
}
|
|
235
|
-
if (key === trimmed || key.startsWith(`${trimmed}:`)) {
|
|
236
|
-
return key;
|
|
237
|
-
}
|
|
238
|
-
}
|
|
239
|
-
return void 0;
|
|
240
|
-
}
|
|
241
|
-
function createPluginState(plugin, options) {
|
|
242
|
-
return {
|
|
243
|
-
async delete(key) {
|
|
244
|
-
validatePluginStateKey(key);
|
|
245
|
-
const state = getStateAdapter();
|
|
246
|
-
await state.connect();
|
|
247
|
-
await state.delete(pluginStateKey(plugin, key));
|
|
248
|
-
const legacyKey = legacyStateKey(key, options);
|
|
249
|
-
if (legacyKey) {
|
|
250
|
-
await state.delete(legacyKey);
|
|
251
|
-
}
|
|
252
|
-
},
|
|
253
|
-
async get(key) {
|
|
254
|
-
validatePluginStateKey(key);
|
|
255
|
-
const state = getStateAdapter();
|
|
256
|
-
await state.connect();
|
|
257
|
-
const value = await state.get(pluginStateKey(plugin, key));
|
|
258
|
-
if (value !== null && value !== void 0) {
|
|
259
|
-
return value;
|
|
260
|
-
}
|
|
261
|
-
const legacyKey = legacyStateKey(key, options);
|
|
262
|
-
return legacyKey ? await state.get(legacyKey) ?? void 0 : void 0;
|
|
263
|
-
},
|
|
264
|
-
async set(key, value, ttlMs) {
|
|
265
|
-
validatePluginStateKey(key);
|
|
266
|
-
const state = getStateAdapter();
|
|
267
|
-
await state.connect();
|
|
268
|
-
await state.set(pluginStateKey(plugin, key), value, ttlMs);
|
|
269
|
-
},
|
|
270
|
-
async setIfNotExists(key, value, ttlMs) {
|
|
271
|
-
validatePluginStateKey(key);
|
|
272
|
-
const state = getStateAdapter();
|
|
273
|
-
await state.connect();
|
|
274
|
-
const legacyKey = legacyStateKey(key, options);
|
|
275
|
-
if (legacyKey) {
|
|
276
|
-
const existing = await state.get(legacyKey);
|
|
277
|
-
if (existing !== null && existing !== void 0) {
|
|
278
|
-
return false;
|
|
279
|
-
}
|
|
280
|
-
}
|
|
281
|
-
return await state.setIfNotExists(
|
|
282
|
-
pluginStateKey(plugin, key),
|
|
283
|
-
value,
|
|
284
|
-
ttlMs
|
|
285
|
-
);
|
|
286
|
-
},
|
|
287
|
-
async withLock(key, ttlMs, callback) {
|
|
288
|
-
validatePluginStateKey(key);
|
|
289
|
-
const state = getStateAdapter();
|
|
290
|
-
await state.connect();
|
|
291
|
-
const lockKey = legacyStateKey(key, options) ?? pluginStateKey(plugin, key);
|
|
292
|
-
const lock = await state.acquireLock(lockKey, ttlMs);
|
|
293
|
-
if (!lock) {
|
|
294
|
-
throw new Error(`Could not acquire plugin state lock for ${key}`);
|
|
295
|
-
}
|
|
296
|
-
try {
|
|
297
|
-
return await callback();
|
|
298
|
-
} finally {
|
|
299
|
-
await state.releaseLock(lock);
|
|
300
|
-
}
|
|
301
|
-
}
|
|
302
|
-
};
|
|
303
|
-
}
|
|
304
|
-
|
|
305
|
-
// src/chat/credentials/subject.ts
|
|
306
|
-
import { createHmac, timingSafeEqual } from "crypto";
|
|
307
|
-
var CREDENTIAL_SUBJECT_HMAC_CONTEXT = "junior.credential_subject.v1";
|
|
308
|
-
var CREDENTIAL_SUBJECT_SIGNATURE_VERSION = "v1";
|
|
309
|
-
function getCredentialSubjectSecret() {
|
|
310
|
-
return process.env.JUNIOR_SECRET?.trim() || void 0;
|
|
311
|
-
}
|
|
312
|
-
function buildPayload(input) {
|
|
313
|
-
return [
|
|
314
|
-
CREDENTIAL_SUBJECT_HMAC_CONTEXT,
|
|
315
|
-
input.allowedWhen,
|
|
316
|
-
input.teamId,
|
|
317
|
-
input.channelId,
|
|
318
|
-
input.userId
|
|
319
|
-
].join("\0");
|
|
320
|
-
}
|
|
321
|
-
function signPayload(secret, payload) {
|
|
322
|
-
const digest = createHmac("sha256", secret).update(payload).digest("hex");
|
|
323
|
-
return `${CREDENTIAL_SUBJECT_SIGNATURE_VERSION}=${digest}`;
|
|
324
|
-
}
|
|
325
|
-
function timingSafeMatch(expected, actual) {
|
|
326
|
-
const expectedBuffer = Buffer.from(expected);
|
|
327
|
-
const actualBuffer = Buffer.from(actual);
|
|
328
|
-
if (expectedBuffer.length !== actualBuffer.length) {
|
|
329
|
-
return false;
|
|
330
|
-
}
|
|
331
|
-
return timingSafeEqual(expectedBuffer, actualBuffer);
|
|
332
|
-
}
|
|
333
|
-
function createSlackDirectCredentialSubject(input) {
|
|
334
|
-
const channelId = normalizeSlackConversationId(input.channelId);
|
|
335
|
-
const teamId = input.teamId?.trim();
|
|
336
|
-
const userId = input.userId?.trim();
|
|
337
|
-
if (!channelId || !teamId || !userId || !isDmChannel(channelId)) {
|
|
338
|
-
return void 0;
|
|
339
|
-
}
|
|
340
|
-
return {
|
|
341
|
-
type: "user",
|
|
342
|
-
userId,
|
|
343
|
-
allowedWhen: "private-direct-conversation"
|
|
344
|
-
};
|
|
345
|
-
}
|
|
346
|
-
function bindSlackDirectCredentialSubject(input) {
|
|
347
|
-
const channelId = normalizeSlackConversationId(input.channelId);
|
|
348
|
-
const teamId = input.teamId.trim();
|
|
349
|
-
const secret = getCredentialSubjectSecret();
|
|
350
|
-
const { subject } = input;
|
|
351
|
-
const userId = subject.userId.trim();
|
|
352
|
-
if (!channelId || !teamId || !secret || !isDmChannel(channelId) || subject.type !== "user" || !userId || subject.allowedWhen !== "private-direct-conversation") {
|
|
353
|
-
return void 0;
|
|
354
|
-
}
|
|
355
|
-
return {
|
|
356
|
-
type: "user",
|
|
357
|
-
userId,
|
|
358
|
-
allowedWhen: subject.allowedWhen,
|
|
359
|
-
binding: {
|
|
360
|
-
type: "slack-direct-conversation",
|
|
361
|
-
teamId,
|
|
362
|
-
channelId,
|
|
363
|
-
signature: signPayload(
|
|
364
|
-
secret,
|
|
365
|
-
buildPayload({
|
|
366
|
-
allowedWhen: subject.allowedWhen,
|
|
367
|
-
teamId,
|
|
368
|
-
channelId,
|
|
369
|
-
userId
|
|
370
|
-
})
|
|
371
|
-
)
|
|
372
|
-
}
|
|
373
|
-
};
|
|
374
|
-
}
|
|
375
|
-
function verifySlackDirectCredentialSubject(input) {
|
|
376
|
-
const channelId = normalizeSlackConversationId(input.channelId);
|
|
377
|
-
const secret = getCredentialSubjectSecret();
|
|
378
|
-
if (!channelId || !secret) {
|
|
379
|
-
return false;
|
|
380
|
-
}
|
|
381
|
-
const { subject } = input;
|
|
382
|
-
const binding = subject.binding;
|
|
383
|
-
if (subject.type !== "user" || typeof subject.userId !== "string" || !subject.userId || subject.allowedWhen !== "private-direct-conversation" || !binding || binding.type !== "slack-direct-conversation" || typeof binding.signature !== "string" || !binding.signature || binding.teamId !== input.teamId || binding.channelId !== channelId) {
|
|
384
|
-
return false;
|
|
385
|
-
}
|
|
386
|
-
const expected = signPayload(
|
|
387
|
-
secret,
|
|
388
|
-
buildPayload({
|
|
389
|
-
allowedWhen: subject.allowedWhen,
|
|
390
|
-
teamId: binding.teamId,
|
|
391
|
-
channelId: binding.channelId,
|
|
392
|
-
userId: subject.userId
|
|
393
|
-
})
|
|
394
|
-
);
|
|
395
|
-
return timingSafeMatch(expected, binding.signature);
|
|
396
|
-
}
|
|
397
|
-
|
|
398
|
-
// src/chat/plugins/agent-hooks.ts
|
|
399
|
-
var AgentPluginHookDeniedError = class extends Error {
|
|
400
|
-
constructor(message) {
|
|
401
|
-
super(message);
|
|
402
|
-
this.name = "AgentPluginHookDeniedError";
|
|
403
|
-
}
|
|
404
|
-
};
|
|
405
|
-
var agentPlugins = [];
|
|
406
|
-
var AGENT_PLUGIN_NAME_RE = /^[a-z][a-z0-9-]*$/;
|
|
407
|
-
var AGENT_PLUGIN_TOOL_NAME_RE = /^[a-z][A-Za-z0-9]*$/;
|
|
408
|
-
var AGENT_PLUGIN_ROUTE_METHODS = /* @__PURE__ */ new Set([
|
|
409
|
-
"GET",
|
|
410
|
-
"POST",
|
|
411
|
-
"PUT",
|
|
412
|
-
"PATCH",
|
|
413
|
-
"DELETE",
|
|
414
|
-
"HEAD",
|
|
415
|
-
"OPTIONS",
|
|
416
|
-
"ALL"
|
|
417
|
-
]);
|
|
418
|
-
function validateLegacyStatePrefixes(plugin) {
|
|
419
|
-
const prefixes = plugin.legacyStatePrefixes;
|
|
420
|
-
if (prefixes === void 0) {
|
|
421
|
-
return;
|
|
422
|
-
}
|
|
423
|
-
if (!Array.isArray(prefixes)) {
|
|
424
|
-
throw new Error(
|
|
425
|
-
`Trusted plugin "${plugin.name}" legacyStatePrefixes must be an array`
|
|
426
|
-
);
|
|
427
|
-
}
|
|
428
|
-
const allowedPrefix = `junior:${plugin.name}`;
|
|
429
|
-
for (const rawPrefix of prefixes) {
|
|
430
|
-
const prefix = typeof rawPrefix === "string" ? rawPrefix.trim() : "";
|
|
431
|
-
if (!prefix) {
|
|
432
|
-
throw new Error(
|
|
433
|
-
`Trusted plugin "${plugin.name}" legacy state prefixes must be non-empty strings`
|
|
434
|
-
);
|
|
435
|
-
}
|
|
436
|
-
if (prefix !== allowedPrefix && !prefix.startsWith(`${allowedPrefix}:`)) {
|
|
437
|
-
throw new Error(
|
|
438
|
-
`Trusted plugin "${plugin.name}" legacy state prefix "${prefix}" must stay under "${allowedPrefix}"`
|
|
439
|
-
);
|
|
440
|
-
}
|
|
441
|
-
}
|
|
442
|
-
}
|
|
443
|
-
function validateAgentPlugins(plugins) {
|
|
444
|
-
const seen = /* @__PURE__ */ new Set();
|
|
445
|
-
for (const plugin of plugins) {
|
|
446
|
-
if (!AGENT_PLUGIN_NAME_RE.test(plugin.name)) {
|
|
447
|
-
throw new Error(
|
|
448
|
-
`Trusted plugin name "${plugin.name}" must be a lowercase plugin identifier`
|
|
449
|
-
);
|
|
450
|
-
}
|
|
451
|
-
if (seen.has(plugin.name)) {
|
|
452
|
-
throw new Error(`Duplicate trusted plugin name "${plugin.name}"`);
|
|
453
|
-
}
|
|
454
|
-
seen.add(plugin.name);
|
|
455
|
-
validateLegacyStatePrefixes(plugin);
|
|
456
|
-
}
|
|
457
|
-
}
|
|
458
|
-
function setAgentPlugins(plugins) {
|
|
459
|
-
validateAgentPlugins(plugins);
|
|
460
|
-
const previous = agentPlugins;
|
|
461
|
-
agentPlugins = [...plugins].sort(
|
|
462
|
-
(left, right) => left.name.localeCompare(right.name)
|
|
463
|
-
);
|
|
464
|
-
return previous;
|
|
465
|
-
}
|
|
466
|
-
function getAgentPlugins() {
|
|
467
|
-
return [...agentPlugins];
|
|
468
|
-
}
|
|
469
|
-
function getAgentPluginTools(context) {
|
|
470
|
-
const tools = {};
|
|
471
|
-
for (const plugin of getAgentPlugins()) {
|
|
472
|
-
const hook = plugin.hooks?.tools;
|
|
473
|
-
if (!hook) {
|
|
474
|
-
continue;
|
|
475
|
-
}
|
|
476
|
-
const log = createAgentPluginLogger(plugin.name);
|
|
477
|
-
const credentialSubject = createSlackDirectCredentialSubject({
|
|
478
|
-
channelId: context.channelId,
|
|
479
|
-
teamId: context.teamId,
|
|
480
|
-
userId: context.requester?.userId
|
|
481
|
-
});
|
|
482
|
-
const pluginTools = hook({
|
|
483
|
-
plugin: { name: plugin.name },
|
|
484
|
-
log,
|
|
485
|
-
requester: context.requester,
|
|
486
|
-
channelCapabilities: context.channelCapabilities,
|
|
487
|
-
channelId: context.channelId,
|
|
488
|
-
...credentialSubject ? { credentialSubject } : {},
|
|
489
|
-
teamId: context.teamId,
|
|
490
|
-
messageTs: context.messageTs,
|
|
491
|
-
threadTs: context.threadTs,
|
|
492
|
-
userText: context.userText,
|
|
493
|
-
state: createPluginState(plugin.name, {
|
|
494
|
-
legacyStatePrefixes: plugin.legacyStatePrefixes
|
|
495
|
-
})
|
|
496
|
-
});
|
|
497
|
-
for (const [name, tool2] of Object.entries(pluginTools)) {
|
|
498
|
-
if (!AGENT_PLUGIN_TOOL_NAME_RE.test(name)) {
|
|
499
|
-
throw new Error(
|
|
500
|
-
`Trusted plugin tool "${name}" from plugin "${plugin.name}" must be a camelCase identifier`
|
|
501
|
-
);
|
|
502
|
-
}
|
|
503
|
-
if (tools[name]) {
|
|
504
|
-
throw new Error(
|
|
505
|
-
`Duplicate trusted plugin tool "${name}" from plugin "${plugin.name}"`
|
|
506
|
-
);
|
|
507
|
-
}
|
|
508
|
-
tools[name] = tool2;
|
|
509
|
-
}
|
|
510
|
-
}
|
|
511
|
-
return tools;
|
|
512
|
-
}
|
|
513
|
-
function routeMethods(route, pluginName) {
|
|
514
|
-
const methods = Array.isArray(route.method) ? route.method : [route.method ?? "ALL"];
|
|
515
|
-
if (methods.length === 0) {
|
|
516
|
-
throw new Error(
|
|
517
|
-
`Trusted plugin route "${route.path}" from plugin "${pluginName}" must declare at least one method`
|
|
518
|
-
);
|
|
519
|
-
}
|
|
520
|
-
for (const method of methods) {
|
|
521
|
-
if (!AGENT_PLUGIN_ROUTE_METHODS.has(method)) {
|
|
522
|
-
throw new Error(
|
|
523
|
-
`Trusted plugin route "${route.path}" from plugin "${pluginName}" has invalid method "${String(method)}"`
|
|
524
|
-
);
|
|
525
|
-
}
|
|
526
|
-
}
|
|
527
|
-
if (methods.includes("ALL") && methods.length > 1) {
|
|
528
|
-
throw new Error(
|
|
529
|
-
`Trusted plugin route "${route.path}" from plugin "${pluginName}" must not combine ALL with explicit methods`
|
|
530
|
-
);
|
|
531
|
-
}
|
|
532
|
-
return methods;
|
|
533
|
-
}
|
|
534
|
-
function getAgentPluginRoutes() {
|
|
535
|
-
const routes = [];
|
|
536
|
-
const seen = /* @__PURE__ */ new Set();
|
|
537
|
-
const methodsByPath = /* @__PURE__ */ new Map();
|
|
538
|
-
for (const plugin of getAgentPlugins()) {
|
|
539
|
-
const hook = plugin.hooks?.routes;
|
|
540
|
-
if (!hook) {
|
|
541
|
-
continue;
|
|
542
|
-
}
|
|
543
|
-
const log = createAgentPluginLogger(plugin.name);
|
|
544
|
-
const pluginRoutes = hook({
|
|
545
|
-
plugin: { name: plugin.name },
|
|
546
|
-
log
|
|
547
|
-
});
|
|
548
|
-
if (!Array.isArray(pluginRoutes)) {
|
|
549
|
-
throw new Error(
|
|
550
|
-
`Trusted plugin routes hook from plugin "${plugin.name}" must return an array`
|
|
551
|
-
);
|
|
552
|
-
}
|
|
553
|
-
for (const route of pluginRoutes) {
|
|
554
|
-
if (!isRecord2(route)) {
|
|
555
|
-
throw new Error(
|
|
556
|
-
`Trusted plugin route from plugin "${plugin.name}" must be an object`
|
|
557
|
-
);
|
|
558
|
-
}
|
|
559
|
-
if (typeof route.path !== "string" || !route.path.startsWith("/")) {
|
|
560
|
-
throw new Error(
|
|
561
|
-
`Trusted plugin route "${route.path}" from plugin "${plugin.name}" must start with /`
|
|
562
|
-
);
|
|
563
|
-
}
|
|
564
|
-
if (typeof route.handler !== "function") {
|
|
565
|
-
throw new Error(
|
|
566
|
-
`Trusted plugin route "${route.path}" from plugin "${plugin.name}" must provide a handler`
|
|
567
|
-
);
|
|
568
|
-
}
|
|
569
|
-
const methods = routeMethods(route, plugin.name);
|
|
570
|
-
const pathMethods = methodsByPath.get(route.path) ?? /* @__PURE__ */ new Set();
|
|
571
|
-
if (pathMethods.has("ALL") || methods.includes("ALL") && pathMethods.size > 0) {
|
|
572
|
-
throw new Error(
|
|
573
|
-
`Trusted plugin route "${route.path}" conflicts with an ALL route for the same path`
|
|
574
|
-
);
|
|
575
|
-
}
|
|
576
|
-
for (const method of methods) {
|
|
577
|
-
const key = `${method}:${route.path}`;
|
|
578
|
-
if (seen.has(key)) {
|
|
579
|
-
throw new Error(
|
|
580
|
-
`Duplicate trusted plugin route "${method} ${route.path}"`
|
|
581
|
-
);
|
|
582
|
-
}
|
|
583
|
-
seen.add(key);
|
|
584
|
-
pathMethods.add(method);
|
|
585
|
-
}
|
|
586
|
-
methodsByPath.set(route.path, pathMethods);
|
|
587
|
-
routes.push({
|
|
588
|
-
...route,
|
|
589
|
-
pluginName: plugin.name
|
|
590
|
-
});
|
|
591
|
-
}
|
|
592
|
-
}
|
|
593
|
-
return routes;
|
|
594
|
-
}
|
|
595
|
-
function trustedSlackConversationUrl(pluginName, link) {
|
|
596
|
-
const url = typeof link?.url === "string" ? link.url.trim() : "";
|
|
597
|
-
if (!url) {
|
|
598
|
-
return void 0;
|
|
599
|
-
}
|
|
600
|
-
let parsed;
|
|
601
|
-
try {
|
|
602
|
-
parsed = new URL(url);
|
|
603
|
-
} catch (error) {
|
|
604
|
-
throw new Error(
|
|
605
|
-
`Trusted plugin "${pluginName}" slackConversationLink must return an absolute http(s) URL`,
|
|
606
|
-
{ cause: error }
|
|
607
|
-
);
|
|
608
|
-
}
|
|
609
|
-
if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
|
|
610
|
-
throw new Error(
|
|
611
|
-
`Trusted plugin "${pluginName}" slackConversationLink must return an absolute http(s) URL`
|
|
612
|
-
);
|
|
613
|
-
}
|
|
614
|
-
return parsed.toString();
|
|
615
|
-
}
|
|
616
|
-
function getAgentPluginSlackConversationLink(conversationId) {
|
|
617
|
-
for (const plugin of getAgentPlugins()) {
|
|
618
|
-
const hook = plugin.hooks?.slackConversationLink;
|
|
619
|
-
if (!hook) {
|
|
620
|
-
continue;
|
|
621
|
-
}
|
|
622
|
-
const log = createAgentPluginLogger(plugin.name);
|
|
623
|
-
const link = hook({
|
|
624
|
-
plugin: { name: plugin.name },
|
|
625
|
-
log,
|
|
626
|
-
conversationId
|
|
627
|
-
});
|
|
628
|
-
const url = trustedSlackConversationUrl(plugin.name, link);
|
|
629
|
-
if (url) {
|
|
630
|
-
return { url };
|
|
631
|
-
}
|
|
632
|
-
}
|
|
633
|
-
return void 0;
|
|
634
|
-
}
|
|
635
|
-
function isRecord2(value) {
|
|
636
|
-
return Boolean(value && typeof value === "object" && !Array.isArray(value));
|
|
637
|
-
}
|
|
638
|
-
function normalizeEnv(value) {
|
|
639
|
-
if (!isRecord2(value)) {
|
|
640
|
-
return {};
|
|
641
|
-
}
|
|
642
|
-
const env = {};
|
|
643
|
-
for (const [key, rawValue] of Object.entries(value)) {
|
|
644
|
-
if (typeof rawValue === "string") {
|
|
645
|
-
env[key] = rawValue;
|
|
646
|
-
}
|
|
647
|
-
}
|
|
648
|
-
return env;
|
|
649
|
-
}
|
|
650
|
-
function createSandboxCapability(sandbox) {
|
|
651
|
-
return {
|
|
652
|
-
root: SANDBOX_WORKSPACE_ROOT,
|
|
653
|
-
juniorRoot: `${SANDBOX_WORKSPACE_ROOT}/.junior`,
|
|
654
|
-
async readFile(filePath) {
|
|
655
|
-
return await sandbox.readFileToBuffer({ path: filePath }) ?? null;
|
|
656
|
-
},
|
|
657
|
-
async run(input) {
|
|
658
|
-
const result = await sandbox.runCommand(input);
|
|
659
|
-
const [stdout, stderr] = await Promise.all([
|
|
660
|
-
result.stdout(),
|
|
661
|
-
result.stderr()
|
|
662
|
-
]);
|
|
663
|
-
return {
|
|
664
|
-
exitCode: result.exitCode,
|
|
665
|
-
stdout,
|
|
666
|
-
stderr
|
|
667
|
-
};
|
|
668
|
-
},
|
|
669
|
-
async writeFile(input) {
|
|
670
|
-
await sandbox.writeFiles([
|
|
671
|
-
{
|
|
672
|
-
path: input.path,
|
|
673
|
-
content: input.content,
|
|
674
|
-
...input.mode !== void 0 ? { mode: input.mode } : {}
|
|
675
|
-
}
|
|
676
|
-
]);
|
|
677
|
-
}
|
|
678
|
-
};
|
|
679
|
-
}
|
|
680
|
-
function createAgentPluginHookRunner(input = {}) {
|
|
681
|
-
const loaded = getAgentPlugins();
|
|
682
|
-
return {
|
|
683
|
-
async prepareSandbox(sandbox) {
|
|
684
|
-
const sandboxCapability = createSandboxCapability(sandbox);
|
|
685
|
-
for (const plugin of loaded) {
|
|
686
|
-
const hook = plugin.hooks?.sandboxPrepare;
|
|
687
|
-
if (!hook) {
|
|
688
|
-
continue;
|
|
689
|
-
}
|
|
690
|
-
logInfo(
|
|
691
|
-
"agent_plugin_hook_sandbox_prepare",
|
|
692
|
-
{},
|
|
693
|
-
{ "app.plugin.name": plugin.name },
|
|
694
|
-
"Running agent plugin sandbox prepare hook"
|
|
695
|
-
);
|
|
696
|
-
await hook({
|
|
697
|
-
plugin: { name: plugin.name },
|
|
698
|
-
log: createAgentPluginLogger(plugin.name),
|
|
699
|
-
requester: input.requester,
|
|
700
|
-
sandbox: sandboxCapability
|
|
701
|
-
});
|
|
702
|
-
}
|
|
703
|
-
},
|
|
704
|
-
async beforeToolExecute(tool2) {
|
|
705
|
-
let nextInput = { ...tool2.input };
|
|
706
|
-
const env = normalizeEnv(nextInput.env);
|
|
707
|
-
for (const plugin of loaded) {
|
|
708
|
-
const hook = plugin.hooks?.beforeToolExecute;
|
|
709
|
-
if (!hook) {
|
|
710
|
-
continue;
|
|
711
|
-
}
|
|
712
|
-
let replacement;
|
|
713
|
-
let denied;
|
|
714
|
-
await hook({
|
|
715
|
-
plugin: { name: plugin.name },
|
|
716
|
-
log: createAgentPluginLogger(plugin.name),
|
|
717
|
-
requester: input.requester,
|
|
718
|
-
tool: {
|
|
719
|
-
name: tool2.name,
|
|
720
|
-
input: nextInput
|
|
721
|
-
},
|
|
722
|
-
env: {
|
|
723
|
-
get(key) {
|
|
724
|
-
return env[key];
|
|
725
|
-
},
|
|
726
|
-
set(key, value) {
|
|
727
|
-
env[key] = value;
|
|
728
|
-
}
|
|
729
|
-
},
|
|
730
|
-
decision: {
|
|
731
|
-
deny(message) {
|
|
732
|
-
denied = message;
|
|
733
|
-
},
|
|
734
|
-
replaceInput(input2) {
|
|
735
|
-
replacement = input2;
|
|
736
|
-
}
|
|
737
|
-
}
|
|
738
|
-
});
|
|
739
|
-
if (denied) {
|
|
740
|
-
throw new AgentPluginHookDeniedError(denied);
|
|
741
|
-
}
|
|
742
|
-
if (replacement !== void 0) {
|
|
743
|
-
if (!isRecord2(replacement)) {
|
|
744
|
-
throw new Error(
|
|
745
|
-
`Plugin "${plugin.name}" replaced tool input with a non-object value`
|
|
746
|
-
);
|
|
747
|
-
}
|
|
748
|
-
nextInput = { ...replacement };
|
|
749
|
-
Object.assign(env, normalizeEnv(nextInput.env));
|
|
750
|
-
}
|
|
751
|
-
}
|
|
752
|
-
return {
|
|
753
|
-
input: {
|
|
754
|
-
...nextInput,
|
|
755
|
-
...Object.keys(env).length > 0 ? { env } : {}
|
|
756
|
-
},
|
|
757
|
-
env
|
|
758
|
-
};
|
|
759
|
-
}
|
|
760
|
-
};
|
|
761
|
-
}
|
|
762
|
-
|
|
763
197
|
// src/chat/respond.ts
|
|
764
198
|
import { Agent as Agent2 } from "@earendil-works/pi-agent-core";
|
|
765
199
|
import { THREAD_STATE_TTL_MS as THREAD_STATE_TTL_MS3 } from "chat";
|
|
@@ -1915,7 +1349,7 @@ function parseMcpProviderFromToolName(toolName) {
|
|
|
1915
1349
|
|
|
1916
1350
|
// src/chat/pi/derived-state.ts
|
|
1917
1351
|
var MCP_BRIDGE_TOOLS = /* @__PURE__ */ new Set(["callMcpTool", "searchMcpTools"]);
|
|
1918
|
-
function
|
|
1352
|
+
function isRecord2(value) {
|
|
1919
1353
|
return Boolean(value && typeof value === "object" && !Array.isArray(value));
|
|
1920
1354
|
}
|
|
1921
1355
|
function providerFromToolName(value) {
|
|
@@ -1937,7 +1371,7 @@ function addBridgeToolProvider(toolName, value, providers) {
|
|
|
1937
1371
|
if (bridgeTool === "searchMcpTools") {
|
|
1938
1372
|
for (const argsKey of ["input", "args", "arguments", "params"]) {
|
|
1939
1373
|
const args = value[argsKey];
|
|
1940
|
-
if (
|
|
1374
|
+
if (isRecord2(args)) {
|
|
1941
1375
|
addString(providers, args.provider);
|
|
1942
1376
|
}
|
|
1943
1377
|
}
|
|
@@ -1946,7 +1380,7 @@ function addBridgeToolProvider(toolName, value, providers) {
|
|
|
1946
1380
|
if (bridgeTool === "callMcpTool") {
|
|
1947
1381
|
for (const argsKey of ["input", "args", "arguments", "params"]) {
|
|
1948
1382
|
const args = value[argsKey];
|
|
1949
|
-
if (
|
|
1383
|
+
if (isRecord2(args)) {
|
|
1950
1384
|
addString(providers, providerFromToolName(args.tool_name));
|
|
1951
1385
|
}
|
|
1952
1386
|
}
|
|
@@ -1959,18 +1393,18 @@ function addMcpResultProvider(message, providers) {
|
|
|
1959
1393
|
return;
|
|
1960
1394
|
}
|
|
1961
1395
|
if (toolName === "loadSkill") {
|
|
1962
|
-
if (
|
|
1396
|
+
if (isRecord2(message.details)) {
|
|
1963
1397
|
addString(providers, message.details.mcp_provider);
|
|
1964
1398
|
}
|
|
1965
1399
|
addString(providers, message.mcp_provider);
|
|
1966
1400
|
return;
|
|
1967
1401
|
}
|
|
1968
1402
|
if (toolName === "searchMcpTools") {
|
|
1969
|
-
if (
|
|
1403
|
+
if (isRecord2(message.details)) {
|
|
1970
1404
|
addString(providers, message.details.provider);
|
|
1971
1405
|
if (Array.isArray(message.details.tools)) {
|
|
1972
1406
|
for (const tool2 of message.details.tools) {
|
|
1973
|
-
if (
|
|
1407
|
+
if (isRecord2(tool2)) {
|
|
1974
1408
|
addString(providers, providerFromToolName(tool2.tool_name));
|
|
1975
1409
|
}
|
|
1976
1410
|
}
|
|
@@ -1982,11 +1416,11 @@ function addMcpResultProvider(message, providers) {
|
|
|
1982
1416
|
if (toolName === "callMcpTool") {
|
|
1983
1417
|
for (const argsKey of ["input", "args", "arguments", "params"]) {
|
|
1984
1418
|
const args = message[argsKey];
|
|
1985
|
-
if (
|
|
1419
|
+
if (isRecord2(args)) {
|
|
1986
1420
|
addString(providers, providerFromToolName(args.tool_name));
|
|
1987
1421
|
}
|
|
1988
1422
|
}
|
|
1989
|
-
if (
|
|
1423
|
+
if (isRecord2(message.details)) {
|
|
1990
1424
|
addString(providers, message.details.provider);
|
|
1991
1425
|
addString(providers, providerFromToolName(message.details.tool_name));
|
|
1992
1426
|
}
|
|
@@ -1994,7 +1428,7 @@ function addMcpResultProvider(message, providers) {
|
|
|
1994
1428
|
}
|
|
1995
1429
|
}
|
|
1996
1430
|
function scanMcpProviders(message, providers) {
|
|
1997
|
-
if (!
|
|
1431
|
+
if (!isRecord2(message)) {
|
|
1998
1432
|
return;
|
|
1999
1433
|
}
|
|
2000
1434
|
if (message.role === "toolResult") {
|
|
@@ -2006,15 +1440,15 @@ function scanMcpProviders(message, providers) {
|
|
|
2006
1440
|
return;
|
|
2007
1441
|
}
|
|
2008
1442
|
for (const part of content) {
|
|
2009
|
-
if (!
|
|
1443
|
+
if (!isRecord2(part)) {
|
|
2010
1444
|
continue;
|
|
2011
1445
|
}
|
|
2012
1446
|
addBridgeToolProvider(getToolName(part), part, providers);
|
|
2013
1447
|
}
|
|
2014
1448
|
}
|
|
2015
1449
|
function scanLoadedSkills(message, skills) {
|
|
2016
|
-
if (
|
|
2017
|
-
if (
|
|
1450
|
+
if (isRecord2(message) && message.role === "toolResult" && message.toolName === "loadSkill" && message.isError !== true) {
|
|
1451
|
+
if (isRecord2(message.details)) {
|
|
2018
1452
|
addString(skills, message.details.skill_name);
|
|
2019
1453
|
}
|
|
2020
1454
|
addString(skills, message.skill_name);
|
|
@@ -3906,17 +3340,6 @@ function createSlackChannelListMessagesTool(context) {
|
|
|
3906
3340
|
// src/chat/tools/slack/channel-post-message.ts
|
|
3907
3341
|
import { Type as Type14 } from "@sinclair/typebox";
|
|
3908
3342
|
|
|
3909
|
-
// src/chat/slack/emoji.ts
|
|
3910
|
-
var SLACK_EMOJI_NAME_RE = /^(?:[a-z0-9_+-]+)(?:::(?:skin-tone-[2-6]))?$/;
|
|
3911
|
-
function normalizeSlackEmojiName(value) {
|
|
3912
|
-
const trimmed = value.trim().toLowerCase();
|
|
3913
|
-
if (!trimmed) {
|
|
3914
|
-
return null;
|
|
3915
|
-
}
|
|
3916
|
-
const normalized = trimmed.startsWith(":") && trimmed.endsWith(":") ? trimmed.slice(1, -1) : trimmed;
|
|
3917
|
-
return SLACK_EMOJI_NAME_RE.test(normalized) ? normalized : null;
|
|
3918
|
-
}
|
|
3919
|
-
|
|
3920
3343
|
// src/chat/slack/outbound.ts
|
|
3921
3344
|
var MAX_SLACK_MESSAGE_TEXT_CHARS = 4e4;
|
|
3922
3345
|
function requireSlackConversationId(channelId, action) {
|
|
@@ -4024,7 +3447,7 @@ async function postSlackEphemeralMessage(input) {
|
|
|
4024
3447
|
input.channelId,
|
|
4025
3448
|
"Slack ephemeral message posting"
|
|
4026
3449
|
);
|
|
4027
|
-
const userId = input.userId
|
|
3450
|
+
const userId = parseActorUserId(input.userId);
|
|
4028
3451
|
if (!userId) {
|
|
4029
3452
|
throw new Error("Slack ephemeral message posting requires a user ID");
|
|
4030
3453
|
}
|
|
@@ -7308,7 +6731,7 @@ async function startOAuthFlow(provider, input) {
|
|
|
7308
6731
|
}
|
|
7309
6732
|
|
|
7310
6733
|
// src/chat/sandbox/egress-session.ts
|
|
7311
|
-
import { createHmac
|
|
6734
|
+
import { createHmac, randomUUID, timingSafeEqual } from "crypto";
|
|
7312
6735
|
var SANDBOX_EGRESS_PROXY_PATH = "/api/internal/sandbox-egress";
|
|
7313
6736
|
var SANDBOX_EGRESS_TOKEN_VERSION = "v1";
|
|
7314
6737
|
var SANDBOX_EGRESS_HMAC_CONTEXT = "junior.sandbox_egress.v1";
|
|
@@ -7332,16 +6755,16 @@ function base64Url(input) {
|
|
|
7332
6755
|
function fromBase64Url(input) {
|
|
7333
6756
|
return Buffer.from(input, "base64url").toString("utf8");
|
|
7334
6757
|
}
|
|
7335
|
-
function
|
|
7336
|
-
return
|
|
6758
|
+
function signPayload(payload) {
|
|
6759
|
+
return createHmac("sha256", getSandboxEgressSecret()).update(`${SANDBOX_EGRESS_HMAC_CONTEXT}:${payload}`).digest("base64url");
|
|
7337
6760
|
}
|
|
7338
|
-
function
|
|
6761
|
+
function timingSafeMatch(expected, actual) {
|
|
7339
6762
|
const expectedBuffer = Buffer.from(expected);
|
|
7340
6763
|
const actualBuffer = Buffer.from(actual);
|
|
7341
6764
|
if (expectedBuffer.length !== actualBuffer.length) {
|
|
7342
6765
|
return false;
|
|
7343
6766
|
}
|
|
7344
|
-
return
|
|
6767
|
+
return timingSafeEqual(expectedBuffer, actualBuffer);
|
|
7345
6768
|
}
|
|
7346
6769
|
function parseSandboxEgressContext(value) {
|
|
7347
6770
|
if (!value || typeof value !== "object") {
|
|
@@ -7400,7 +6823,7 @@ function createSandboxEgressCredentialToken(input) {
|
|
|
7400
6823
|
const payload = `${SANDBOX_EGRESS_TOKEN_VERSION}.${base64Url(
|
|
7401
6824
|
JSON.stringify(context)
|
|
7402
6825
|
)}`;
|
|
7403
|
-
return `${payload}.${
|
|
6826
|
+
return `${payload}.${signPayload(payload)}`;
|
|
7404
6827
|
}
|
|
7405
6828
|
function parseSandboxEgressCredentialToken(token) {
|
|
7406
6829
|
if (!token) {
|
|
@@ -7416,7 +6839,7 @@ function parseSandboxEgressCredentialToken(token) {
|
|
|
7416
6839
|
return void 0;
|
|
7417
6840
|
}
|
|
7418
6841
|
const payload = `${parts[0]}.${encodedSession}`;
|
|
7419
|
-
if (!
|
|
6842
|
+
if (!timingSafeMatch(signPayload(payload), signature)) {
|
|
7420
6843
|
return void 0;
|
|
7421
6844
|
}
|
|
7422
6845
|
try {
|
|
@@ -11125,6 +10548,7 @@ async function persistRunningSessionRecord(args) {
|
|
|
11125
10548
|
sliceId: args.sliceId,
|
|
11126
10549
|
state: "running",
|
|
11127
10550
|
piMessages: args.messages,
|
|
10551
|
+
...args.surface ?? latestSessionRecord?.surface ? { surface: args.surface ?? latestSessionRecord?.surface } : {},
|
|
11128
10552
|
...args.loadedSkillNames ? { loadedSkillNames: args.loadedSkillNames } : {},
|
|
11129
10553
|
...args.requester ?? latestSessionRecord?.requester ? { requester: args.requester ?? latestSessionRecord?.requester } : {},
|
|
11130
10554
|
...getActiveTraceId() ?? latestSessionRecord?.traceId ? { traceId: getActiveTraceId() ?? latestSessionRecord?.traceId } : {}
|
|
@@ -11164,6 +10588,7 @@ async function persistCompletedSessionRecord(args) {
|
|
|
11164
10588
|
sliceId: args.sliceId,
|
|
11165
10589
|
state: "completed",
|
|
11166
10590
|
piMessages: args.allMessages,
|
|
10591
|
+
...args.surface ?? latestSessionRecord?.surface ? { surface: args.surface ?? latestSessionRecord?.surface } : {},
|
|
11167
10592
|
...args.loadedSkillNames ? { loadedSkillNames: args.loadedSkillNames } : {},
|
|
11168
10593
|
...args.requester ?? latestSessionRecord?.requester ? { requester: args.requester ?? latestSessionRecord?.requester } : {},
|
|
11169
10594
|
...getActiveTraceId() ?? latestSessionRecord?.traceId ? { traceId: getActiveTraceId() ?? latestSessionRecord?.traceId } : {}
|
|
@@ -11209,6 +10634,7 @@ async function persistAuthPauseSessionRecord(args) {
|
|
|
11209
10634
|
sliceId: nextSliceId,
|
|
11210
10635
|
state: "awaiting_resume",
|
|
11211
10636
|
piMessages,
|
|
10637
|
+
...args.surface ?? latestSessionRecord?.surface ? { surface: args.surface ?? latestSessionRecord?.surface } : {},
|
|
11212
10638
|
...args.loadedSkillNames ? { loadedSkillNames: args.loadedSkillNames } : {},
|
|
11213
10639
|
resumeReason: "auth",
|
|
11214
10640
|
resumedFromSliceId: args.currentSliceId,
|
|
@@ -11264,6 +10690,7 @@ async function persistTimeoutSessionRecord(args) {
|
|
|
11264
10690
|
sliceId: args.currentSliceId,
|
|
11265
10691
|
state: "failed",
|
|
11266
10692
|
piMessages,
|
|
10693
|
+
...args.surface ?? latestSessionRecord?.surface ? { surface: args.surface ?? latestSessionRecord?.surface } : {},
|
|
11267
10694
|
...args.loadedSkillNames ? { loadedSkillNames: args.loadedSkillNames } : {},
|
|
11268
10695
|
resumeReason: "timeout",
|
|
11269
10696
|
resumedFromSliceId: latestSessionRecord?.resumedFromSliceId,
|
|
@@ -11281,6 +10708,7 @@ async function persistTimeoutSessionRecord(args) {
|
|
|
11281
10708
|
sliceId: nextSliceId,
|
|
11282
10709
|
state: "awaiting_resume",
|
|
11283
10710
|
piMessages,
|
|
10711
|
+
...args.surface ?? latestSessionRecord?.surface ? { surface: args.surface ?? latestSessionRecord?.surface } : {},
|
|
11284
10712
|
...args.loadedSkillNames ? { loadedSkillNames: args.loadedSkillNames } : {},
|
|
11285
10713
|
resumeReason: "timeout",
|
|
11286
10714
|
resumedFromSliceId: args.currentSliceId,
|
|
@@ -11330,6 +10758,7 @@ async function persistYieldSessionRecord(args) {
|
|
|
11330
10758
|
sliceId: args.currentSliceId,
|
|
11331
10759
|
state: "awaiting_resume",
|
|
11332
10760
|
piMessages,
|
|
10761
|
+
...args.surface ?? latestSessionRecord?.surface ? { surface: args.surface ?? latestSessionRecord?.surface } : {},
|
|
11333
10762
|
...args.loadedSkillNames ? { loadedSkillNames: args.loadedSkillNames } : {},
|
|
11334
10763
|
resumeReason: "yield",
|
|
11335
10764
|
resumedFromSliceId: latestSessionRecord?.resumedFromSliceId,
|
|
@@ -11789,13 +11218,34 @@ function extractSliceUsage(messages, beforeMessageCount) {
|
|
|
11789
11218
|
return hasAgentTurnUsage(usage) ? usage : void 0;
|
|
11790
11219
|
}
|
|
11791
11220
|
function requesterFromContext(requester, requesterId) {
|
|
11792
|
-
const identity =
|
|
11793
|
-
|
|
11794
|
-
...
|
|
11795
|
-
...
|
|
11796
|
-
...
|
|
11221
|
+
const identity = actorRequesterFromContext(requester, requesterId);
|
|
11222
|
+
const agentRequester = {
|
|
11223
|
+
...identity?.email ? { email: identity.email } : {},
|
|
11224
|
+
...identity?.fullName ? { fullName: identity.fullName } : {},
|
|
11225
|
+
...identity?.userId ? { slackUserId: identity.userId } : {},
|
|
11226
|
+
...identity?.userName ? { slackUserName: identity.userName } : {}
|
|
11797
11227
|
};
|
|
11798
|
-
return Object.keys(
|
|
11228
|
+
return Object.keys(agentRequester).length > 0 ? agentRequester : void 0;
|
|
11229
|
+
}
|
|
11230
|
+
function actorRequesterFromContext(requester, requesterId) {
|
|
11231
|
+
return buildActorIdentity(requester, requesterId);
|
|
11232
|
+
}
|
|
11233
|
+
function surfaceFromContext(context) {
|
|
11234
|
+
if (context.surface) {
|
|
11235
|
+
return context.surface;
|
|
11236
|
+
}
|
|
11237
|
+
const conversationId = context.correlation?.conversationId ?? context.correlation?.threadId ?? context.correlation?.runId;
|
|
11238
|
+
if (context.slackConversation || (conversationId ? parseSlackThreadId(conversationId) : void 0)) {
|
|
11239
|
+
return "slack";
|
|
11240
|
+
}
|
|
11241
|
+
const actor = context.credentialContext?.actor;
|
|
11242
|
+
if (actor?.type === "system" && actor.id === "scheduler") {
|
|
11243
|
+
return "scheduler";
|
|
11244
|
+
}
|
|
11245
|
+
if (conversationId) {
|
|
11246
|
+
return "api";
|
|
11247
|
+
}
|
|
11248
|
+
return void 0;
|
|
11799
11249
|
}
|
|
11800
11250
|
function supportsRouterTextPreview(mediaType) {
|
|
11801
11251
|
const baseMediaType = mediaType.split(";", 1)[0]?.trim().toLowerCase();
|
|
@@ -11912,6 +11362,11 @@ async function generateAssistantReply(messageText2, context = {}) {
|
|
|
11912
11362
|
context.requester,
|
|
11913
11363
|
context.correlation?.requesterId
|
|
11914
11364
|
);
|
|
11365
|
+
const actorRequester = actorRequesterFromContext(
|
|
11366
|
+
context.requester,
|
|
11367
|
+
context.correlation?.requesterId
|
|
11368
|
+
);
|
|
11369
|
+
const surface = surfaceFromContext(context);
|
|
11915
11370
|
const credentialActor = context.credentialContext?.actor;
|
|
11916
11371
|
const credentialActorLogContext = credentialActor ? {
|
|
11917
11372
|
actorType: credentialActor.type,
|
|
@@ -12040,7 +11495,7 @@ async function generateAssistantReply(messageText2, context = {}) {
|
|
|
12040
11495
|
const authRequesterId = context.credentialContext?.actor.type === "user" ? context.credentialContext.actor.userId : void 0;
|
|
12041
11496
|
const userTokenStore = createUserTokenStore();
|
|
12042
11497
|
const agentPluginHooks = createAgentPluginHookRunner({
|
|
12043
|
-
requester:
|
|
11498
|
+
requester: actorRequester
|
|
12044
11499
|
});
|
|
12045
11500
|
sandboxExecutor = createSandboxExecutor({
|
|
12046
11501
|
sandboxId: context.sandbox?.sandboxId,
|
|
@@ -12057,7 +11512,7 @@ async function generateAssistantReply(messageText2, context = {}) {
|
|
|
12057
11512
|
const result = await maybeExecuteJrRpcCustomCommand(command, {
|
|
12058
11513
|
activeSkill: skillSandbox.getActiveSkill(),
|
|
12059
11514
|
channelConfiguration: context.channelConfiguration,
|
|
12060
|
-
requesterId:
|
|
11515
|
+
requesterId: actorRequester?.userId,
|
|
12061
11516
|
onConfigurationValueChanged: (key, value) => {
|
|
12062
11517
|
if (value === void 0) {
|
|
12063
11518
|
delete configurationValues[key];
|
|
@@ -12293,7 +11748,7 @@ async function generateAssistantReply(messageText2, context = {}) {
|
|
|
12293
11748
|
{
|
|
12294
11749
|
channelId: toolChannelId,
|
|
12295
11750
|
channelCapabilities,
|
|
12296
|
-
requester:
|
|
11751
|
+
requester: actorRequester,
|
|
12297
11752
|
teamId: context.correlation?.teamId,
|
|
12298
11753
|
messageTs: context.correlation?.messageTs,
|
|
12299
11754
|
threadTs: context.correlation?.threadTs,
|
|
@@ -12356,7 +11811,7 @@ async function generateAssistantReply(messageText2, context = {}) {
|
|
|
12356
11811
|
slackConversation: context.slackConversation
|
|
12357
11812
|
},
|
|
12358
11813
|
invocation: skillInvocation,
|
|
12359
|
-
requester:
|
|
11814
|
+
requester: actorRequester,
|
|
12360
11815
|
artifactState: context.artifactState,
|
|
12361
11816
|
configuration: configurationValues
|
|
12362
11817
|
}) : null;
|
|
@@ -12437,7 +11892,8 @@ async function generateAssistantReply(messageText2, context = {}) {
|
|
|
12437
11892
|
messages,
|
|
12438
11893
|
loadedSkillNames: loadedSkillNamesForResume,
|
|
12439
11894
|
logContext: sessionRecordLogContext,
|
|
12440
|
-
requester
|
|
11895
|
+
requester,
|
|
11896
|
+
...surface ? { surface } : {}
|
|
12441
11897
|
});
|
|
12442
11898
|
if (!persisted) {
|
|
12443
11899
|
return false;
|
|
@@ -12750,7 +12206,8 @@ async function generateAssistantReply(messageText2, context = {}) {
|
|
|
12750
12206
|
allMessages: agent.state.messages,
|
|
12751
12207
|
loadedSkillNames: loadedSkillNamesForResume,
|
|
12752
12208
|
logContext: sessionRecordLogContext,
|
|
12753
|
-
requester
|
|
12209
|
+
requester,
|
|
12210
|
+
...surface ? { surface } : {}
|
|
12754
12211
|
});
|
|
12755
12212
|
}
|
|
12756
12213
|
return buildTurnResult({
|
|
@@ -12785,7 +12242,8 @@ async function generateAssistantReply(messageText2, context = {}) {
|
|
|
12785
12242
|
errorMessage: error.message,
|
|
12786
12243
|
loadedSkillNames: loadedSkillNamesForResume,
|
|
12787
12244
|
logContext: sessionRecordLogContext,
|
|
12788
|
-
requester
|
|
12245
|
+
requester,
|
|
12246
|
+
...surface ? { surface } : {}
|
|
12789
12247
|
});
|
|
12790
12248
|
if (!sessionRecord) {
|
|
12791
12249
|
throw new Error(
|
|
@@ -12808,7 +12266,8 @@ async function generateAssistantReply(messageText2, context = {}) {
|
|
|
12808
12266
|
errorMessage: error instanceof Error ? error.message : String(error),
|
|
12809
12267
|
loadedSkillNames: loadedSkillNamesForResume,
|
|
12810
12268
|
logContext: sessionRecordLogContext,
|
|
12811
|
-
requester
|
|
12269
|
+
requester,
|
|
12270
|
+
...surface ? { surface } : {}
|
|
12812
12271
|
});
|
|
12813
12272
|
if (!sessionRecord) {
|
|
12814
12273
|
throw new Error(
|
|
@@ -12850,7 +12309,8 @@ async function generateAssistantReply(messageText2, context = {}) {
|
|
|
12850
12309
|
errorMessage: error.message,
|
|
12851
12310
|
loadedSkillNames: loadedSkillNamesForResume,
|
|
12852
12311
|
logContext: sessionRecordLogContext,
|
|
12853
|
-
requester
|
|
12312
|
+
requester,
|
|
12313
|
+
...surface ? { surface } : {}
|
|
12854
12314
|
});
|
|
12855
12315
|
if (sessionRecord) {
|
|
12856
12316
|
throw new RetryableTurnError(
|
|
@@ -12987,6 +12447,7 @@ var CONTEXT_MIN_LIVE_MESSAGES = 12;
|
|
|
12987
12447
|
var CONTEXT_COMPACTION_BATCH_SIZE = 24;
|
|
12988
12448
|
var CONTEXT_MAX_COMPACTIONS = 16;
|
|
12989
12449
|
var CONTEXT_MAX_MESSAGE_CHARS = 3200;
|
|
12450
|
+
var SLACK_USER_ID_DISPLAY_PATTERN = /^[UW][A-Z0-9]{5,}$/;
|
|
12990
12451
|
function generateConversationId(prefix) {
|
|
12991
12452
|
return `${prefix}_${Date.now()}_${Math.random().toString(36).slice(2, 10)}`;
|
|
12992
12453
|
}
|
|
@@ -13006,7 +12467,7 @@ function buildImageContextSuffix(message, conversation) {
|
|
|
13006
12467
|
return ` [image context: ${summaries.join(" | ")}]`;
|
|
13007
12468
|
}
|
|
13008
12469
|
function renderConversationMessageLine(message, conversation) {
|
|
13009
|
-
const displayName =
|
|
12470
|
+
const displayName = conversationAuthorDisplayName(message);
|
|
13010
12471
|
const markers = [];
|
|
13011
12472
|
if (message.meta?.replied === false) {
|
|
13012
12473
|
markers.push(
|
|
@@ -13020,6 +12481,18 @@ function renderConversationMessageLine(message, conversation) {
|
|
|
13020
12481
|
const imageContext = buildImageContextSuffix(message, conversation);
|
|
13021
12482
|
return `[${message.role}] ${displayName}: ${message.text}${imageContext}${markerSuffix}`;
|
|
13022
12483
|
}
|
|
12484
|
+
function conversationAuthorDisplayName(message) {
|
|
12485
|
+
const author = message.author;
|
|
12486
|
+
const fullName = authorDisplayField(author?.fullName, author?.userId);
|
|
12487
|
+
const userName = authorDisplayField(author?.userName, author?.userId);
|
|
12488
|
+
return fullName ?? userName ?? (message.role === "assistant" ? botConfig.userName : message.role);
|
|
12489
|
+
}
|
|
12490
|
+
function authorDisplayField(value, userId) {
|
|
12491
|
+
if (!value || value === userId || SLACK_USER_ID_DISPLAY_PATTERN.test(value)) {
|
|
12492
|
+
return void 0;
|
|
12493
|
+
}
|
|
12494
|
+
return value;
|
|
12495
|
+
}
|
|
13023
12496
|
function updateConversationStats(conversation) {
|
|
13024
12497
|
const contextText = buildConversationContext(conversation);
|
|
13025
12498
|
conversation.stats.estimatedContextTokens = estimateTextTokens(
|
|
@@ -13089,11 +12562,12 @@ function buildConversationContext(conversation, options = {}) {
|
|
|
13089
12562
|
}
|
|
13090
12563
|
lines.push("<thread-transcript>");
|
|
13091
12564
|
for (const [index, message] of messages.entries()) {
|
|
13092
|
-
const author = escapeXml(message
|
|
12565
|
+
const author = escapeXml(conversationAuthorDisplayName(message));
|
|
12566
|
+
const actorIdAttr = message.author?.userId ? ` actor_id="${escapeXml(message.author.userId)}"` : "";
|
|
13093
12567
|
const ts = new Date(message.createdAtMs).toISOString();
|
|
13094
12568
|
const slackTsAttr = message.meta?.slackTs ? ` slack_ts="${escapeXml(message.meta.slackTs)}"` : "";
|
|
13095
12569
|
lines.push(
|
|
13096
|
-
` <message index="${index + 1}" ts="${ts}" role="${message.role}" author="${author}"${slackTsAttr}>`,
|
|
12570
|
+
` <message index="${index + 1}" ts="${ts}" role="${message.role}" author="${author}"${actorIdAttr}${slackTsAttr}>`,
|
|
13097
12571
|
renderConversationMessageLine(message, conversation),
|
|
13098
12572
|
" </message>"
|
|
13099
12573
|
);
|
|
@@ -13630,7 +13104,7 @@ function finalizeFailedTurnReply(args) {
|
|
|
13630
13104
|
}
|
|
13631
13105
|
|
|
13632
13106
|
// src/chat/agent-dispatch/signing.ts
|
|
13633
|
-
import { createHmac as
|
|
13107
|
+
import { createHmac as createHmac2, timingSafeEqual as timingSafeEqual2 } from "crypto";
|
|
13634
13108
|
var DISPATCH_CALLBACK_PATH = "/api/internal/agent-dispatch";
|
|
13635
13109
|
var DISPATCH_HMAC_CONTEXT = "junior.agent_dispatch.v1";
|
|
13636
13110
|
var DISPATCH_SIGNATURE_VERSION = "v1";
|
|
@@ -13645,16 +13119,16 @@ function buildSignedPayload(timestamp, body) {
|
|
|
13645
13119
|
return `${DISPATCH_HMAC_CONTEXT}:${timestamp}:${body}`;
|
|
13646
13120
|
}
|
|
13647
13121
|
function signBody(secret, timestamp, body) {
|
|
13648
|
-
const digest =
|
|
13122
|
+
const digest = createHmac2("sha256", secret).update(buildSignedPayload(timestamp, body)).digest("hex");
|
|
13649
13123
|
return `${DISPATCH_SIGNATURE_VERSION}=${digest}`;
|
|
13650
13124
|
}
|
|
13651
|
-
function
|
|
13125
|
+
function timingSafeMatch2(expected, actual) {
|
|
13652
13126
|
const expectedBuffer = Buffer.from(expected);
|
|
13653
13127
|
const actualBuffer = Buffer.from(actual);
|
|
13654
13128
|
if (expectedBuffer.length !== actualBuffer.length) {
|
|
13655
13129
|
return false;
|
|
13656
13130
|
}
|
|
13657
|
-
return
|
|
13131
|
+
return timingSafeEqual2(expectedBuffer, actualBuffer);
|
|
13658
13132
|
}
|
|
13659
13133
|
function parseDispatchCallback(value) {
|
|
13660
13134
|
if (!value || typeof value !== "object") {
|
|
@@ -13713,7 +13187,7 @@ async function verifyDispatchCallbackRequest(request) {
|
|
|
13713
13187
|
}
|
|
13714
13188
|
const body = await request.text();
|
|
13715
13189
|
const expectedSignature = signBody(secret, timestamp, body);
|
|
13716
|
-
if (!
|
|
13190
|
+
if (!timingSafeMatch2(expectedSignature, signature)) {
|
|
13717
13191
|
return void 0;
|
|
13718
13192
|
}
|
|
13719
13193
|
try {
|
|
@@ -13724,7 +13198,7 @@ async function verifyDispatchCallbackRequest(request) {
|
|
|
13724
13198
|
}
|
|
13725
13199
|
|
|
13726
13200
|
// src/chat/agent-dispatch/store.ts
|
|
13727
|
-
import { createHash
|
|
13201
|
+
import { createHash } from "crypto";
|
|
13728
13202
|
var DISPATCH_PREFIX = "junior:agent_dispatch";
|
|
13729
13203
|
var DISPATCH_LOCK_TTL_MS = 10 * 60 * 1e3;
|
|
13730
13204
|
var DISPATCH_INDEX_LOCK_TTL_MS = 1e4;
|
|
@@ -13752,7 +13226,7 @@ function normalizeMetadata(metadata) {
|
|
|
13752
13226
|
return entries.length > 0 ? Object.fromEntries(entries) : void 0;
|
|
13753
13227
|
}
|
|
13754
13228
|
function buildDispatchId(plugin, idempotencyKey) {
|
|
13755
|
-
const digest =
|
|
13229
|
+
const digest = createHash("sha256").update(plugin).update("\0").update(idempotencyKey).digest("hex").slice(0, 32);
|
|
13756
13230
|
return `dispatch_${digest}`;
|
|
13757
13231
|
}
|
|
13758
13232
|
function getDispatchDestinationLockId(destination) {
|
|
@@ -14059,6 +13533,7 @@ async function runAgentDispatchSlice(callback, deps = {}) {
|
|
|
14059
13533
|
channelId: dispatch.destination.channelId,
|
|
14060
13534
|
teamId: dispatch.destination.teamId
|
|
14061
13535
|
},
|
|
13536
|
+
surface: dispatch.actor.id === "scheduler" ? "scheduler" : "api",
|
|
14062
13537
|
toolChannelId: dispatch.destination.channelId,
|
|
14063
13538
|
sandbox: {
|
|
14064
13539
|
sandboxId,
|
|
@@ -14234,10 +13709,10 @@ async function POST(request, waitUntil) {
|
|
|
14234
13709
|
}
|
|
14235
13710
|
|
|
14236
13711
|
// src/handlers/heartbeat.ts
|
|
14237
|
-
import { timingSafeEqual as
|
|
13712
|
+
import { timingSafeEqual as timingSafeEqual4 } from "crypto";
|
|
14238
13713
|
|
|
14239
13714
|
// src/chat/services/timeout-resume.ts
|
|
14240
|
-
import { createHmac as
|
|
13715
|
+
import { createHmac as createHmac3, timingSafeEqual as timingSafeEqual3 } from "crypto";
|
|
14241
13716
|
|
|
14242
13717
|
// src/chat/task-execution/store.ts
|
|
14243
13718
|
import { randomUUID as randomUUID4 } from "crypto";
|
|
@@ -14836,16 +14311,16 @@ function buildSignedPayload2(timestamp, body) {
|
|
|
14836
14311
|
return `${TURN_TIMEOUT_RESUME_HMAC_CONTEXT}:${timestamp}:${body}`;
|
|
14837
14312
|
}
|
|
14838
14313
|
function signTurnTimeoutResumeBody(secret, timestamp, body) {
|
|
14839
|
-
const digest =
|
|
14314
|
+
const digest = createHmac3("sha256", secret).update(buildSignedPayload2(timestamp, body)).digest("hex");
|
|
14840
14315
|
return `${TURN_TIMEOUT_RESUME_SIGNATURE_VERSION}=${digest}`;
|
|
14841
14316
|
}
|
|
14842
|
-
function
|
|
14317
|
+
function timingSafeMatch3(expected, actual) {
|
|
14843
14318
|
const expectedBuffer = Buffer.from(expected);
|
|
14844
14319
|
const actualBuffer = Buffer.from(actual);
|
|
14845
14320
|
if (expectedBuffer.length !== actualBuffer.length) {
|
|
14846
14321
|
return false;
|
|
14847
14322
|
}
|
|
14848
|
-
return
|
|
14323
|
+
return timingSafeEqual3(expectedBuffer, actualBuffer);
|
|
14849
14324
|
}
|
|
14850
14325
|
function parseTurnTimeoutResumeRequest(value) {
|
|
14851
14326
|
if (!value || typeof value !== "object") {
|
|
@@ -14900,7 +14375,7 @@ async function verifyTurnTimeoutResumeRequest(request) {
|
|
|
14900
14375
|
}
|
|
14901
14376
|
const body = await request.text();
|
|
14902
14377
|
const expectedSignature = signTurnTimeoutResumeBody(secret, timestamp, body);
|
|
14903
|
-
if (!
|
|
14378
|
+
if (!timingSafeMatch3(expectedSignature, signature)) {
|
|
14904
14379
|
return void 0;
|
|
14905
14380
|
}
|
|
14906
14381
|
try {
|
|
@@ -15052,7 +14527,7 @@ function validateDispatchOptions(options) {
|
|
|
15052
14527
|
if (options.credentialSubject.type !== "user") {
|
|
15053
14528
|
throw new Error("Dispatch credentialSubject type must be user");
|
|
15054
14529
|
}
|
|
15055
|
-
if (!options.credentialSubject.userId
|
|
14530
|
+
if (!isActorUserId(options.credentialSubject.userId)) {
|
|
15056
14531
|
throw new Error("Dispatch credentialSubject userId is required");
|
|
15057
14532
|
}
|
|
15058
14533
|
if (options.credentialSubject.allowedWhen !== "private-direct-conversation") {
|
|
@@ -15402,7 +14877,7 @@ function verifyHeartbeatRequest(request) {
|
|
|
15402
14877
|
}
|
|
15403
14878
|
const actual = Buffer.from(authorization.slice("Bearer ".length));
|
|
15404
14879
|
const expected = Buffer.from(secret);
|
|
15405
|
-
return actual.length === expected.length &&
|
|
14880
|
+
return actual.length === expected.length && timingSafeEqual4(actual, expected);
|
|
15406
14881
|
}
|
|
15407
14882
|
async function GET2(request, waitUntil, options = {}) {
|
|
15408
14883
|
if (!verifyHeartbeatRequest(request)) {
|
|
@@ -16005,15 +15480,13 @@ function getTeamId(message) {
|
|
|
16005
15480
|
}
|
|
16006
15481
|
|
|
16007
15482
|
// src/chat/runtime/processing-reaction.ts
|
|
16008
|
-
var PROCESSING_REACTION_EMOJI = "eyes";
|
|
16009
|
-
var COMPLETED_REACTION_EMOJI = "white_check_mark";
|
|
16010
15483
|
var noProcessingReaction = {
|
|
16011
15484
|
complete: async () => void 0,
|
|
16012
15485
|
keep: () => void 0,
|
|
16013
15486
|
stop: async () => void 0
|
|
16014
15487
|
};
|
|
16015
15488
|
function isProcessingReactionEmoji(value) {
|
|
16016
|
-
return typeof value === "string" && normalizeSlackEmojiName(value) ===
|
|
15489
|
+
return typeof value === "string" && normalizeSlackEmojiName(value) === botConfig.processingReactionEmoji;
|
|
16017
15490
|
}
|
|
16018
15491
|
function shouldKeepProcessingReactionForToolInvocation(input) {
|
|
16019
15492
|
return input.toolName === "slackMessageAddReaction" && isProcessingReactionEmoji(input.params.emoji);
|
|
@@ -16039,7 +15512,7 @@ async function startSlackProcessingReactionForMessage(args) {
|
|
|
16039
15512
|
await addReactionToMessage({
|
|
16040
15513
|
channelId: args.channelId,
|
|
16041
15514
|
timestamp: args.timestamp,
|
|
16042
|
-
emoji:
|
|
15515
|
+
emoji: botConfig.processingReactionEmoji
|
|
16043
15516
|
});
|
|
16044
15517
|
} catch (error) {
|
|
16045
15518
|
args.logException(
|
|
@@ -16064,7 +15537,7 @@ async function startSlackProcessingReactionForMessage(args) {
|
|
|
16064
15537
|
await removeReactionFromMessage({
|
|
16065
15538
|
channelId: args.channelId,
|
|
16066
15539
|
timestamp: args.timestamp,
|
|
16067
|
-
emoji:
|
|
15540
|
+
emoji: botConfig.processingReactionEmoji
|
|
16068
15541
|
});
|
|
16069
15542
|
return true;
|
|
16070
15543
|
} catch (error) {
|
|
@@ -16091,7 +15564,7 @@ async function startSlackProcessingReactionForMessage(args) {
|
|
|
16091
15564
|
await addReactionToMessage({
|
|
16092
15565
|
channelId: args.channelId,
|
|
16093
15566
|
timestamp: args.timestamp,
|
|
16094
|
-
emoji:
|
|
15567
|
+
emoji: botConfig.completedReactionEmoji
|
|
16095
15568
|
});
|
|
16096
15569
|
} catch (error) {
|
|
16097
15570
|
args.logException(
|
|
@@ -16538,6 +16011,87 @@ function htmlCallbackResponse(title, message, status) {
|
|
|
16538
16011
|
});
|
|
16539
16012
|
}
|
|
16540
16013
|
|
|
16014
|
+
// src/chat/slack/user.ts
|
|
16015
|
+
var USER_CACHE_TTL_MS = 5 * 60 * 1e3;
|
|
16016
|
+
var userCache = /* @__PURE__ */ new Map();
|
|
16017
|
+
function readFromCache(userId) {
|
|
16018
|
+
const hit = userCache.get(userId);
|
|
16019
|
+
if (!hit) return null;
|
|
16020
|
+
if (hit.expiresAt < Date.now()) {
|
|
16021
|
+
userCache.delete(userId);
|
|
16022
|
+
return null;
|
|
16023
|
+
}
|
|
16024
|
+
return hit.value;
|
|
16025
|
+
}
|
|
16026
|
+
function writeToCache(userId, value) {
|
|
16027
|
+
userCache.set(userId, {
|
|
16028
|
+
value,
|
|
16029
|
+
expiresAt: Date.now() + USER_CACHE_TTL_MS
|
|
16030
|
+
});
|
|
16031
|
+
}
|
|
16032
|
+
async function lookupSlackUser(userId) {
|
|
16033
|
+
if (!userId) {
|
|
16034
|
+
return null;
|
|
16035
|
+
}
|
|
16036
|
+
const cached = readFromCache(userId);
|
|
16037
|
+
if (cached) {
|
|
16038
|
+
return cached;
|
|
16039
|
+
}
|
|
16040
|
+
const token = getSlackBotToken();
|
|
16041
|
+
if (!token) {
|
|
16042
|
+
return null;
|
|
16043
|
+
}
|
|
16044
|
+
try {
|
|
16045
|
+
const response = await fetch(
|
|
16046
|
+
`https://slack.com/api/users.info?user=${encodeURIComponent(userId)}`,
|
|
16047
|
+
{
|
|
16048
|
+
headers: {
|
|
16049
|
+
authorization: `Bearer ${token}`
|
|
16050
|
+
}
|
|
16051
|
+
}
|
|
16052
|
+
);
|
|
16053
|
+
if (!response.ok) {
|
|
16054
|
+
logWarn(
|
|
16055
|
+
"slack_user_lookup_failed",
|
|
16056
|
+
{},
|
|
16057
|
+
{
|
|
16058
|
+
"enduser.id": userId,
|
|
16059
|
+
"http.response.status_code": response.status
|
|
16060
|
+
},
|
|
16061
|
+
"Slack user lookup request failed"
|
|
16062
|
+
);
|
|
16063
|
+
return null;
|
|
16064
|
+
}
|
|
16065
|
+
const payload = await response.json();
|
|
16066
|
+
if (!payload.ok || !payload.user) {
|
|
16067
|
+
return null;
|
|
16068
|
+
}
|
|
16069
|
+
const userName = payload.user.name?.trim() || void 0;
|
|
16070
|
+
const fullName = payload.user.profile?.display_name?.trim() || payload.user.profile?.real_name?.trim() || payload.user.real_name?.trim() || void 0;
|
|
16071
|
+
const result = {
|
|
16072
|
+
userName,
|
|
16073
|
+
fullName,
|
|
16074
|
+
email: payload.user.profile?.email?.trim() || void 0
|
|
16075
|
+
};
|
|
16076
|
+
writeToCache(userId, result);
|
|
16077
|
+
return result;
|
|
16078
|
+
} catch (error) {
|
|
16079
|
+
logWarn(
|
|
16080
|
+
"slack_user_lookup_failed",
|
|
16081
|
+
{},
|
|
16082
|
+
{
|
|
16083
|
+
"enduser.id": userId,
|
|
16084
|
+
"exception.message": error instanceof Error ? error.message : String(error)
|
|
16085
|
+
},
|
|
16086
|
+
"Slack user lookup failed with exception"
|
|
16087
|
+
);
|
|
16088
|
+
return null;
|
|
16089
|
+
}
|
|
16090
|
+
}
|
|
16091
|
+
async function lookupSlackActorIdentity(userId) {
|
|
16092
|
+
return slackActorIdentity(userId, await lookupSlackUser(userId));
|
|
16093
|
+
}
|
|
16094
|
+
|
|
16541
16095
|
// src/handlers/mcp-oauth-callback.ts
|
|
16542
16096
|
var CALLBACK_PAGES = {
|
|
16543
16097
|
missing_state: {
|
|
@@ -16738,6 +16292,7 @@ async function resumeAuthorizedMcpTurn(args) {
|
|
|
16738
16292
|
}),
|
|
16739
16293
|
ttlMs: THREAD_STATE_TTL_MS4
|
|
16740
16294
|
});
|
|
16295
|
+
const requester = await lookupSlackActorIdentity(authSession.userId);
|
|
16741
16296
|
return {
|
|
16742
16297
|
messageText: lockedUserMessage.text,
|
|
16743
16298
|
messageTs: getTurnUserSlackMessageTs(lockedUserMessage),
|
|
@@ -16745,11 +16300,7 @@ async function resumeAuthorizedMcpTurn(args) {
|
|
|
16745
16300
|
credentialContext: {
|
|
16746
16301
|
actor: { type: "user", userId: authSession.userId }
|
|
16747
16302
|
},
|
|
16748
|
-
requester
|
|
16749
|
-
userId: authSession.userId,
|
|
16750
|
-
userName: lockedUserMessage.author?.userName,
|
|
16751
|
-
fullName: lockedUserMessage.author?.fullName
|
|
16752
|
-
},
|
|
16303
|
+
requester,
|
|
16753
16304
|
correlation: {
|
|
16754
16305
|
conversationId: authSession.conversationId,
|
|
16755
16306
|
turnId: lockedSessionId,
|
|
@@ -17232,6 +16783,9 @@ async function resumeOAuthSessionRecordTurn(stored) {
|
|
|
17232
16783
|
}),
|
|
17233
16784
|
ttlMs: THREAD_STATE_TTL_MS5
|
|
17234
16785
|
});
|
|
16786
|
+
const requester = await lookupSlackActorIdentity(
|
|
16787
|
+
lockedUserMessage.author.userId
|
|
16788
|
+
);
|
|
17235
16789
|
return {
|
|
17236
16790
|
messageText: stored.pendingMessage ?? lockedUserMessage.text,
|
|
17237
16791
|
messageTs: getTurnUserSlackMessageTs(lockedUserMessage),
|
|
@@ -17242,11 +16796,7 @@ async function resumeOAuthSessionRecordTurn(stored) {
|
|
|
17242
16796
|
userId: lockedUserMessage.author.userId
|
|
17243
16797
|
}
|
|
17244
16798
|
},
|
|
17245
|
-
requester
|
|
17246
|
-
userId: lockedUserMessage.author.userId,
|
|
17247
|
-
userName: lockedUserMessage.author.userName,
|
|
17248
|
-
fullName: lockedUserMessage.author.fullName
|
|
17249
|
-
},
|
|
16799
|
+
requester,
|
|
17250
16800
|
correlation: {
|
|
17251
16801
|
conversationId: stored.resumeConversationId,
|
|
17252
16802
|
turnId: lockedSessionId,
|
|
@@ -17342,6 +16892,7 @@ async function resumePendingOAuthMessage(stored) {
|
|
|
17342
16892
|
const conversationContext = buildConversationContext(conversation, {
|
|
17343
16893
|
excludeMessageId: latestUserMessage?.id
|
|
17344
16894
|
});
|
|
16895
|
+
const requester = await lookupSlackActorIdentity(stored.userId);
|
|
17345
16896
|
await resumeAuthorizedRequest({
|
|
17346
16897
|
messageText: stored.pendingMessage,
|
|
17347
16898
|
channelId: stored.channelId,
|
|
@@ -17352,7 +16903,7 @@ async function resumePendingOAuthMessage(stored) {
|
|
|
17352
16903
|
credentialContext: {
|
|
17353
16904
|
actor: { type: "user", userId: stored.userId }
|
|
17354
16905
|
},
|
|
17355
|
-
requester
|
|
16906
|
+
requester,
|
|
17356
16907
|
conversationContext,
|
|
17357
16908
|
piMessages: conversation.piMessages,
|
|
17358
16909
|
configuration: stored.configuration
|
|
@@ -18231,6 +17782,9 @@ async function resumeTimedOutTurn(payload, options = {}) {
|
|
|
18231
17782
|
excludeMessageId: userMessage2.id
|
|
18232
17783
|
});
|
|
18233
17784
|
const sandbox = getPersistedSandboxState(currentState);
|
|
17785
|
+
const requester = await lookupSlackActorIdentity(
|
|
17786
|
+
userMessage2.author.userId
|
|
17787
|
+
);
|
|
18234
17788
|
return {
|
|
18235
17789
|
messageText: userMessage2.text,
|
|
18236
17790
|
messageTs: getTurnUserSlackMessageTs(userMessage2),
|
|
@@ -18241,11 +17795,7 @@ async function resumeTimedOutTurn(payload, options = {}) {
|
|
|
18241
17795
|
userId: userMessage2.author.userId
|
|
18242
17796
|
}
|
|
18243
17797
|
},
|
|
18244
|
-
requester
|
|
18245
|
-
userId: userMessage2.author.userId,
|
|
18246
|
-
userName: userMessage2.author.userName,
|
|
18247
|
-
fullName: userMessage2.author.fullName
|
|
18248
|
-
},
|
|
17798
|
+
requester,
|
|
18249
17799
|
correlation: {
|
|
18250
17800
|
conversationId: payload.conversationId,
|
|
18251
17801
|
turnId: payload.sessionId,
|
|
@@ -18735,6 +18285,60 @@ function combineTurnText(queuedMessages, latestText) {
|
|
|
18735
18285
|
};
|
|
18736
18286
|
}
|
|
18737
18287
|
|
|
18288
|
+
// src/chat/services/message-actor-identity.ts
|
|
18289
|
+
var messageActors = /* @__PURE__ */ new WeakMap();
|
|
18290
|
+
function canonicalUserId(author, identity) {
|
|
18291
|
+
const authorUserId = parseActorUserId(author.userId);
|
|
18292
|
+
const identityUserId = parseActorUserId(identity.userId);
|
|
18293
|
+
if (authorUserId && identityUserId && authorUserId !== identityUserId) {
|
|
18294
|
+
throw new Error("Message actor identity user id mismatch");
|
|
18295
|
+
}
|
|
18296
|
+
const userId = authorUserId ?? identityUserId;
|
|
18297
|
+
if (!userId) {
|
|
18298
|
+
throw new Error("Message actor identity requires a user id");
|
|
18299
|
+
}
|
|
18300
|
+
return userId;
|
|
18301
|
+
}
|
|
18302
|
+
function actorIdentityFromAuthor(author) {
|
|
18303
|
+
const userId = parseActorUserId(author.userId);
|
|
18304
|
+
return userId ? { userId } : void 0;
|
|
18305
|
+
}
|
|
18306
|
+
function applyIdentityToAuthor(author, identity) {
|
|
18307
|
+
if (!isActorUserId(identity.userId)) {
|
|
18308
|
+
throw new Error("Message actor identity requires a user id");
|
|
18309
|
+
}
|
|
18310
|
+
author.userId = identity.userId;
|
|
18311
|
+
author.userName = identity.userName ?? "";
|
|
18312
|
+
author.fullName = identity.fullName ?? "";
|
|
18313
|
+
}
|
|
18314
|
+
function bindMessageActorIdentity(message, identity) {
|
|
18315
|
+
const userId = canonicalUserId(message.author, identity);
|
|
18316
|
+
const actorIdentity = buildActorIdentity(identity, userId);
|
|
18317
|
+
if (!actorIdentity?.userId) {
|
|
18318
|
+
throw new Error("Message actor identity requires a user id");
|
|
18319
|
+
}
|
|
18320
|
+
messageActors.set(message, actorIdentity);
|
|
18321
|
+
applyIdentityToAuthor(message.author, actorIdentity);
|
|
18322
|
+
return actorIdentity;
|
|
18323
|
+
}
|
|
18324
|
+
function getMessageActorIdentity(message) {
|
|
18325
|
+
return messageActors.get(message) ?? actorIdentityFromAuthor(message.author);
|
|
18326
|
+
}
|
|
18327
|
+
async function ensureSlackMessageActorIdentity(message, lookupSlackUser2) {
|
|
18328
|
+
const existing = messageActors.get(message);
|
|
18329
|
+
if (existing) {
|
|
18330
|
+
return existing;
|
|
18331
|
+
}
|
|
18332
|
+
const userId = parseActorUserId(message.author.userId);
|
|
18333
|
+
if (!userId) {
|
|
18334
|
+
throw new Error("Slack message actor identity requires a user id");
|
|
18335
|
+
}
|
|
18336
|
+
return bindMessageActorIdentity(
|
|
18337
|
+
message,
|
|
18338
|
+
slackActorIdentity(userId, await lookupSlackUser2(userId))
|
|
18339
|
+
);
|
|
18340
|
+
}
|
|
18341
|
+
|
|
18738
18342
|
// src/chat/runtime/slack-runtime.ts
|
|
18739
18343
|
var THREAD_OPTOUT_ACK = "Understood. I'll stay out of this thread unless someone @mentions me again.";
|
|
18740
18344
|
async function maybeHandleThreadOptOutDecision(args) {
|
|
@@ -18794,6 +18398,9 @@ function buildLogContext(deps, args) {
|
|
|
18794
18398
|
modelId: deps.modelId
|
|
18795
18399
|
};
|
|
18796
18400
|
}
|
|
18401
|
+
function requesterUserName(message) {
|
|
18402
|
+
return getMessageActorIdentity(message)?.userName;
|
|
18403
|
+
}
|
|
18797
18404
|
function createSlackTurnRuntime(deps) {
|
|
18798
18405
|
const logContext = (args) => buildLogContext(deps, args);
|
|
18799
18406
|
const createToolInvocationHook = (processingReaction, hooks) => {
|
|
@@ -18867,7 +18474,7 @@ function createSlackTurnRuntime(deps) {
|
|
|
18867
18474
|
logContext({
|
|
18868
18475
|
threadId: args.context.threadId,
|
|
18869
18476
|
requesterId: args.context.requesterId,
|
|
18870
|
-
requesterUserName: args.message
|
|
18477
|
+
requesterUserName: requesterUserName(args.message),
|
|
18871
18478
|
channelId: args.context.channelId,
|
|
18872
18479
|
runId: args.context.runId
|
|
18873
18480
|
}),
|
|
@@ -18909,7 +18516,7 @@ function createSlackTurnRuntime(deps) {
|
|
|
18909
18516
|
threadId,
|
|
18910
18517
|
channelId,
|
|
18911
18518
|
requesterId: message.author.userId,
|
|
18912
|
-
requesterUserName: message
|
|
18519
|
+
requesterUserName: requesterUserName(message),
|
|
18913
18520
|
runId
|
|
18914
18521
|
});
|
|
18915
18522
|
processingReaction = await processingReactions.start(context, message);
|
|
@@ -18969,7 +18576,7 @@ function createSlackTurnRuntime(deps) {
|
|
|
18969
18576
|
const errorContext = logContext({
|
|
18970
18577
|
threadId: deps.getThreadId(thread, message),
|
|
18971
18578
|
requesterId: message.author.userId,
|
|
18972
|
-
requesterUserName: message
|
|
18579
|
+
requesterUserName: requesterUserName(message),
|
|
18973
18580
|
channelId: deps.getChannelId(thread, message),
|
|
18974
18581
|
runId: deps.getRunId(thread, message)
|
|
18975
18582
|
});
|
|
@@ -19025,7 +18632,7 @@ function createSlackTurnRuntime(deps) {
|
|
|
19025
18632
|
const turnContext = logContext({
|
|
19026
18633
|
threadId,
|
|
19027
18634
|
requesterId: message.author.userId,
|
|
19028
|
-
requesterUserName: message
|
|
18635
|
+
requesterUserName: requesterUserName(message),
|
|
19029
18636
|
channelId,
|
|
19030
18637
|
runId
|
|
19031
18638
|
});
|
|
@@ -19175,7 +18782,7 @@ function createSlackTurnRuntime(deps) {
|
|
|
19175
18782
|
const errorContext = logContext({
|
|
19176
18783
|
threadId: deps.getThreadId(thread, message),
|
|
19177
18784
|
requesterId: message.author.userId,
|
|
19178
|
-
requesterUserName: message
|
|
18785
|
+
requesterUserName: requesterUserName(message),
|
|
19179
18786
|
channelId: deps.getChannelId(thread, message),
|
|
19180
18787
|
runId: deps.getRunId(thread, message)
|
|
19181
18788
|
});
|
|
@@ -19533,84 +19140,6 @@ function createContextCompactor(deps) {
|
|
|
19533
19140
|
};
|
|
19534
19141
|
}
|
|
19535
19142
|
|
|
19536
|
-
// src/chat/slack/user.ts
|
|
19537
|
-
var USER_CACHE_TTL_MS = 5 * 60 * 1e3;
|
|
19538
|
-
var userCache = /* @__PURE__ */ new Map();
|
|
19539
|
-
function readFromCache(userId) {
|
|
19540
|
-
const hit = userCache.get(userId);
|
|
19541
|
-
if (!hit) return null;
|
|
19542
|
-
if (hit.expiresAt < Date.now()) {
|
|
19543
|
-
userCache.delete(userId);
|
|
19544
|
-
return null;
|
|
19545
|
-
}
|
|
19546
|
-
return hit.value;
|
|
19547
|
-
}
|
|
19548
|
-
function writeToCache(userId, value) {
|
|
19549
|
-
userCache.set(userId, {
|
|
19550
|
-
value,
|
|
19551
|
-
expiresAt: Date.now() + USER_CACHE_TTL_MS
|
|
19552
|
-
});
|
|
19553
|
-
}
|
|
19554
|
-
async function lookupSlackUser(userId) {
|
|
19555
|
-
if (!userId) {
|
|
19556
|
-
return null;
|
|
19557
|
-
}
|
|
19558
|
-
const cached = readFromCache(userId);
|
|
19559
|
-
if (cached) {
|
|
19560
|
-
return cached;
|
|
19561
|
-
}
|
|
19562
|
-
const token = getSlackBotToken();
|
|
19563
|
-
if (!token) {
|
|
19564
|
-
return null;
|
|
19565
|
-
}
|
|
19566
|
-
try {
|
|
19567
|
-
const response = await fetch(
|
|
19568
|
-
`https://slack.com/api/users.info?user=${encodeURIComponent(userId)}`,
|
|
19569
|
-
{
|
|
19570
|
-
headers: {
|
|
19571
|
-
authorization: `Bearer ${token}`
|
|
19572
|
-
}
|
|
19573
|
-
}
|
|
19574
|
-
);
|
|
19575
|
-
if (!response.ok) {
|
|
19576
|
-
logWarn(
|
|
19577
|
-
"slack_user_lookup_failed",
|
|
19578
|
-
{},
|
|
19579
|
-
{
|
|
19580
|
-
"enduser.id": userId,
|
|
19581
|
-
"http.response.status_code": response.status
|
|
19582
|
-
},
|
|
19583
|
-
"Slack user lookup request failed"
|
|
19584
|
-
);
|
|
19585
|
-
return null;
|
|
19586
|
-
}
|
|
19587
|
-
const payload = await response.json();
|
|
19588
|
-
if (!payload.ok || !payload.user) {
|
|
19589
|
-
return null;
|
|
19590
|
-
}
|
|
19591
|
-
const userName = payload.user.name?.trim() || void 0;
|
|
19592
|
-
const fullName = payload.user.profile?.display_name?.trim() || payload.user.profile?.real_name?.trim() || payload.user.real_name?.trim() || void 0;
|
|
19593
|
-
const result = {
|
|
19594
|
-
userName,
|
|
19595
|
-
fullName,
|
|
19596
|
-
email: payload.user.profile?.email?.trim() || void 0
|
|
19597
|
-
};
|
|
19598
|
-
writeToCache(userId, result);
|
|
19599
|
-
return result;
|
|
19600
|
-
} catch (error) {
|
|
19601
|
-
logWarn(
|
|
19602
|
-
"slack_user_lookup_failed",
|
|
19603
|
-
{},
|
|
19604
|
-
{
|
|
19605
|
-
"enduser.id": userId,
|
|
19606
|
-
"exception.message": error instanceof Error ? error.message : String(error)
|
|
19607
|
-
},
|
|
19608
|
-
"Slack user lookup failed with exception"
|
|
19609
|
-
);
|
|
19610
|
-
return null;
|
|
19611
|
-
}
|
|
19612
|
-
}
|
|
19613
|
-
|
|
19614
19143
|
// src/chat/services/subscribed-reply-policy.ts
|
|
19615
19144
|
function createSubscribedReplyPolicy(deps) {
|
|
19616
19145
|
return async (args) => {
|
|
@@ -20334,14 +19863,14 @@ function collectCanvasUrls(artifacts) {
|
|
|
20334
19863
|
].filter((url) => typeof url === "string" && url !== "")
|
|
20335
19864
|
);
|
|
20336
19865
|
}
|
|
20337
|
-
function turnRequester(
|
|
19866
|
+
function turnRequester(identity) {
|
|
20338
19867
|
const requester = {
|
|
20339
|
-
...
|
|
20340
|
-
...
|
|
20341
|
-
...
|
|
20342
|
-
...
|
|
19868
|
+
...identity.email ? { email: identity.email } : {},
|
|
19869
|
+
...identity.fullName ? { fullName: identity.fullName } : {},
|
|
19870
|
+
...identity.userId ? { slackUserId: identity.userId } : {},
|
|
19871
|
+
...identity.userName ? { slackUserName: identity.userName } : {}
|
|
20343
19872
|
};
|
|
20344
|
-
return
|
|
19873
|
+
return requester;
|
|
20345
19874
|
}
|
|
20346
19875
|
async function resolveChannelName(thread) {
|
|
20347
19876
|
const existingName = thread.channel.name?.trim();
|
|
@@ -20450,6 +19979,19 @@ function createReplyToThread(deps) {
|
|
|
20450
19979
|
options.queuedMessages ?? [],
|
|
20451
19980
|
currentText
|
|
20452
19981
|
).userText;
|
|
19982
|
+
await Promise.all(
|
|
19983
|
+
(options.queuedMessages ?? []).map(
|
|
19984
|
+
(queued) => ensureSlackMessageActorIdentity(
|
|
19985
|
+
queued.message,
|
|
19986
|
+
deps.services.lookupSlackUser
|
|
19987
|
+
)
|
|
19988
|
+
)
|
|
19989
|
+
);
|
|
19990
|
+
const requesterIdentity = await ensureSlackMessageActorIdentity(
|
|
19991
|
+
message,
|
|
19992
|
+
deps.services.lookupSlackUser
|
|
19993
|
+
);
|
|
19994
|
+
const requester = turnRequester(requesterIdentity);
|
|
20453
19995
|
const preparedState = options.preparedState ?? await deps.prepareTurnState({
|
|
20454
19996
|
thread,
|
|
20455
19997
|
message,
|
|
@@ -20467,15 +20009,6 @@ function createReplyToThread(deps) {
|
|
|
20467
20009
|
});
|
|
20468
20010
|
const slackMessageTs = getSlackMessageTs(message);
|
|
20469
20011
|
const turnId = buildDeterministicTurnId(message.id);
|
|
20470
|
-
const fallbackIdentity = await deps.services.lookupSlackUser(
|
|
20471
|
-
message.author.userId
|
|
20472
|
-
);
|
|
20473
|
-
const requester = turnRequester({
|
|
20474
|
-
email: fallbackIdentity?.email,
|
|
20475
|
-
fullName: message.author.fullName ?? fallbackIdentity?.fullName,
|
|
20476
|
-
userId: message.author.userId,
|
|
20477
|
-
userName: message.author.userName ?? fallbackIdentity?.userName
|
|
20478
|
-
});
|
|
20479
20012
|
const turnTraceContext = {
|
|
20480
20013
|
conversationId,
|
|
20481
20014
|
slackThreadId: threadId,
|
|
@@ -20630,6 +20163,7 @@ function createReplyToThread(deps) {
|
|
|
20630
20163
|
sliceId: 1,
|
|
20631
20164
|
startedAtMs: message.metadata.dateSent.getTime(),
|
|
20632
20165
|
state: "running",
|
|
20166
|
+
surface: "slack",
|
|
20633
20167
|
requester,
|
|
20634
20168
|
traceId: getActiveTraceId()
|
|
20635
20169
|
}).catch((error) => {
|
|
@@ -20662,16 +20196,15 @@ function createReplyToThread(deps) {
|
|
|
20662
20196
|
conversation: preparedState.conversation
|
|
20663
20197
|
});
|
|
20664
20198
|
await options.onTurnStatePersisted?.();
|
|
20665
|
-
const resolvedUserName = message.author.userName ?? fallbackIdentity?.userName;
|
|
20666
20199
|
if (message.author.userId) {
|
|
20667
20200
|
setSentryUser({
|
|
20668
20201
|
id: message.author.userId,
|
|
20669
|
-
...
|
|
20670
|
-
...
|
|
20202
|
+
...requesterIdentity.userName ? { username: requesterIdentity.userName } : {},
|
|
20203
|
+
...requesterIdentity.email ? { email: requesterIdentity.email } : {}
|
|
20671
20204
|
});
|
|
20672
20205
|
}
|
|
20673
|
-
if (
|
|
20674
|
-
setTags({ slackUserName:
|
|
20206
|
+
if (requesterIdentity.userName) {
|
|
20207
|
+
setTags({ slackUserName: requesterIdentity.userName });
|
|
20675
20208
|
}
|
|
20676
20209
|
const turnAttachments = collectTurnAttachments(
|
|
20677
20210
|
message,
|
|
@@ -20801,12 +20334,7 @@ function createReplyToThread(deps) {
|
|
|
20801
20334
|
credentialContext: {
|
|
20802
20335
|
actor: { type: "user", userId: message.author.userId }
|
|
20803
20336
|
},
|
|
20804
|
-
requester:
|
|
20805
|
-
userId: message.author.userId,
|
|
20806
|
-
userName: message.author.userName ?? fallbackIdentity?.userName,
|
|
20807
|
-
fullName: message.author.fullName ?? fallbackIdentity?.fullName,
|
|
20808
|
-
email: fallbackIdentity?.email
|
|
20809
|
-
},
|
|
20337
|
+
requester: requesterIdentity,
|
|
20810
20338
|
conversationContext: preparedState.conversationContext,
|
|
20811
20339
|
artifactState: preparedState.artifacts,
|
|
20812
20340
|
piMessages,
|
|
@@ -20817,6 +20345,7 @@ function createReplyToThread(deps) {
|
|
|
20817
20345
|
omittedImageAttachmentCount,
|
|
20818
20346
|
userAttachments,
|
|
20819
20347
|
slackConversation,
|
|
20348
|
+
surface: "slack",
|
|
20820
20349
|
turnDeadlineAtMs: getTurnRequestDeadline()?.deadlineAtMs,
|
|
20821
20350
|
correlation: {
|
|
20822
20351
|
conversationId,
|
|
@@ -21212,6 +20741,7 @@ function resolveMessageText(args) {
|
|
|
21212
20741
|
return text || NON_TEXT_MESSAGE_TEXT;
|
|
21213
20742
|
}
|
|
21214
20743
|
function toConversationMessage(args) {
|
|
20744
|
+
const actor = getMessageActorIdentity(args.entry);
|
|
21215
20745
|
const messageHasPotentialImageAttachment = hasPotentialImageAttachment(
|
|
21216
20746
|
args.entry.attachments
|
|
21217
20747
|
);
|
|
@@ -21222,9 +20752,9 @@ function toConversationMessage(args) {
|
|
|
21222
20752
|
text: resolveMessageText(args),
|
|
21223
20753
|
createdAtMs: args.entry.metadata.dateSent.getTime(),
|
|
21224
20754
|
author: {
|
|
21225
|
-
userId:
|
|
21226
|
-
userName:
|
|
21227
|
-
fullName:
|
|
20755
|
+
...actor?.userId ? { userId: actor.userId } : {},
|
|
20756
|
+
...actor?.userName ? { userName: actor.userName } : {},
|
|
20757
|
+
...actor?.fullName ? { fullName: actor.fullName } : {},
|
|
21228
20758
|
isBot: typeof args.entry.author.isBot === "boolean" ? args.entry.author.isBot : void 0
|
|
21229
20759
|
},
|
|
21230
20760
|
meta: {
|
|
@@ -21641,6 +21171,13 @@ async function runWithSlackInstallation(args) {
|
|
|
21641
21171
|
}
|
|
21642
21172
|
|
|
21643
21173
|
// src/chat/task-execution/slack-work.ts
|
|
21174
|
+
function requireSlackAuthorId(message) {
|
|
21175
|
+
const authorId = parseActorUserId(message.author.userId);
|
|
21176
|
+
if (!authorId) {
|
|
21177
|
+
throw new Error("Slack message requires an actor user id");
|
|
21178
|
+
}
|
|
21179
|
+
return authorId;
|
|
21180
|
+
}
|
|
21644
21181
|
function getConnectedState3(stateAdapter) {
|
|
21645
21182
|
return stateAdapter ?? getStateAdapter();
|
|
21646
21183
|
}
|
|
@@ -21665,6 +21202,23 @@ function restoreMessage(args) {
|
|
|
21665
21202
|
rehydrateAttachmentFetchers(message);
|
|
21666
21203
|
return message;
|
|
21667
21204
|
}
|
|
21205
|
+
async function bindSlackActorIdentities(args) {
|
|
21206
|
+
const byAuthorId = /* @__PURE__ */ new Map();
|
|
21207
|
+
for (const message of args.messages) {
|
|
21208
|
+
const authorId = requireSlackAuthorId(message);
|
|
21209
|
+
byAuthorId.set(authorId, [...byAuthorId.get(authorId) ?? [], message]);
|
|
21210
|
+
}
|
|
21211
|
+
await Promise.all(
|
|
21212
|
+
[...byAuthorId].map(async ([authorId, messages]) => {
|
|
21213
|
+
const profile = await args.lookupSlackUser(authorId);
|
|
21214
|
+
await Promise.all(
|
|
21215
|
+
messages.map(
|
|
21216
|
+
(message) => ensureSlackMessageActorIdentity(message, async () => profile)
|
|
21217
|
+
)
|
|
21218
|
+
);
|
|
21219
|
+
})
|
|
21220
|
+
);
|
|
21221
|
+
}
|
|
21668
21222
|
function restoreThread(args) {
|
|
21669
21223
|
const threadId = normalizeIncomingSlackThreadId(
|
|
21670
21224
|
args.threadJson.id,
|
|
@@ -21744,6 +21298,7 @@ function getPendingRecords(work) {
|
|
|
21744
21298
|
function createSlackConversationWorker(options) {
|
|
21745
21299
|
return async (context) => {
|
|
21746
21300
|
const adapter = options.getSlackAdapter();
|
|
21301
|
+
const actorLookup = options.lookupSlackUser ?? lookupSlackUser;
|
|
21747
21302
|
const state = getConnectedState3(options.state);
|
|
21748
21303
|
await state.connect();
|
|
21749
21304
|
const resumeContinuation = options.resumeAwaitingContinuation ?? resumeAwaitingContinuation;
|
|
@@ -21781,6 +21336,10 @@ function createSlackConversationWorker(options) {
|
|
|
21781
21336
|
const messages = records.map(
|
|
21782
21337
|
(record) => restoreMessage({ adapter, record })
|
|
21783
21338
|
);
|
|
21339
|
+
await bindSlackActorIdentities({
|
|
21340
|
+
lookupSlackUser: actorLookup,
|
|
21341
|
+
messages
|
|
21342
|
+
});
|
|
21784
21343
|
const latestMessage = messages[messages.length - 1];
|
|
21785
21344
|
if (!latestMessage) {
|
|
21786
21345
|
return;
|
|
@@ -21867,6 +21426,7 @@ function createSlackConversationWorker(options) {
|
|
|
21867
21426
|
};
|
|
21868
21427
|
}
|
|
21869
21428
|
function buildSlackInboundMessage(args) {
|
|
21429
|
+
const authorId = requireSlackAuthorId(args.message);
|
|
21870
21430
|
return {
|
|
21871
21431
|
conversationId: args.conversationId,
|
|
21872
21432
|
inboundMessageId: [
|
|
@@ -21880,7 +21440,7 @@ function buildSlackInboundMessage(args) {
|
|
|
21880
21440
|
receivedAtMs: args.receivedAtMs,
|
|
21881
21441
|
input: {
|
|
21882
21442
|
text: args.message.text || " ",
|
|
21883
|
-
authorId
|
|
21443
|
+
authorId,
|
|
21884
21444
|
attachments: args.message.attachments,
|
|
21885
21445
|
metadata: {
|
|
21886
21446
|
platform: "slack",
|
|
@@ -21999,7 +21559,8 @@ function extractMessageChangedMention(body, botUserId, adapter) {
|
|
|
21999
21559
|
const channelId = event.channel;
|
|
22000
21560
|
const messageTs = event.message.ts;
|
|
22001
21561
|
const threadTs = event.message.thread_ts ?? messageTs;
|
|
22002
|
-
const userId = event.message.user
|
|
21562
|
+
const userId = parseActorUserId(event.message.user);
|
|
21563
|
+
if (!userId) return null;
|
|
22003
21564
|
const threadId = `slack:${channelId}:${threadTs}`;
|
|
22004
21565
|
const teamId = typeof body.team_id === "string" ? body.team_id : void 0;
|
|
22005
21566
|
const userTeam = typeof event.message.user_team === "string" ? event.message.user_team : void 0;
|
|
@@ -22024,8 +21585,9 @@ function extractMessageChangedMention(body, botUserId, adapter) {
|
|
|
22024
21585
|
raw,
|
|
22025
21586
|
author: {
|
|
22026
21587
|
userId,
|
|
22027
|
-
|
|
22028
|
-
|
|
21588
|
+
// Raw message_changed payloads do not include profile fields.
|
|
21589
|
+
userName: "",
|
|
21590
|
+
fullName: "",
|
|
22029
21591
|
isBot: false,
|
|
22030
21592
|
isMe: false
|
|
22031
21593
|
}
|
|
@@ -22055,7 +21617,17 @@ function isExternalSlackUser(raw) {
|
|
|
22055
21617
|
async function postEphemeral(event, text) {
|
|
22056
21618
|
await event.channel.postEphemeral(event.user, text, { fallbackToDM: false });
|
|
22057
21619
|
}
|
|
22058
|
-
|
|
21620
|
+
function requireRequesterId(event) {
|
|
21621
|
+
const userId = parseActorUserId(event.user.userId);
|
|
21622
|
+
if (!userId) {
|
|
21623
|
+
throw new Error("Slack slash command requires a requester user id");
|
|
21624
|
+
}
|
|
21625
|
+
return userId;
|
|
21626
|
+
}
|
|
21627
|
+
function getCommandName() {
|
|
21628
|
+
return getChatConfig().slack.slashCommand;
|
|
21629
|
+
}
|
|
21630
|
+
async function handleLink(event, requesterId, provider) {
|
|
22059
21631
|
if (!isPluginProvider(provider)) {
|
|
22060
21632
|
await postEphemeral(event, `Unknown provider: \`${provider}\``);
|
|
22061
21633
|
return;
|
|
@@ -22069,7 +21641,7 @@ async function handleLink(event, provider) {
|
|
|
22069
21641
|
}
|
|
22070
21642
|
const raw = event.raw;
|
|
22071
21643
|
const result = await startOAuthFlow(provider, {
|
|
22072
|
-
requesterId
|
|
21644
|
+
requesterId,
|
|
22073
21645
|
channelId: raw.channel_id
|
|
22074
21646
|
});
|
|
22075
21647
|
if (!result.ok) {
|
|
@@ -22088,7 +21660,7 @@ async function handleLink(event, provider) {
|
|
|
22088
21660
|
);
|
|
22089
21661
|
}
|
|
22090
21662
|
}
|
|
22091
|
-
async function handleUnlink(event, provider) {
|
|
21663
|
+
async function handleUnlink(event, requesterId, provider) {
|
|
22092
21664
|
if (!isPluginProvider(provider)) {
|
|
22093
21665
|
await postEphemeral(event, `Unknown provider: \`${provider}\``);
|
|
22094
21666
|
return;
|
|
@@ -22101,12 +21673,12 @@ async function handleUnlink(event, provider) {
|
|
|
22101
21673
|
return;
|
|
22102
21674
|
}
|
|
22103
21675
|
const tokenStore = createUserTokenStore();
|
|
22104
|
-
await tokenStore.delete(
|
|
21676
|
+
await tokenStore.delete(requesterId, provider);
|
|
22105
21677
|
logInfo(
|
|
22106
21678
|
"slash_command_unlink",
|
|
22107
|
-
{ slackUserId:
|
|
21679
|
+
{ slackUserId: requesterId },
|
|
22108
21680
|
{ "app.credential.provider": provider },
|
|
22109
|
-
`Unlinked ${formatProviderLabel(provider)} account via
|
|
21681
|
+
`Unlinked ${formatProviderLabel(provider)} account via ${getCommandName()} slash command`
|
|
22110
21682
|
);
|
|
22111
21683
|
await postEphemeral(
|
|
22112
21684
|
event,
|
|
@@ -22118,19 +21690,23 @@ async function handleSlashCommand(event) {
|
|
|
22118
21690
|
if (!subcommand || !["link", "unlink"].includes(subcommand)) {
|
|
22119
21691
|
await postEphemeral(
|
|
22120
21692
|
event,
|
|
22121
|
-
|
|
21693
|
+
`Usage: \`${getCommandName()} link <provider>\` or \`${getCommandName()} unlink <provider>\``
|
|
22122
21694
|
);
|
|
22123
21695
|
return;
|
|
22124
21696
|
}
|
|
22125
21697
|
if (!provider || rest.length > 0) {
|
|
22126
|
-
await postEphemeral(
|
|
21698
|
+
await postEphemeral(
|
|
21699
|
+
event,
|
|
21700
|
+
`Usage: \`${getCommandName()} ${subcommand} <provider>\``
|
|
21701
|
+
);
|
|
22127
21702
|
return;
|
|
22128
21703
|
}
|
|
22129
21704
|
const normalized = provider.toLowerCase();
|
|
21705
|
+
const requesterId = requireRequesterId(event);
|
|
22130
21706
|
if (subcommand === "link") {
|
|
22131
|
-
await handleLink(event, normalized);
|
|
21707
|
+
await handleLink(event, requesterId, normalized);
|
|
22132
21708
|
} else {
|
|
22133
|
-
await handleUnlink(event, normalized);
|
|
21709
|
+
await handleUnlink(event, requesterId, normalized);
|
|
22134
21710
|
}
|
|
22135
21711
|
}
|
|
22136
21712
|
|
|
@@ -22370,15 +21946,12 @@ async function handleSlackEvent(args) {
|
|
|
22370
21946
|
})
|
|
22371
21947
|
);
|
|
22372
21948
|
}
|
|
22373
|
-
function
|
|
22374
|
-
const userId =
|
|
22375
|
-
|
|
22376
|
-
|
|
22377
|
-
|
|
22378
|
-
|
|
22379
|
-
isBot: false,
|
|
22380
|
-
isMe: false
|
|
22381
|
-
};
|
|
21949
|
+
function requireSlackPayloadUserId(value, source) {
|
|
21950
|
+
const userId = parseActorUserId(value);
|
|
21951
|
+
if (!userId) {
|
|
21952
|
+
throw new Error(`${source} is missing a Slack user id`);
|
|
21953
|
+
}
|
|
21954
|
+
return userId;
|
|
22382
21955
|
}
|
|
22383
21956
|
async function handleSlashCommandForm(args) {
|
|
22384
21957
|
const raw = Object.fromEntries(args.params);
|
|
@@ -22388,7 +21961,21 @@ async function handleSlashCommandForm(args) {
|
|
|
22388
21961
|
adapter: args.adapter,
|
|
22389
21962
|
stateAdapter: args.state
|
|
22390
21963
|
});
|
|
22391
|
-
const userId =
|
|
21964
|
+
const userId = requireSlackPayloadUserId(
|
|
21965
|
+
args.params.get("user_id"),
|
|
21966
|
+
"Slack slash command payload"
|
|
21967
|
+
);
|
|
21968
|
+
const userIdentity = buildActorIdentity(
|
|
21969
|
+
{
|
|
21970
|
+
userId,
|
|
21971
|
+
userName: args.params.get("user_name") ?? void 0,
|
|
21972
|
+
fullName: args.params.get("user_name") ?? void 0
|
|
21973
|
+
},
|
|
21974
|
+
userId
|
|
21975
|
+
);
|
|
21976
|
+
if (!userIdentity?.userId) {
|
|
21977
|
+
throw new Error("Slack slash command payload actor identity is invalid");
|
|
21978
|
+
}
|
|
22392
21979
|
await withSpan(
|
|
22393
21980
|
"chat.slash_command",
|
|
22394
21981
|
"chat.slash_command",
|
|
@@ -22403,8 +21990,8 @@ async function handleSlashCommandForm(args) {
|
|
|
22403
21990
|
raw,
|
|
22404
21991
|
user: {
|
|
22405
21992
|
userId,
|
|
22406
|
-
userName:
|
|
22407
|
-
fullName:
|
|
21993
|
+
userName: userIdentity.userName ?? "",
|
|
21994
|
+
fullName: userIdentity.fullName ?? "",
|
|
22408
21995
|
isBot: false,
|
|
22409
21996
|
isMe: false
|
|
22410
21997
|
},
|
|
@@ -22421,10 +22008,13 @@ async function handleInteractivePayload(args) {
|
|
|
22421
22008
|
(candidate) => candidate.action_id === "app_home_disconnect"
|
|
22422
22009
|
);
|
|
22423
22010
|
const provider = action?.selected_option?.value ?? action?.value;
|
|
22424
|
-
|
|
22425
|
-
if (!provider || !userId) {
|
|
22011
|
+
if (!provider) {
|
|
22426
22012
|
return;
|
|
22427
22013
|
}
|
|
22014
|
+
const userId = requireSlackPayloadUserId(
|
|
22015
|
+
args.payload.user?.id,
|
|
22016
|
+
"Slack app home disconnect payload"
|
|
22017
|
+
);
|
|
22428
22018
|
await withSpan(
|
|
22429
22019
|
"chat.app_home_disconnect",
|
|
22430
22020
|
"chat.app_home_disconnect",
|
|
@@ -22519,7 +22109,7 @@ async function handleSlackForm(args) {
|
|
|
22519
22109
|
})
|
|
22520
22110
|
).catch((error) => {
|
|
22521
22111
|
logException(error, "slack_interactive_payload_failed", {
|
|
22522
|
-
slackUserId:
|
|
22112
|
+
slackUserId: payload.user?.id?.trim() || void 0
|
|
22523
22113
|
});
|
|
22524
22114
|
})
|
|
22525
22115
|
);
|
|
@@ -23242,16 +22832,16 @@ function mountAgentPluginRoutes(app, routes) {
|
|
|
23242
22832
|
async function createApp(options) {
|
|
23243
22833
|
const virtualConfig = await resolveVirtualConfig();
|
|
23244
22834
|
const configuredPlugins = options?.plugins ?? virtualConfig?.pluginSet;
|
|
23245
|
-
const
|
|
22835
|
+
const agentPlugins = trustedPluginRegistrationsFromPluginSet(configuredPlugins);
|
|
23246
22836
|
const pluginConfig = configuredPlugins ? pluginCatalogConfigFromPluginSet(configuredPlugins) : virtualConfig?.plugins ?? resolveEnvPluginCatalogConfig();
|
|
23247
22837
|
if (configuredPlugins) {
|
|
23248
22838
|
validateBuildIncludesPluginPackages(pluginConfig, virtualConfig);
|
|
23249
22839
|
}
|
|
23250
|
-
validateBuildIncludesTrustedRegistrations(
|
|
23251
|
-
validateAgentPlugins(
|
|
22840
|
+
validateBuildIncludesTrustedRegistrations(agentPlugins, virtualConfig);
|
|
22841
|
+
validateAgentPlugins(agentPlugins);
|
|
23252
22842
|
const shouldValidatePluginCatalog = hasConfiguredPluginCatalog(pluginConfig) || Boolean(configuredPlugins?.registrations.length) || Boolean(Object.keys(options?.configDefaults ?? {}).length);
|
|
23253
22843
|
const previousPluginCatalogConfig = setPluginCatalogConfig(pluginConfig);
|
|
23254
|
-
const previousAgentPlugins = setAgentPlugins(
|
|
22844
|
+
const previousAgentPlugins = setAgentPlugins(agentPlugins);
|
|
23255
22845
|
const previousConfigDefaults = getConfigDefaults();
|
|
23256
22846
|
let agentPluginRoutes = [];
|
|
23257
22847
|
try {
|