@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 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-DG2I6GXC.js";
50
+ } from "./chunk-5UIDU7XR.js";
40
51
  import {
41
52
  discoverSkills,
42
53
  findSkillByName,
43
54
  loadSkillsByName,
44
55
  parseSkillInvocation
45
- } from "./chunk-YL5G5YC4.js";
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-JA5QR3N4.js";
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-SAYCFF7O.js";
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-6QWWMZCK.js";
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 isRecord3(value) {
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 (isRecord3(args)) {
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 (isRecord3(args)) {
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 (isRecord3(message.details)) {
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 (isRecord3(message.details)) {
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 (isRecord3(tool2)) {
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 (isRecord3(args)) {
1419
+ if (isRecord2(args)) {
1986
1420
  addString(providers, providerFromToolName(args.tool_name));
1987
1421
  }
1988
1422
  }
1989
- if (isRecord3(message.details)) {
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 (!isRecord3(message)) {
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 (!isRecord3(part)) {
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 (isRecord3(message) && message.role === "toolResult" && message.toolName === "loadSkill" && message.isError !== true) {
2017
- if (isRecord3(message.details)) {
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.trim();
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 as createHmac2, randomUUID, timingSafeEqual as timingSafeEqual2 } from "crypto";
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 signPayload2(payload) {
7336
- return createHmac2("sha256", getSandboxEgressSecret()).update(`${SANDBOX_EGRESS_HMAC_CONTEXT}:${payload}`).digest("base64url");
6758
+ function signPayload(payload) {
6759
+ return createHmac("sha256", getSandboxEgressSecret()).update(`${SANDBOX_EGRESS_HMAC_CONTEXT}:${payload}`).digest("base64url");
7337
6760
  }
7338
- function timingSafeMatch2(expected, actual) {
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 timingSafeEqual2(expectedBuffer, actualBuffer);
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}.${signPayload2(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 (!timingSafeMatch2(signPayload2(payload), signature)) {
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
- ...requester?.email ? { email: requester.email } : {},
11794
- ...requester?.fullName ? { fullName: requester.fullName } : {},
11795
- ...requesterId ?? requester?.userId ? { slackUserId: requesterId ?? requester?.userId } : {},
11796
- ...requester?.userName ? { slackUserName: requester.userName } : {}
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(identity).length > 0 ? identity : void 0;
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: context.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: context.requester?.userId,
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: context.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: context.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 = message.author?.fullName || message.author?.userName || (message.role === "assistant" ? botConfig.userName : message.role);
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.author?.userName ?? message.role);
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 createHmac3, timingSafeEqual as timingSafeEqual3 } from "crypto";
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 = createHmac3("sha256", secret).update(buildSignedPayload(timestamp, body)).digest("hex");
13122
+ const digest = createHmac2("sha256", secret).update(buildSignedPayload(timestamp, body)).digest("hex");
13649
13123
  return `${DISPATCH_SIGNATURE_VERSION}=${digest}`;
13650
13124
  }
13651
- function timingSafeMatch3(expected, actual) {
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 timingSafeEqual3(expectedBuffer, actualBuffer);
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 (!timingSafeMatch3(expectedSignature, signature)) {
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 as createHash2 } from "crypto";
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 = createHash2("sha256").update(plugin).update("\0").update(idempotencyKey).digest("hex").slice(0, 32);
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 timingSafeEqual5 } from "crypto";
13712
+ import { timingSafeEqual as timingSafeEqual4 } from "crypto";
14238
13713
 
14239
13714
  // src/chat/services/timeout-resume.ts
14240
- import { createHmac as createHmac4, timingSafeEqual as timingSafeEqual4 } from "crypto";
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 = createHmac4("sha256", secret).update(buildSignedPayload2(timestamp, body)).digest("hex");
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 timingSafeMatch4(expected, actual) {
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 timingSafeEqual4(expectedBuffer, actualBuffer);
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 (!timingSafeMatch4(expectedSignature, signature)) {
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.trim()) {
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 && timingSafeEqual5(actual, expected);
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) === PROCESSING_REACTION_EMOJI;
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: PROCESSING_REACTION_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: PROCESSING_REACTION_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: COMPLETED_REACTION_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: { userId: stored.userId },
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.author.userName,
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.author.userName,
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.author.userName,
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.author.userName,
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.author.userName,
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(args) {
19866
+ function turnRequester(identity) {
20338
19867
  const requester = {
20339
- ...args.email ? { email: args.email } : {},
20340
- ...args.fullName ? { fullName: args.fullName } : {},
20341
- ...args.userId ? { slackUserId: args.userId } : {},
20342
- ...args.userName ? { slackUserName: args.userName } : {}
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 Object.keys(requester).length > 0 ? requester : void 0;
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
- ...resolvedUserName ? { username: resolvedUserName } : {},
20670
- ...fallbackIdentity?.email ? { email: fallbackIdentity.email } : {}
20202
+ ...requesterIdentity.userName ? { username: requesterIdentity.userName } : {},
20203
+ ...requesterIdentity.email ? { email: requesterIdentity.email } : {}
20671
20204
  });
20672
20205
  }
20673
- if (resolvedUserName) {
20674
- setTags({ slackUserName: resolvedUserName });
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: args.entry.author.userId,
21226
- userName: args.entry.author.userName,
21227
- fullName: args.entry.author.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: args.message.author.userId,
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 ?? "unknown";
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
- userName: userId,
22028
- fullName: userId,
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
- async function handleLink(event, provider) {
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: event.user.userId,
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(event.user.userId, provider);
21676
+ await tokenStore.delete(requesterId, provider);
22105
21677
  logInfo(
22106
21678
  "slash_command_unlink",
22107
- { slackUserId: event.user.userId },
21679
+ { slackUserId: requesterId },
22108
21680
  { "app.credential.provider": provider },
22109
- `Unlinked ${formatProviderLabel(provider)} account via /jr slash command`
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
- "Usage: `/jr link <provider>` or `/jr unlink <provider>`"
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(event, `Usage: \`/jr ${subcommand} <provider>\``);
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 buildAuthorFromInteractive(user) {
22374
- const userId = user?.id ?? "unknown";
22375
- return {
22376
- userId,
22377
- userName: user?.username ?? user?.name ?? userId,
22378
- fullName: user?.name ?? user?.username ?? userId,
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 = args.params.get("user_id") || "unknown";
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: args.params.get("user_name") || userId,
22407
- fullName: args.params.get("user_name") || userId,
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
- const userId = args.payload.user?.id;
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: buildAuthorFromInteractive(payload.user).userId
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 agentPlugins2 = trustedPluginRegistrationsFromPluginSet(configuredPlugins);
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(agentPlugins2, virtualConfig);
23251
- validateAgentPlugins(agentPlugins2);
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(agentPlugins2);
22844
+ const previousAgentPlugins = setAgentPlugins(agentPlugins);
23255
22845
  const previousConfigDefaults = getConfigDefaults();
23256
22846
  let agentPluginRoutes = [];
23257
22847
  try {