@sentry/junior 0.66.3 → 0.67.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/app.js +97 -630
- package/dist/chat/config.d.ts +1 -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/turn-session-record.d.ts +6 -1
- package/dist/chat/state/turn-session.d.ts +4 -0
- package/dist/{chunk-DG2I6GXC.js → chunk-HFMZE67J.js} +1801 -1014
- package/dist/{chunk-JA5QR3N4.js → chunk-KWEE2436.js} +1 -1
- package/dist/{chunk-SAYCFF7O.js → chunk-NWU2Z6SM.js} +12 -1
- package/dist/cli/init.js +1 -0
- package/dist/cli/snapshot-warmup.js +2 -2
- package/dist/reporting.d.ts +45 -2
- package/dist/reporting.js +301 -11
- 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,11 +40,14 @@ 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-HFMZE67J.js";
|
|
40
51
|
import {
|
|
41
52
|
discoverSkills,
|
|
42
53
|
findSkillByName,
|
|
@@ -51,7 +62,7 @@ import {
|
|
|
51
62
|
isSnapshotMissingError,
|
|
52
63
|
resolveRuntimeDependencySnapshot,
|
|
53
64
|
runNonInteractiveCommand
|
|
54
|
-
} from "./chunk-
|
|
65
|
+
} from "./chunk-KWEE2436.js";
|
|
55
66
|
import {
|
|
56
67
|
ACTIVE_LOCK_TTL_MS,
|
|
57
68
|
FUNCTION_TIMEOUT_BUFFER_SECONDS,
|
|
@@ -86,7 +97,7 @@ import {
|
|
|
86
97
|
toGenAiPayloadMetadata,
|
|
87
98
|
toGenAiPayloadTraceAttributes,
|
|
88
99
|
toGenAiTextMetadata
|
|
89
|
-
} from "./chunk-
|
|
100
|
+
} from "./chunk-NWU2Z6SM.js";
|
|
90
101
|
import {
|
|
91
102
|
CredentialUnavailableError,
|
|
92
103
|
buildOAuthTokenRequest,
|
|
@@ -178,588 +189,6 @@ function getConfigDefaults() {
|
|
|
178
189
|
return cloneDefaults(installDefaults);
|
|
179
190
|
}
|
|
180
191
|
|
|
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
192
|
// src/chat/respond.ts
|
|
764
193
|
import { Agent as Agent2 } from "@earendil-works/pi-agent-core";
|
|
765
194
|
import { THREAD_STATE_TTL_MS as THREAD_STATE_TTL_MS3 } from "chat";
|
|
@@ -1915,7 +1344,7 @@ function parseMcpProviderFromToolName(toolName) {
|
|
|
1915
1344
|
|
|
1916
1345
|
// src/chat/pi/derived-state.ts
|
|
1917
1346
|
var MCP_BRIDGE_TOOLS = /* @__PURE__ */ new Set(["callMcpTool", "searchMcpTools"]);
|
|
1918
|
-
function
|
|
1347
|
+
function isRecord2(value) {
|
|
1919
1348
|
return Boolean(value && typeof value === "object" && !Array.isArray(value));
|
|
1920
1349
|
}
|
|
1921
1350
|
function providerFromToolName(value) {
|
|
@@ -1937,7 +1366,7 @@ function addBridgeToolProvider(toolName, value, providers) {
|
|
|
1937
1366
|
if (bridgeTool === "searchMcpTools") {
|
|
1938
1367
|
for (const argsKey of ["input", "args", "arguments", "params"]) {
|
|
1939
1368
|
const args = value[argsKey];
|
|
1940
|
-
if (
|
|
1369
|
+
if (isRecord2(args)) {
|
|
1941
1370
|
addString(providers, args.provider);
|
|
1942
1371
|
}
|
|
1943
1372
|
}
|
|
@@ -1946,7 +1375,7 @@ function addBridgeToolProvider(toolName, value, providers) {
|
|
|
1946
1375
|
if (bridgeTool === "callMcpTool") {
|
|
1947
1376
|
for (const argsKey of ["input", "args", "arguments", "params"]) {
|
|
1948
1377
|
const args = value[argsKey];
|
|
1949
|
-
if (
|
|
1378
|
+
if (isRecord2(args)) {
|
|
1950
1379
|
addString(providers, providerFromToolName(args.tool_name));
|
|
1951
1380
|
}
|
|
1952
1381
|
}
|
|
@@ -1959,18 +1388,18 @@ function addMcpResultProvider(message, providers) {
|
|
|
1959
1388
|
return;
|
|
1960
1389
|
}
|
|
1961
1390
|
if (toolName === "loadSkill") {
|
|
1962
|
-
if (
|
|
1391
|
+
if (isRecord2(message.details)) {
|
|
1963
1392
|
addString(providers, message.details.mcp_provider);
|
|
1964
1393
|
}
|
|
1965
1394
|
addString(providers, message.mcp_provider);
|
|
1966
1395
|
return;
|
|
1967
1396
|
}
|
|
1968
1397
|
if (toolName === "searchMcpTools") {
|
|
1969
|
-
if (
|
|
1398
|
+
if (isRecord2(message.details)) {
|
|
1970
1399
|
addString(providers, message.details.provider);
|
|
1971
1400
|
if (Array.isArray(message.details.tools)) {
|
|
1972
1401
|
for (const tool2 of message.details.tools) {
|
|
1973
|
-
if (
|
|
1402
|
+
if (isRecord2(tool2)) {
|
|
1974
1403
|
addString(providers, providerFromToolName(tool2.tool_name));
|
|
1975
1404
|
}
|
|
1976
1405
|
}
|
|
@@ -1982,11 +1411,11 @@ function addMcpResultProvider(message, providers) {
|
|
|
1982
1411
|
if (toolName === "callMcpTool") {
|
|
1983
1412
|
for (const argsKey of ["input", "args", "arguments", "params"]) {
|
|
1984
1413
|
const args = message[argsKey];
|
|
1985
|
-
if (
|
|
1414
|
+
if (isRecord2(args)) {
|
|
1986
1415
|
addString(providers, providerFromToolName(args.tool_name));
|
|
1987
1416
|
}
|
|
1988
1417
|
}
|
|
1989
|
-
if (
|
|
1418
|
+
if (isRecord2(message.details)) {
|
|
1990
1419
|
addString(providers, message.details.provider);
|
|
1991
1420
|
addString(providers, providerFromToolName(message.details.tool_name));
|
|
1992
1421
|
}
|
|
@@ -1994,7 +1423,7 @@ function addMcpResultProvider(message, providers) {
|
|
|
1994
1423
|
}
|
|
1995
1424
|
}
|
|
1996
1425
|
function scanMcpProviders(message, providers) {
|
|
1997
|
-
if (!
|
|
1426
|
+
if (!isRecord2(message)) {
|
|
1998
1427
|
return;
|
|
1999
1428
|
}
|
|
2000
1429
|
if (message.role === "toolResult") {
|
|
@@ -2006,15 +1435,15 @@ function scanMcpProviders(message, providers) {
|
|
|
2006
1435
|
return;
|
|
2007
1436
|
}
|
|
2008
1437
|
for (const part of content) {
|
|
2009
|
-
if (!
|
|
1438
|
+
if (!isRecord2(part)) {
|
|
2010
1439
|
continue;
|
|
2011
1440
|
}
|
|
2012
1441
|
addBridgeToolProvider(getToolName(part), part, providers);
|
|
2013
1442
|
}
|
|
2014
1443
|
}
|
|
2015
1444
|
function scanLoadedSkills(message, skills) {
|
|
2016
|
-
if (
|
|
2017
|
-
if (
|
|
1445
|
+
if (isRecord2(message) && message.role === "toolResult" && message.toolName === "loadSkill" && message.isError !== true) {
|
|
1446
|
+
if (isRecord2(message.details)) {
|
|
2018
1447
|
addString(skills, message.details.skill_name);
|
|
2019
1448
|
}
|
|
2020
1449
|
addString(skills, message.skill_name);
|
|
@@ -7308,7 +6737,7 @@ async function startOAuthFlow(provider, input) {
|
|
|
7308
6737
|
}
|
|
7309
6738
|
|
|
7310
6739
|
// src/chat/sandbox/egress-session.ts
|
|
7311
|
-
import { createHmac
|
|
6740
|
+
import { createHmac, randomUUID, timingSafeEqual } from "crypto";
|
|
7312
6741
|
var SANDBOX_EGRESS_PROXY_PATH = "/api/internal/sandbox-egress";
|
|
7313
6742
|
var SANDBOX_EGRESS_TOKEN_VERSION = "v1";
|
|
7314
6743
|
var SANDBOX_EGRESS_HMAC_CONTEXT = "junior.sandbox_egress.v1";
|
|
@@ -7332,16 +6761,16 @@ function base64Url(input) {
|
|
|
7332
6761
|
function fromBase64Url(input) {
|
|
7333
6762
|
return Buffer.from(input, "base64url").toString("utf8");
|
|
7334
6763
|
}
|
|
7335
|
-
function
|
|
7336
|
-
return
|
|
6764
|
+
function signPayload(payload) {
|
|
6765
|
+
return createHmac("sha256", getSandboxEgressSecret()).update(`${SANDBOX_EGRESS_HMAC_CONTEXT}:${payload}`).digest("base64url");
|
|
7337
6766
|
}
|
|
7338
|
-
function
|
|
6767
|
+
function timingSafeMatch(expected, actual) {
|
|
7339
6768
|
const expectedBuffer = Buffer.from(expected);
|
|
7340
6769
|
const actualBuffer = Buffer.from(actual);
|
|
7341
6770
|
if (expectedBuffer.length !== actualBuffer.length) {
|
|
7342
6771
|
return false;
|
|
7343
6772
|
}
|
|
7344
|
-
return
|
|
6773
|
+
return timingSafeEqual(expectedBuffer, actualBuffer);
|
|
7345
6774
|
}
|
|
7346
6775
|
function parseSandboxEgressContext(value) {
|
|
7347
6776
|
if (!value || typeof value !== "object") {
|
|
@@ -7400,7 +6829,7 @@ function createSandboxEgressCredentialToken(input) {
|
|
|
7400
6829
|
const payload = `${SANDBOX_EGRESS_TOKEN_VERSION}.${base64Url(
|
|
7401
6830
|
JSON.stringify(context)
|
|
7402
6831
|
)}`;
|
|
7403
|
-
return `${payload}.${
|
|
6832
|
+
return `${payload}.${signPayload(payload)}`;
|
|
7404
6833
|
}
|
|
7405
6834
|
function parseSandboxEgressCredentialToken(token) {
|
|
7406
6835
|
if (!token) {
|
|
@@ -7416,7 +6845,7 @@ function parseSandboxEgressCredentialToken(token) {
|
|
|
7416
6845
|
return void 0;
|
|
7417
6846
|
}
|
|
7418
6847
|
const payload = `${parts[0]}.${encodedSession}`;
|
|
7419
|
-
if (!
|
|
6848
|
+
if (!timingSafeMatch(signPayload(payload), signature)) {
|
|
7420
6849
|
return void 0;
|
|
7421
6850
|
}
|
|
7422
6851
|
try {
|
|
@@ -11125,6 +10554,7 @@ async function persistRunningSessionRecord(args) {
|
|
|
11125
10554
|
sliceId: args.sliceId,
|
|
11126
10555
|
state: "running",
|
|
11127
10556
|
piMessages: args.messages,
|
|
10557
|
+
...args.surface ?? latestSessionRecord?.surface ? { surface: args.surface ?? latestSessionRecord?.surface } : {},
|
|
11128
10558
|
...args.loadedSkillNames ? { loadedSkillNames: args.loadedSkillNames } : {},
|
|
11129
10559
|
...args.requester ?? latestSessionRecord?.requester ? { requester: args.requester ?? latestSessionRecord?.requester } : {},
|
|
11130
10560
|
...getActiveTraceId() ?? latestSessionRecord?.traceId ? { traceId: getActiveTraceId() ?? latestSessionRecord?.traceId } : {}
|
|
@@ -11164,6 +10594,7 @@ async function persistCompletedSessionRecord(args) {
|
|
|
11164
10594
|
sliceId: args.sliceId,
|
|
11165
10595
|
state: "completed",
|
|
11166
10596
|
piMessages: args.allMessages,
|
|
10597
|
+
...args.surface ?? latestSessionRecord?.surface ? { surface: args.surface ?? latestSessionRecord?.surface } : {},
|
|
11167
10598
|
...args.loadedSkillNames ? { loadedSkillNames: args.loadedSkillNames } : {},
|
|
11168
10599
|
...args.requester ?? latestSessionRecord?.requester ? { requester: args.requester ?? latestSessionRecord?.requester } : {},
|
|
11169
10600
|
...getActiveTraceId() ?? latestSessionRecord?.traceId ? { traceId: getActiveTraceId() ?? latestSessionRecord?.traceId } : {}
|
|
@@ -11209,6 +10640,7 @@ async function persistAuthPauseSessionRecord(args) {
|
|
|
11209
10640
|
sliceId: nextSliceId,
|
|
11210
10641
|
state: "awaiting_resume",
|
|
11211
10642
|
piMessages,
|
|
10643
|
+
...args.surface ?? latestSessionRecord?.surface ? { surface: args.surface ?? latestSessionRecord?.surface } : {},
|
|
11212
10644
|
...args.loadedSkillNames ? { loadedSkillNames: args.loadedSkillNames } : {},
|
|
11213
10645
|
resumeReason: "auth",
|
|
11214
10646
|
resumedFromSliceId: args.currentSliceId,
|
|
@@ -11264,6 +10696,7 @@ async function persistTimeoutSessionRecord(args) {
|
|
|
11264
10696
|
sliceId: args.currentSliceId,
|
|
11265
10697
|
state: "failed",
|
|
11266
10698
|
piMessages,
|
|
10699
|
+
...args.surface ?? latestSessionRecord?.surface ? { surface: args.surface ?? latestSessionRecord?.surface } : {},
|
|
11267
10700
|
...args.loadedSkillNames ? { loadedSkillNames: args.loadedSkillNames } : {},
|
|
11268
10701
|
resumeReason: "timeout",
|
|
11269
10702
|
resumedFromSliceId: latestSessionRecord?.resumedFromSliceId,
|
|
@@ -11281,6 +10714,7 @@ async function persistTimeoutSessionRecord(args) {
|
|
|
11281
10714
|
sliceId: nextSliceId,
|
|
11282
10715
|
state: "awaiting_resume",
|
|
11283
10716
|
piMessages,
|
|
10717
|
+
...args.surface ?? latestSessionRecord?.surface ? { surface: args.surface ?? latestSessionRecord?.surface } : {},
|
|
11284
10718
|
...args.loadedSkillNames ? { loadedSkillNames: args.loadedSkillNames } : {},
|
|
11285
10719
|
resumeReason: "timeout",
|
|
11286
10720
|
resumedFromSliceId: args.currentSliceId,
|
|
@@ -11330,6 +10764,7 @@ async function persistYieldSessionRecord(args) {
|
|
|
11330
10764
|
sliceId: args.currentSliceId,
|
|
11331
10765
|
state: "awaiting_resume",
|
|
11332
10766
|
piMessages,
|
|
10767
|
+
...args.surface ?? latestSessionRecord?.surface ? { surface: args.surface ?? latestSessionRecord?.surface } : {},
|
|
11333
10768
|
...args.loadedSkillNames ? { loadedSkillNames: args.loadedSkillNames } : {},
|
|
11334
10769
|
resumeReason: "yield",
|
|
11335
10770
|
resumedFromSliceId: latestSessionRecord?.resumedFromSliceId,
|
|
@@ -11797,6 +11232,23 @@ function requesterFromContext(requester, requesterId) {
|
|
|
11797
11232
|
};
|
|
11798
11233
|
return Object.keys(identity).length > 0 ? identity : void 0;
|
|
11799
11234
|
}
|
|
11235
|
+
function surfaceFromContext(context) {
|
|
11236
|
+
if (context.surface) {
|
|
11237
|
+
return context.surface;
|
|
11238
|
+
}
|
|
11239
|
+
const conversationId = context.correlation?.conversationId ?? context.correlation?.threadId ?? context.correlation?.runId;
|
|
11240
|
+
if (context.slackConversation || (conversationId ? parseSlackThreadId(conversationId) : void 0)) {
|
|
11241
|
+
return "slack";
|
|
11242
|
+
}
|
|
11243
|
+
const actor = context.credentialContext?.actor;
|
|
11244
|
+
if (actor?.type === "system" && actor.id === "scheduler") {
|
|
11245
|
+
return "scheduler";
|
|
11246
|
+
}
|
|
11247
|
+
if (conversationId) {
|
|
11248
|
+
return "api";
|
|
11249
|
+
}
|
|
11250
|
+
return void 0;
|
|
11251
|
+
}
|
|
11800
11252
|
function supportsRouterTextPreview(mediaType) {
|
|
11801
11253
|
const baseMediaType = mediaType.split(";", 1)[0]?.trim().toLowerCase();
|
|
11802
11254
|
if (!baseMediaType) {
|
|
@@ -11912,6 +11364,7 @@ async function generateAssistantReply(messageText2, context = {}) {
|
|
|
11912
11364
|
context.requester,
|
|
11913
11365
|
context.correlation?.requesterId
|
|
11914
11366
|
);
|
|
11367
|
+
const surface = surfaceFromContext(context);
|
|
11915
11368
|
const credentialActor = context.credentialContext?.actor;
|
|
11916
11369
|
const credentialActorLogContext = credentialActor ? {
|
|
11917
11370
|
actorType: credentialActor.type,
|
|
@@ -12437,7 +11890,8 @@ async function generateAssistantReply(messageText2, context = {}) {
|
|
|
12437
11890
|
messages,
|
|
12438
11891
|
loadedSkillNames: loadedSkillNamesForResume,
|
|
12439
11892
|
logContext: sessionRecordLogContext,
|
|
12440
|
-
requester
|
|
11893
|
+
requester,
|
|
11894
|
+
...surface ? { surface } : {}
|
|
12441
11895
|
});
|
|
12442
11896
|
if (!persisted) {
|
|
12443
11897
|
return false;
|
|
@@ -12750,7 +12204,8 @@ async function generateAssistantReply(messageText2, context = {}) {
|
|
|
12750
12204
|
allMessages: agent.state.messages,
|
|
12751
12205
|
loadedSkillNames: loadedSkillNamesForResume,
|
|
12752
12206
|
logContext: sessionRecordLogContext,
|
|
12753
|
-
requester
|
|
12207
|
+
requester,
|
|
12208
|
+
...surface ? { surface } : {}
|
|
12754
12209
|
});
|
|
12755
12210
|
}
|
|
12756
12211
|
return buildTurnResult({
|
|
@@ -12785,7 +12240,8 @@ async function generateAssistantReply(messageText2, context = {}) {
|
|
|
12785
12240
|
errorMessage: error.message,
|
|
12786
12241
|
loadedSkillNames: loadedSkillNamesForResume,
|
|
12787
12242
|
logContext: sessionRecordLogContext,
|
|
12788
|
-
requester
|
|
12243
|
+
requester,
|
|
12244
|
+
...surface ? { surface } : {}
|
|
12789
12245
|
});
|
|
12790
12246
|
if (!sessionRecord) {
|
|
12791
12247
|
throw new Error(
|
|
@@ -12808,7 +12264,8 @@ async function generateAssistantReply(messageText2, context = {}) {
|
|
|
12808
12264
|
errorMessage: error instanceof Error ? error.message : String(error),
|
|
12809
12265
|
loadedSkillNames: loadedSkillNamesForResume,
|
|
12810
12266
|
logContext: sessionRecordLogContext,
|
|
12811
|
-
requester
|
|
12267
|
+
requester,
|
|
12268
|
+
...surface ? { surface } : {}
|
|
12812
12269
|
});
|
|
12813
12270
|
if (!sessionRecord) {
|
|
12814
12271
|
throw new Error(
|
|
@@ -12850,7 +12307,8 @@ async function generateAssistantReply(messageText2, context = {}) {
|
|
|
12850
12307
|
errorMessage: error.message,
|
|
12851
12308
|
loadedSkillNames: loadedSkillNamesForResume,
|
|
12852
12309
|
logContext: sessionRecordLogContext,
|
|
12853
|
-
requester
|
|
12310
|
+
requester,
|
|
12311
|
+
...surface ? { surface } : {}
|
|
12854
12312
|
});
|
|
12855
12313
|
if (sessionRecord) {
|
|
12856
12314
|
throw new RetryableTurnError(
|
|
@@ -13630,7 +13088,7 @@ function finalizeFailedTurnReply(args) {
|
|
|
13630
13088
|
}
|
|
13631
13089
|
|
|
13632
13090
|
// src/chat/agent-dispatch/signing.ts
|
|
13633
|
-
import { createHmac as
|
|
13091
|
+
import { createHmac as createHmac2, timingSafeEqual as timingSafeEqual2 } from "crypto";
|
|
13634
13092
|
var DISPATCH_CALLBACK_PATH = "/api/internal/agent-dispatch";
|
|
13635
13093
|
var DISPATCH_HMAC_CONTEXT = "junior.agent_dispatch.v1";
|
|
13636
13094
|
var DISPATCH_SIGNATURE_VERSION = "v1";
|
|
@@ -13645,16 +13103,16 @@ function buildSignedPayload(timestamp, body) {
|
|
|
13645
13103
|
return `${DISPATCH_HMAC_CONTEXT}:${timestamp}:${body}`;
|
|
13646
13104
|
}
|
|
13647
13105
|
function signBody(secret, timestamp, body) {
|
|
13648
|
-
const digest =
|
|
13106
|
+
const digest = createHmac2("sha256", secret).update(buildSignedPayload(timestamp, body)).digest("hex");
|
|
13649
13107
|
return `${DISPATCH_SIGNATURE_VERSION}=${digest}`;
|
|
13650
13108
|
}
|
|
13651
|
-
function
|
|
13109
|
+
function timingSafeMatch2(expected, actual) {
|
|
13652
13110
|
const expectedBuffer = Buffer.from(expected);
|
|
13653
13111
|
const actualBuffer = Buffer.from(actual);
|
|
13654
13112
|
if (expectedBuffer.length !== actualBuffer.length) {
|
|
13655
13113
|
return false;
|
|
13656
13114
|
}
|
|
13657
|
-
return
|
|
13115
|
+
return timingSafeEqual2(expectedBuffer, actualBuffer);
|
|
13658
13116
|
}
|
|
13659
13117
|
function parseDispatchCallback(value) {
|
|
13660
13118
|
if (!value || typeof value !== "object") {
|
|
@@ -13713,7 +13171,7 @@ async function verifyDispatchCallbackRequest(request) {
|
|
|
13713
13171
|
}
|
|
13714
13172
|
const body = await request.text();
|
|
13715
13173
|
const expectedSignature = signBody(secret, timestamp, body);
|
|
13716
|
-
if (!
|
|
13174
|
+
if (!timingSafeMatch2(expectedSignature, signature)) {
|
|
13717
13175
|
return void 0;
|
|
13718
13176
|
}
|
|
13719
13177
|
try {
|
|
@@ -13724,7 +13182,7 @@ async function verifyDispatchCallbackRequest(request) {
|
|
|
13724
13182
|
}
|
|
13725
13183
|
|
|
13726
13184
|
// src/chat/agent-dispatch/store.ts
|
|
13727
|
-
import { createHash
|
|
13185
|
+
import { createHash } from "crypto";
|
|
13728
13186
|
var DISPATCH_PREFIX = "junior:agent_dispatch";
|
|
13729
13187
|
var DISPATCH_LOCK_TTL_MS = 10 * 60 * 1e3;
|
|
13730
13188
|
var DISPATCH_INDEX_LOCK_TTL_MS = 1e4;
|
|
@@ -13752,7 +13210,7 @@ function normalizeMetadata(metadata) {
|
|
|
13752
13210
|
return entries.length > 0 ? Object.fromEntries(entries) : void 0;
|
|
13753
13211
|
}
|
|
13754
13212
|
function buildDispatchId(plugin, idempotencyKey) {
|
|
13755
|
-
const digest =
|
|
13213
|
+
const digest = createHash("sha256").update(plugin).update("\0").update(idempotencyKey).digest("hex").slice(0, 32);
|
|
13756
13214
|
return `dispatch_${digest}`;
|
|
13757
13215
|
}
|
|
13758
13216
|
function getDispatchDestinationLockId(destination) {
|
|
@@ -14059,6 +13517,7 @@ async function runAgentDispatchSlice(callback, deps = {}) {
|
|
|
14059
13517
|
channelId: dispatch.destination.channelId,
|
|
14060
13518
|
teamId: dispatch.destination.teamId
|
|
14061
13519
|
},
|
|
13520
|
+
surface: dispatch.actor.id === "scheduler" ? "scheduler" : "api",
|
|
14062
13521
|
toolChannelId: dispatch.destination.channelId,
|
|
14063
13522
|
sandbox: {
|
|
14064
13523
|
sandboxId,
|
|
@@ -14234,10 +13693,10 @@ async function POST(request, waitUntil) {
|
|
|
14234
13693
|
}
|
|
14235
13694
|
|
|
14236
13695
|
// src/handlers/heartbeat.ts
|
|
14237
|
-
import { timingSafeEqual as
|
|
13696
|
+
import { timingSafeEqual as timingSafeEqual4 } from "crypto";
|
|
14238
13697
|
|
|
14239
13698
|
// src/chat/services/timeout-resume.ts
|
|
14240
|
-
import { createHmac as
|
|
13699
|
+
import { createHmac as createHmac3, timingSafeEqual as timingSafeEqual3 } from "crypto";
|
|
14241
13700
|
|
|
14242
13701
|
// src/chat/task-execution/store.ts
|
|
14243
13702
|
import { randomUUID as randomUUID4 } from "crypto";
|
|
@@ -14836,16 +14295,16 @@ function buildSignedPayload2(timestamp, body) {
|
|
|
14836
14295
|
return `${TURN_TIMEOUT_RESUME_HMAC_CONTEXT}:${timestamp}:${body}`;
|
|
14837
14296
|
}
|
|
14838
14297
|
function signTurnTimeoutResumeBody(secret, timestamp, body) {
|
|
14839
|
-
const digest =
|
|
14298
|
+
const digest = createHmac3("sha256", secret).update(buildSignedPayload2(timestamp, body)).digest("hex");
|
|
14840
14299
|
return `${TURN_TIMEOUT_RESUME_SIGNATURE_VERSION}=${digest}`;
|
|
14841
14300
|
}
|
|
14842
|
-
function
|
|
14301
|
+
function timingSafeMatch3(expected, actual) {
|
|
14843
14302
|
const expectedBuffer = Buffer.from(expected);
|
|
14844
14303
|
const actualBuffer = Buffer.from(actual);
|
|
14845
14304
|
if (expectedBuffer.length !== actualBuffer.length) {
|
|
14846
14305
|
return false;
|
|
14847
14306
|
}
|
|
14848
|
-
return
|
|
14307
|
+
return timingSafeEqual3(expectedBuffer, actualBuffer);
|
|
14849
14308
|
}
|
|
14850
14309
|
function parseTurnTimeoutResumeRequest(value) {
|
|
14851
14310
|
if (!value || typeof value !== "object") {
|
|
@@ -14900,7 +14359,7 @@ async function verifyTurnTimeoutResumeRequest(request) {
|
|
|
14900
14359
|
}
|
|
14901
14360
|
const body = await request.text();
|
|
14902
14361
|
const expectedSignature = signTurnTimeoutResumeBody(secret, timestamp, body);
|
|
14903
|
-
if (!
|
|
14362
|
+
if (!timingSafeMatch3(expectedSignature, signature)) {
|
|
14904
14363
|
return void 0;
|
|
14905
14364
|
}
|
|
14906
14365
|
try {
|
|
@@ -15402,7 +14861,7 @@ function verifyHeartbeatRequest(request) {
|
|
|
15402
14861
|
}
|
|
15403
14862
|
const actual = Buffer.from(authorization.slice("Bearer ".length));
|
|
15404
14863
|
const expected = Buffer.from(secret);
|
|
15405
|
-
return actual.length === expected.length &&
|
|
14864
|
+
return actual.length === expected.length && timingSafeEqual4(actual, expected);
|
|
15406
14865
|
}
|
|
15407
14866
|
async function GET2(request, waitUntil, options = {}) {
|
|
15408
14867
|
if (!verifyHeartbeatRequest(request)) {
|
|
@@ -20630,6 +20089,7 @@ function createReplyToThread(deps) {
|
|
|
20630
20089
|
sliceId: 1,
|
|
20631
20090
|
startedAtMs: message.metadata.dateSent.getTime(),
|
|
20632
20091
|
state: "running",
|
|
20092
|
+
surface: "slack",
|
|
20633
20093
|
requester,
|
|
20634
20094
|
traceId: getActiveTraceId()
|
|
20635
20095
|
}).catch((error) => {
|
|
@@ -20817,6 +20277,7 @@ function createReplyToThread(deps) {
|
|
|
20817
20277
|
omittedImageAttachmentCount,
|
|
20818
20278
|
userAttachments,
|
|
20819
20279
|
slackConversation,
|
|
20280
|
+
surface: "slack",
|
|
20820
20281
|
turnDeadlineAtMs: getTurnRequestDeadline()?.deadlineAtMs,
|
|
20821
20282
|
correlation: {
|
|
20822
20283
|
conversationId,
|
|
@@ -22055,6 +21516,9 @@ function isExternalSlackUser(raw) {
|
|
|
22055
21516
|
async function postEphemeral(event, text) {
|
|
22056
21517
|
await event.channel.postEphemeral(event.user, text, { fallbackToDM: false });
|
|
22057
21518
|
}
|
|
21519
|
+
function getCommandName() {
|
|
21520
|
+
return getChatConfig().slack.slashCommand;
|
|
21521
|
+
}
|
|
22058
21522
|
async function handleLink(event, provider) {
|
|
22059
21523
|
if (!isPluginProvider(provider)) {
|
|
22060
21524
|
await postEphemeral(event, `Unknown provider: \`${provider}\``);
|
|
@@ -22106,7 +21570,7 @@ async function handleUnlink(event, provider) {
|
|
|
22106
21570
|
"slash_command_unlink",
|
|
22107
21571
|
{ slackUserId: event.user.userId },
|
|
22108
21572
|
{ "app.credential.provider": provider },
|
|
22109
|
-
`Unlinked ${formatProviderLabel(provider)} account via
|
|
21573
|
+
`Unlinked ${formatProviderLabel(provider)} account via ${getCommandName()} slash command`
|
|
22110
21574
|
);
|
|
22111
21575
|
await postEphemeral(
|
|
22112
21576
|
event,
|
|
@@ -22118,12 +21582,15 @@ async function handleSlashCommand(event) {
|
|
|
22118
21582
|
if (!subcommand || !["link", "unlink"].includes(subcommand)) {
|
|
22119
21583
|
await postEphemeral(
|
|
22120
21584
|
event,
|
|
22121
|
-
|
|
21585
|
+
`Usage: \`${getCommandName()} link <provider>\` or \`${getCommandName()} unlink <provider>\``
|
|
22122
21586
|
);
|
|
22123
21587
|
return;
|
|
22124
21588
|
}
|
|
22125
21589
|
if (!provider || rest.length > 0) {
|
|
22126
|
-
await postEphemeral(
|
|
21590
|
+
await postEphemeral(
|
|
21591
|
+
event,
|
|
21592
|
+
`Usage: \`${getCommandName()} ${subcommand} <provider>\``
|
|
21593
|
+
);
|
|
22127
21594
|
return;
|
|
22128
21595
|
}
|
|
22129
21596
|
const normalized = provider.toLowerCase();
|
|
@@ -23242,16 +22709,16 @@ function mountAgentPluginRoutes(app, routes) {
|
|
|
23242
22709
|
async function createApp(options) {
|
|
23243
22710
|
const virtualConfig = await resolveVirtualConfig();
|
|
23244
22711
|
const configuredPlugins = options?.plugins ?? virtualConfig?.pluginSet;
|
|
23245
|
-
const
|
|
22712
|
+
const agentPlugins = trustedPluginRegistrationsFromPluginSet(configuredPlugins);
|
|
23246
22713
|
const pluginConfig = configuredPlugins ? pluginCatalogConfigFromPluginSet(configuredPlugins) : virtualConfig?.plugins ?? resolveEnvPluginCatalogConfig();
|
|
23247
22714
|
if (configuredPlugins) {
|
|
23248
22715
|
validateBuildIncludesPluginPackages(pluginConfig, virtualConfig);
|
|
23249
22716
|
}
|
|
23250
|
-
validateBuildIncludesTrustedRegistrations(
|
|
23251
|
-
validateAgentPlugins(
|
|
22717
|
+
validateBuildIncludesTrustedRegistrations(agentPlugins, virtualConfig);
|
|
22718
|
+
validateAgentPlugins(agentPlugins);
|
|
23252
22719
|
const shouldValidatePluginCatalog = hasConfiguredPluginCatalog(pluginConfig) || Boolean(configuredPlugins?.registrations.length) || Boolean(Object.keys(options?.configDefaults ?? {}).length);
|
|
23253
22720
|
const previousPluginCatalogConfig = setPluginCatalogConfig(pluginConfig);
|
|
23254
|
-
const previousAgentPlugins = setAgentPlugins(
|
|
22721
|
+
const previousAgentPlugins = setAgentPlugins(agentPlugins);
|
|
23255
22722
|
const previousConfigDefaults = getConfigDefaults();
|
|
23256
22723
|
let agentPluginRoutes = [];
|
|
23257
22724
|
try {
|