@sentry/junior 0.113.0 → 0.115.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.
Files changed (98) hide show
  1. package/dist/{agent-hooks-BKEMWR3Z.js → agent-hooks-4EUUXP2F.js} +12 -11
  2. package/dist/api/conversations/detail.d.ts +26 -0
  3. package/dist/api/conversations/event-list.d.ts +26 -0
  4. package/dist/api/conversations/events.d.ts +1 -1
  5. package/dist/api/health.d.ts +7 -0
  6. package/dist/api/plugin-reports.d.ts +62 -0
  7. package/dist/api/plugins.d.ts +9 -0
  8. package/dist/api/runtime.d.ts +24 -0
  9. package/dist/api/schema/conversation.d.ts +104 -0
  10. package/dist/api/schema.d.ts +2 -2
  11. package/dist/api/schema.js +6 -6
  12. package/dist/api/skills.d.ts +6 -0
  13. package/dist/api.js +140 -100
  14. package/dist/app.d.ts +2 -0
  15. package/dist/app.js +1117 -5010
  16. package/dist/catalog-runtime-Y32UEQLB.js +18 -0
  17. package/dist/chat/agent/request.d.ts +6 -0
  18. package/dist/chat/agent/sandbox.d.ts +2 -0
  19. package/dist/chat/agent/tools.d.ts +2 -0
  20. package/dist/chat/config.d.ts +3 -1
  21. package/dist/chat/conversations/history.d.ts +92 -0
  22. package/dist/chat/conversations/projection.d.ts +20 -1
  23. package/dist/chat/local/credential-sync.d.ts +5 -0
  24. package/dist/chat/local/oauth-relay.d.ts +4 -0
  25. package/dist/chat/mcp/oauth.d.ts +1 -0
  26. package/dist/chat/oauth-authorization.d.ts +17 -0
  27. package/dist/chat/oauth-flow.d.ts +9 -0
  28. package/dist/chat/sandbox/egress/schemas.d.ts +46 -0
  29. package/dist/chat/sandbox/egress/signals.d.ts +9 -0
  30. package/dist/chat/sandbox/sandbox.d.ts +2 -0
  31. package/dist/chat/sentry-payload-filter.d.ts +4 -4
  32. package/dist/chat/services/context-budget.d.ts +7 -2
  33. package/dist/chat/services/context-compaction.d.ts +28 -1
  34. package/dist/chat/services/conversation-memory.d.ts +1 -0
  35. package/dist/chat/services/mcp-auth-orchestration.d.ts +5 -3
  36. package/dist/chat/services/plugin-auth-orchestration.d.ts +2 -0
  37. package/dist/chat/services/provider-error.d.ts +1 -0
  38. package/dist/chat/services/turn-router.d.ts +1 -0
  39. package/dist/chat/slack/message-control.d.ts +2 -0
  40. package/dist/chat/slack/oauth-authorization-message.d.ts +3 -0
  41. package/dist/chat/task-execution/slack-work.d.ts +2 -0
  42. package/dist/chat/tool-support/zod-tool.d.ts +1 -1
  43. package/dist/chat/tools/definition.d.ts +5 -0
  44. package/dist/chat/tools/sandbox/edit-file.d.ts +1 -1
  45. package/dist/chat/tools/sandbox/text-edits.d.ts +1 -0
  46. package/dist/{chunk-4WZ7YKDF.js → chunk-2QG4MZQ7.js} +2 -2
  47. package/dist/{chunk-H5ON7XTE.js → chunk-43RYMWJ2.js} +954 -2514
  48. package/dist/chunk-4CODA7P5.js +43 -0
  49. package/dist/{chunk-HMRVILWZ.js → chunk-67CVNDJ7.js} +1 -1
  50. package/dist/{chunk-LR2SKL5Y.js → chunk-6FKLPVF3.js} +1 -1
  51. package/dist/{chunk-2ZTTXTSZ.js → chunk-6OPTF4BQ.js} +10 -4
  52. package/dist/{chunk-3CTIAKSS.js → chunk-6V762AB4.js} +1 -1
  53. package/dist/{chunk-WBB22ANL.js → chunk-AGLQYELI.js} +77 -8
  54. package/dist/{chunk-DXRPHBOU.js → chunk-CEYOL62C.js} +77 -36
  55. package/dist/{chunk-QTS66GMR.js → chunk-CWEZEPD4.js} +3 -3
  56. package/dist/chunk-DIB4GX3G.js +8 -0
  57. package/dist/{chunk-OXE5ZUTS.js → chunk-EW5MLKT5.js} +10 -75
  58. package/dist/{chunk-42WRQLJ6.js → chunk-F3B2YJ76.js} +0 -14
  59. package/dist/chunk-FA6POGLH.js +70 -0
  60. package/dist/{chunk-ZRSUCJGA.js → chunk-FHC4JUSF.js} +1 -1
  61. package/dist/{chunk-6Y5L4BOM.js → chunk-FXOYURO4.js} +5 -3
  62. package/dist/{chunk-UKGNSJQQ.js → chunk-G36V2YGB.js} +108 -82
  63. package/dist/{chunk-K5BXPM74.js → chunk-G5SX4UPR.js} +3 -43
  64. package/dist/chunk-HDSDCEPK.js +80 -0
  65. package/dist/{chunk-LLKDNPYV.js → chunk-HWCQRGLL.js} +37 -3
  66. package/dist/{chunk-C4MZ3GP5.js → chunk-HZF5P5UN.js} +1 -1
  67. package/dist/chunk-LYA2AB5G.js +1914 -0
  68. package/dist/{chunk-36UJA3OD.js → chunk-NVUQSFWX.js} +2 -2
  69. package/dist/chunk-OB42YVAE.js +16 -0
  70. package/dist/{chunk-4VKFQPCW.js → chunk-PVZHG2XZ.js} +4 -4
  71. package/dist/{chunk-YJ44UT7D.js → chunk-RAVKCKXA.js} +7 -7
  72. package/dist/{chunk-RCBUZGIT.js → chunk-SYJT7AKB.js} +23 -20
  73. package/dist/{chunk-NP7TYFYR.js → chunk-TVE25LPW.js} +1 -1
  74. package/dist/chunk-W3U3YQYI.js +71 -0
  75. package/dist/chunk-YCLO7D7M.js +4267 -0
  76. package/dist/cli/chat.js +72 -40
  77. package/dist/cli/check.js +10 -8
  78. package/dist/cli/init.js +1 -0
  79. package/dist/cli/plugins.js +16 -14
  80. package/dist/cli/snapshot-warmup.js +13 -11
  81. package/dist/cli/upgrade.js +10 -8
  82. package/dist/{db-2ASK6MV6.js → db-KBSJHAZU.js} +8 -7
  83. package/dist/handlers/sandbox-egress-signals.d.ts +4 -0
  84. package/dist/instrumentation.js +72 -4
  85. package/dist/nitro.js +2 -2
  86. package/dist/oauth-callback-server-EYRMJTCS.js +231 -0
  87. package/dist/oauth-relay-3ZU72UGL.js +9 -0
  88. package/dist/reporting-schema.d.ts +12 -4
  89. package/dist/reporting.d.ts +5 -5
  90. package/dist/{runner-L363K62Z.js → runner-RQGZ5Z2S.js} +139 -72
  91. package/dist/sandbox-egress-signals-CDFAP67O.js +75 -0
  92. package/dist/{skills-VCPG3NOO.js → skills-JJQIE3BT.js} +10 -8
  93. package/dist/validation-MZ47PZP5.js +21 -0
  94. package/dist/version.js +1 -1
  95. package/package.json +5 -5
  96. package/dist/catalog-runtime-6XYOUVRZ.js +0 -16
  97. package/dist/chat/oauth-authorization-message.d.ts +0 -6
  98. package/dist/validation-GY7AJBLC.js +0 -19
@@ -1,3 +1,31 @@
1
+ import {
2
+ authorizationId
3
+ } from "./chunk-DIB4GX3G.js";
4
+ import {
5
+ RetryableDeliveryError,
6
+ actorFromRouting,
7
+ addReactionToMessage,
8
+ assertRunRoutingConsistency,
9
+ createMcpOAuthClientProvider,
10
+ createUserTokenStore,
11
+ deleteMcpAuthSession,
12
+ deliverOAuthAuthorization,
13
+ formatProviderLabel,
14
+ getMcpAuthSession,
15
+ getSlackMessagePermalink,
16
+ hasEgressCredentialHooks,
17
+ issuePluginCredential,
18
+ issueProviderCredentialLease,
19
+ onPluginEgressResponse,
20
+ patchMcpAuthSession,
21
+ resolveBaseUrl,
22
+ selectPluginGrant,
23
+ slackOutputPolicy,
24
+ startOAuthFlow,
25
+ surfaceFromRouting,
26
+ toolInvocationDestination,
27
+ uploadFilesToConversation
28
+ } from "./chunk-LYA2AB5G.js";
1
29
  import {
2
30
  CooperativeTurnYieldError,
3
31
  JUNIOR_THREAD_STATE_TTL_MS,
@@ -9,7 +37,7 @@ import {
9
37
  estimateTextTokens,
10
38
  extractAssistantText,
11
39
  getAgentContextCompactionTriggerTokens,
12
- getInterruptionMarker,
40
+ getAgentContextInputLimitTokens,
13
41
  getPiMessageRole,
14
42
  getTerminalAssistantMessages,
15
43
  hasRuntimeTurnContext,
@@ -20,6 +48,7 @@ import {
20
48
  isTurnInputCommitLostError,
21
49
  loadConnectedMcpProviders,
22
50
  loadConversationProjection,
51
+ loadTurnRoute,
23
52
  loadTurnSessionRecord,
24
53
  mergeArtifactsState,
25
54
  normalizeToolNameFromResult,
@@ -31,11 +60,18 @@ import {
31
60
  recordAuthorizationRequested,
32
61
  recordMcpProviderConnected,
33
62
  recordToolExecutionStarted,
63
+ recordTurnRoute,
34
64
  retainRuntimeTurnContext,
35
65
  stripRuntimeTurnContext,
36
66
  trimTrailingAssistantMessages,
37
67
  updateConversationStats
38
- } from "./chunk-DXRPHBOU.js";
68
+ } from "./chunk-CEYOL62C.js";
69
+ import {
70
+ parseSandboxEgressAuthRequiredSignal,
71
+ parseSandboxEgressPermissionDeniedSignal,
72
+ sandboxEgressCredentialContextSchema,
73
+ sandboxEgressCredentialLeaseSchema
74
+ } from "./chunk-HDSDCEPK.js";
39
75
  import {
40
76
  buildNonInteractiveShellScript,
41
77
  createSandboxSession,
@@ -52,7 +88,7 @@ import {
52
88
  runNonInteractiveCommand,
53
89
  throwSandboxOperationError,
54
90
  wrapSandboxSetupError
55
- } from "./chunk-4VKFQPCW.js";
91
+ } from "./chunk-PVZHG2XZ.js";
56
92
  import {
57
93
  addAgentTurnUsage,
58
94
  hasAgentTurnUsage
@@ -62,43 +98,37 @@ import {
62
98
  } from "./chunk-UIE3R5XU.js";
63
99
  import {
64
100
  createPluginHookRunner,
65
- createPluginLogger,
66
101
  getPluginSystemPromptContributions,
67
102
  getPluginTools,
68
103
  getPluginUserPromptContributions,
69
- getPlugins,
70
104
  getSlackToolContext,
71
105
  resolveChannelCapabilities
72
- } from "./chunk-YJ44UT7D.js";
106
+ } from "./chunk-RAVKCKXA.js";
73
107
  import {
74
108
  SANDBOX_DATA_ROOT,
75
109
  SANDBOX_SKILLS_ROOT,
76
110
  SANDBOX_WORKSPACE_ROOT,
77
- acquireActiveLock,
78
111
  getStateAdapter,
79
112
  sandboxSkillDir,
80
113
  sandboxSkillFile
81
- } from "./chunk-HMRVILWZ.js";
114
+ } from "./chunk-67CVNDJ7.js";
82
115
  import {
83
116
  SlackActionError,
84
117
  downloadPrivateSlackFile,
85
118
  getFilePermalink,
86
119
  getSlackClient,
87
120
  isConversationScopedChannel,
88
- isDmChannel,
89
121
  normalizeSlackConversationId,
90
122
  withSlackRetries
91
- } from "./chunk-QTS66GMR.js";
123
+ } from "./chunk-CWEZEPD4.js";
92
124
  import {
93
125
  contextProvenance,
94
126
  getConversationEventStore,
95
127
  getConversationSearchStore,
96
128
  getConversationStore,
97
- getDb,
98
129
  instructionActors,
99
- instructionProvenanceFor,
100
- parseDestination
101
- } from "./chunk-LLKDNPYV.js";
130
+ instructionProvenanceFor
131
+ } from "./chunk-HWCQRGLL.js";
102
132
  import {
103
133
  sleep
104
134
  } from "./chunk-4ZNGQH7C.js";
@@ -126,11 +156,12 @@ import {
126
156
  modelIdForProfile,
127
157
  modelProfileSchema,
128
158
  normalizeSlackEmojiName,
159
+ parseTurnReasoningLevel,
129
160
  profileConfig,
130
161
  renderCurrentInstruction,
131
162
  resolveGatewayModel,
132
163
  unwrapCurrentInstruction
133
- } from "./chunk-RCBUZGIT.js";
164
+ } from "./chunk-SYJT7AKB.js";
134
165
  import {
135
166
  parseSlackThreadId,
136
167
  resolveConversationPrivacy,
@@ -140,26 +171,27 @@ import {
140
171
  toGenAiMessagesTraceAttributes,
141
172
  toGenAiPayloadMetadata,
142
173
  toGenAiPayloadTraceAttributes
143
- } from "./chunk-6Y5L4BOM.js";
174
+ } from "./chunk-FXOYURO4.js";
144
175
  import {
145
176
  discoverSkills,
146
177
  findSkillByName,
147
178
  loadSkillsByName,
148
179
  parseSkillInvocation
149
- } from "./chunk-4WZ7YKDF.js";
180
+ } from "./chunk-2QG4MZQ7.js";
150
181
  import {
151
182
  pluginCatalogRuntime
152
- } from "./chunk-LR2SKL5Y.js";
183
+ } from "./chunk-6FKLPVF3.js";
153
184
  import {
154
185
  CredentialUnavailableError,
155
- credentialContextSchema,
156
- credentialUserSubjectId,
157
186
  resolveAuthTokenPlaceholder,
158
187
  resolvePluginCommandEnv
159
- } from "./chunk-OXE5ZUTS.js";
188
+ } from "./chunk-EW5MLKT5.js";
189
+ import {
190
+ credentialUserSubjectId
191
+ } from "./chunk-W3U3YQYI.js";
160
192
  import {
161
193
  isUserActor
162
- } from "./chunk-C4MZ3GP5.js";
194
+ } from "./chunk-HZF5P5UN.js";
163
195
  import {
164
196
  extractGenAiUsageAttributes,
165
197
  extractGenAiUsageSummary,
@@ -177,13 +209,12 @@ import {
177
209
  summarizeMessageText,
178
210
  withLogContext,
179
211
  withSpan
180
- } from "./chunk-36UJA3OD.js";
212
+ } from "./chunk-NVUQSFWX.js";
181
213
  import {
182
- isRecord,
183
214
  parseSlackChannelId,
184
215
  parseSlackMessageTs,
185
216
  parseSlackUserId
186
- } from "./chunk-42WRQLJ6.js";
217
+ } from "./chunk-F3B2YJ76.js";
187
218
  import {
188
219
  startInactiveSpan,
189
220
  withActiveSpan
@@ -916,12 +947,12 @@ function sortJsonValue(value) {
916
947
  Object.entries(value).filter(([, item]) => item !== void 0).sort(([left], [right]) => left.localeCompare(right)).map(([key, item]) => [key, sortJsonValue(item)])
917
948
  );
918
949
  }
919
- function isRecord2(value) {
950
+ function isRecord(value) {
920
951
  return Boolean(value && typeof value === "object" && !Array.isArray(value));
921
952
  }
922
953
  function injectContinuationToolName(details, toolName) {
923
954
  const parsed = juniorToolResultSchema.parse(details);
924
- if (!isRecord2(parsed.continuation)) {
955
+ if (!isRecord(parsed.continuation)) {
925
956
  return parsed;
926
957
  }
927
958
  return juniorToolResultWithBoundContinuationSchema.parse({
@@ -1098,7 +1129,8 @@ function configuredTurnRoute(profile, reasoningLevel, source) {
1098
1129
  return {
1099
1130
  profile,
1100
1131
  reasoningLevel,
1101
- reason: `configured:${source}`
1132
+ reason: `configured:${source}`,
1133
+ source: source === "profile" ? "inherited" : "configured"
1102
1134
  };
1103
1135
  }
1104
1136
  async function selectTurnRoute(args) {
@@ -1158,7 +1190,7 @@ async function selectTurnRoute(args) {
1158
1190
  "gen_ai.request.reasoning.level_confidence": normalizedSelection.confidence
1159
1191
  } : {}
1160
1192
  });
1161
- return normalizedSelection;
1193
+ return { ...normalizedSelection, source: "router" };
1162
1194
  }
1163
1195
  );
1164
1196
  }
@@ -1204,20 +1236,37 @@ async function classifyTurn(args) {
1204
1236
  confidence: parsed.confidence,
1205
1237
  profile: STANDARD_MODEL_PROFILE,
1206
1238
  reasoningLevel: CLASSIFIER_FALLBACK_REASONING_LEVEL,
1207
- reason: `low_confidence_medium_default:${reason}`
1239
+ reason: `low_confidence_medium_default:${reason}`,
1240
+ source: "router"
1208
1241
  };
1209
1242
  }
1210
1243
  return {
1211
1244
  confidence: parsed.confidence,
1212
1245
  profile: parsed.profile,
1213
1246
  reasoningLevel: parsed.reasoning_level,
1214
- reason
1247
+ reason,
1248
+ source: "router"
1215
1249
  };
1216
- } catch {
1250
+ } catch (error) {
1251
+ logWarn(
1252
+ "turn_router_classifier_failed",
1253
+ {
1254
+ messageConversationId: args.metadata.threadId,
1255
+ destinationName: args.metadata.channelId,
1256
+ userId: args.metadata.actorId,
1257
+ runId: args.metadata.runId,
1258
+ modelId: args.fastModelId
1259
+ },
1260
+ {
1261
+ "exception.message": error instanceof Error ? error.message : String(error)
1262
+ },
1263
+ "Turn router classifier failed; using the default route"
1264
+ );
1217
1265
  return {
1218
1266
  profile: STANDARD_MODEL_PROFILE,
1219
1267
  reasoningLevel: CLASSIFIER_FALLBACK_REASONING_LEVEL,
1220
- reason: "classifier_error_default"
1268
+ reason: "classifier_error_default",
1269
+ source: "router"
1221
1270
  };
1222
1271
  }
1223
1272
  }
@@ -1277,69 +1326,6 @@ var AuthorizationFlowDisabledError = class extends Error {
1277
1326
  }
1278
1327
  };
1279
1328
 
1280
- // src/chat/agent/request.ts
1281
- var RetryableDeliveryError = class extends Error {
1282
- constructor(cause) {
1283
- super("Assistant delivery was transient or ambiguous", { cause });
1284
- this.name = "RetryableDeliveryError";
1285
- }
1286
- };
1287
- function actorFromRouting(routing) {
1288
- if (routing.dispatch?.actor) {
1289
- return routing.dispatch.actor;
1290
- }
1291
- if (routing.actor) {
1292
- return routing.actor;
1293
- }
1294
- if (routing.credentialContext && !("type" in routing.credentialContext.actor)) {
1295
- return routing.credentialContext.actor;
1296
- }
1297
- return void 0;
1298
- }
1299
- function assertRunRoutingConsistency(request) {
1300
- const { destination, source } = request.routing;
1301
- if (source.platform !== destination.platform) {
1302
- throw new TypeError("Run source and destination platforms do not match");
1303
- }
1304
- if (source.platform === "slack" && destination.platform === "slack") {
1305
- if (source.teamId !== destination.teamId) {
1306
- throw new TypeError("Slack source and destination teams do not match");
1307
- }
1308
- } else if (source.platform === "local" && destination.platform === "local" && (source.conversationId !== request.conversationId || destination.conversationId !== request.conversationId)) {
1309
- throw new TypeError(
1310
- "Local source, destination, and run conversation IDs do not match"
1311
- );
1312
- }
1313
- const actor = request.routing.dispatch?.actor ?? request.routing.actor;
1314
- if (!actor || actor.platform === "system") {
1315
- return;
1316
- }
1317
- if (actor.platform !== destination.platform) {
1318
- throw new TypeError(
1319
- `Actor platform "${actor.platform}" does not match destination platform "${destination.platform}"`
1320
- );
1321
- }
1322
- if (actor.platform === "slack" && destination.platform === "slack" && actor.teamId !== destination.teamId) {
1323
- throw new TypeError("Slack actor team does not match destination team");
1324
- }
1325
- }
1326
- function toolInvocationDestination(routing) {
1327
- if (routing.destination.platform !== "slack" || !routing.toolChannelId) {
1328
- return routing.destination;
1329
- }
1330
- return {
1331
- platform: "slack",
1332
- teamId: routing.destination.teamId,
1333
- channelId: routing.toolChannelId
1334
- };
1335
- }
1336
- function surfaceFromRouting(routing) {
1337
- if (routing.surface) {
1338
- return routing.surface;
1339
- }
1340
- return routing.source.platform === "slack" ? "slack" : "internal";
1341
- }
1342
-
1343
1329
  // src/chat/agent/session.ts
1344
1330
  async function restoreSessionRecord(args) {
1345
1331
  const sessionRecordState = await loadTurnSessionRecord({
@@ -1365,7 +1351,7 @@ function parseMcpProviderFromToolName(toolName) {
1365
1351
 
1366
1352
  // src/chat/pi/derived-state.ts
1367
1353
  var MCP_BRIDGE_TOOLS = /* @__PURE__ */ new Set(["callMcpTool", "searchMcpTools"]);
1368
- function isRecord3(value) {
1354
+ function isRecord2(value) {
1369
1355
  return Boolean(value && typeof value === "object" && !Array.isArray(value));
1370
1356
  }
1371
1357
  function providerFromToolName(value) {
@@ -1387,7 +1373,7 @@ function addBridgeToolProvider(toolName, value, providers) {
1387
1373
  if (bridgeTool === "searchMcpTools") {
1388
1374
  for (const argsKey of ["input", "args", "arguments", "params"]) {
1389
1375
  const args = value[argsKey];
1390
- if (isRecord3(args)) {
1376
+ if (isRecord2(args)) {
1391
1377
  addString(providers, args.provider);
1392
1378
  }
1393
1379
  }
@@ -1396,7 +1382,7 @@ function addBridgeToolProvider(toolName, value, providers) {
1396
1382
  if (bridgeTool === "callMcpTool") {
1397
1383
  for (const argsKey of ["input", "args", "arguments", "params"]) {
1398
1384
  const args = value[argsKey];
1399
- if (isRecord3(args)) {
1385
+ if (isRecord2(args)) {
1400
1386
  addString(providers, providerFromToolName(args.tool_name));
1401
1387
  }
1402
1388
  }
@@ -1409,18 +1395,18 @@ function addMcpResultProvider(message, providers) {
1409
1395
  return;
1410
1396
  }
1411
1397
  if (toolName === "loadSkill") {
1412
- if (isRecord3(message.details)) {
1398
+ if (isRecord2(message.details)) {
1413
1399
  addString(providers, message.details.mcp_provider);
1414
1400
  }
1415
1401
  addString(providers, message.mcp_provider);
1416
1402
  return;
1417
1403
  }
1418
1404
  if (toolName === "searchMcpTools") {
1419
- if (isRecord3(message.details)) {
1405
+ if (isRecord2(message.details)) {
1420
1406
  addString(providers, message.details.provider);
1421
1407
  if (Array.isArray(message.details.tools)) {
1422
1408
  for (const tool of message.details.tools) {
1423
- if (isRecord3(tool)) {
1409
+ if (isRecord2(tool)) {
1424
1410
  addString(providers, providerFromToolName(tool.tool_name));
1425
1411
  }
1426
1412
  }
@@ -1432,11 +1418,11 @@ function addMcpResultProvider(message, providers) {
1432
1418
  if (toolName === "callMcpTool") {
1433
1419
  for (const argsKey of ["input", "args", "arguments", "params"]) {
1434
1420
  const args = message[argsKey];
1435
- if (isRecord3(args)) {
1421
+ if (isRecord2(args)) {
1436
1422
  addString(providers, providerFromToolName(args.tool_name));
1437
1423
  }
1438
1424
  }
1439
- if (isRecord3(message.details)) {
1425
+ if (isRecord2(message.details)) {
1440
1426
  addString(providers, message.details.provider);
1441
1427
  addString(providers, providerFromToolName(message.details.tool_name));
1442
1428
  }
@@ -1444,7 +1430,7 @@ function addMcpResultProvider(message, providers) {
1444
1430
  }
1445
1431
  }
1446
1432
  function scanMcpProviders(message, providers) {
1447
- if (!isRecord3(message)) {
1433
+ if (!isRecord2(message)) {
1448
1434
  return;
1449
1435
  }
1450
1436
  if (message.role === "toolResult") {
@@ -1456,15 +1442,15 @@ function scanMcpProviders(message, providers) {
1456
1442
  return;
1457
1443
  }
1458
1444
  for (const part of content) {
1459
- if (!isRecord3(part)) {
1445
+ if (!isRecord2(part)) {
1460
1446
  continue;
1461
1447
  }
1462
1448
  addBridgeToolProvider(getToolName(part), part, providers);
1463
1449
  }
1464
1450
  }
1465
1451
  function scanLoadedSkills(message, skills) {
1466
- if (isRecord3(message) && message.role === "toolResult" && message.toolName === "loadSkill" && message.isError !== true) {
1467
- if (isRecord3(message.details)) {
1452
+ if (isRecord2(message) && message.role === "toolResult" && message.toolName === "loadSkill" && message.isError !== true) {
1453
+ if (isRecord2(message.details)) {
1468
1454
  addString(skills, message.details.skill_name);
1469
1455
  }
1470
1456
  addString(skills, message.skill_name);
@@ -1551,424 +1537,6 @@ import { isDeepStrictEqual } from "util";
1551
1537
  // src/chat/prompt.ts
1552
1538
  import fs2 from "fs";
1553
1539
  import path2 from "path";
1554
-
1555
- // src/chat/slack/status-format.ts
1556
- var SLACK_STATUS_MAX_LENGTH = 50;
1557
- function truncateStatusText(text) {
1558
- const trimmed = text.trim();
1559
- if (!trimmed) {
1560
- return "";
1561
- }
1562
- if (trimmed.length <= SLACK_STATUS_MAX_LENGTH) {
1563
- return trimmed;
1564
- }
1565
- return `${trimmed.slice(0, SLACK_STATUS_MAX_LENGTH - 3).trimEnd()}...`;
1566
- }
1567
-
1568
- // src/chat/slack/mrkdwn.ts
1569
- function escapeSlackMrkdwnText(text) {
1570
- return text.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;");
1571
- }
1572
- function escapeSlackLinkUrl(url) {
1573
- return url.replaceAll("&", "&amp;").replaceAll("<", "%3C").replaceAll(">", "%3E");
1574
- }
1575
- function formatSlackLink(url, label) {
1576
- return `<${escapeSlackLinkUrl(url)}|${escapeSlackMrkdwnText(label)}>`;
1577
- }
1578
- function readInlineCodeSpan(line, start) {
1579
- if (line[start] !== "`") {
1580
- return void 0;
1581
- }
1582
- let n = 1;
1583
- while (line[start + n] === "`") {
1584
- n++;
1585
- }
1586
- const marker = "`".repeat(n);
1587
- let search = start + n;
1588
- while (search < line.length) {
1589
- const close = line.indexOf(marker, search);
1590
- if (close === -1) {
1591
- return void 0;
1592
- }
1593
- const after = close + n;
1594
- if (line[after] !== "`") {
1595
- return { text: line.slice(start, after), end: after };
1596
- }
1597
- search = after + 1;
1598
- }
1599
- return void 0;
1600
- }
1601
- function readExistingSlackAngleToken(line, start) {
1602
- if (line[start] !== "<") {
1603
- return void 0;
1604
- }
1605
- const close = line.indexOf(">", start + 1);
1606
- if (close === -1) {
1607
- return void 0;
1608
- }
1609
- const body = line.slice(start + 1, close);
1610
- if (/^(?:https?:\/\/|@|#|!)/.test(body)) {
1611
- return { text: line.slice(start, close + 1), end: close + 1 };
1612
- }
1613
- return void 0;
1614
- }
1615
- function readMarkdownLink(line, start) {
1616
- if (line[start] !== "[") {
1617
- return void 0;
1618
- }
1619
- const labelEnd = line.indexOf("](", start + 1);
1620
- if (labelEnd === -1) {
1621
- return void 0;
1622
- }
1623
- const destStart = labelEnd + 2;
1624
- if (!line.startsWith("http://", destStart) && !line.startsWith("https://", destStart)) {
1625
- return void 0;
1626
- }
1627
- const closeParens = line.indexOf(")", destStart);
1628
- if (closeParens === -1) {
1629
- return void 0;
1630
- }
1631
- return { text: line.slice(start, closeParens + 1), end: closeParens + 1 };
1632
- }
1633
- function hasUnmatchedClosingParen(text) {
1634
- let balance = 0;
1635
- for (const ch of text) {
1636
- if (ch === "(") balance++;
1637
- else if (ch === ")") balance--;
1638
- }
1639
- return balance < 0;
1640
- }
1641
- function readBareUrl(line, start) {
1642
- let end = start;
1643
- while (end < line.length) {
1644
- const ch = line[end];
1645
- if (/\s/.test(ch) || ch === "<" || ch === ">" || ch === '"' || ch === "`" || ch === "|" || ch === "*") {
1646
- break;
1647
- }
1648
- end++;
1649
- }
1650
- if (end === start) {
1651
- return void 0;
1652
- }
1653
- let raw = line.slice(start, end);
1654
- let suffix = "";
1655
- const peel = () => {
1656
- suffix = raw.slice(-1) + suffix;
1657
- raw = raw.slice(0, -1);
1658
- };
1659
- const shouldPeel = () => raw.endsWith("_") || /[.,!?;:]$/.test(raw) || raw.endsWith(")") && hasUnmatchedClosingParen(raw);
1660
- while (raw.length > 0 && shouldPeel()) {
1661
- peel();
1662
- }
1663
- if (!/^https?:\/\/.+/.test(raw)) {
1664
- return void 0;
1665
- }
1666
- return { url: raw, suffix, end };
1667
- }
1668
- function wrapBareUrlsOnLine(line) {
1669
- let result = "";
1670
- let i = 0;
1671
- while (i < line.length) {
1672
- const codeSpan = readInlineCodeSpan(line, i);
1673
- if (codeSpan) {
1674
- result += codeSpan.text;
1675
- i = codeSpan.end;
1676
- continue;
1677
- }
1678
- const angleToken = readExistingSlackAngleToken(line, i);
1679
- if (angleToken) {
1680
- result += angleToken.text;
1681
- i = angleToken.end;
1682
- continue;
1683
- }
1684
- const mdLink = readMarkdownLink(line, i);
1685
- if (mdLink) {
1686
- result += mdLink.text;
1687
- i = mdLink.end;
1688
- continue;
1689
- }
1690
- if (line.startsWith("https://", i) || line.startsWith("http://", i)) {
1691
- const parsed = readBareUrl(line, i);
1692
- if (parsed) {
1693
- result += `<${parsed.url}>${parsed.suffix}`;
1694
- i = parsed.end;
1695
- continue;
1696
- }
1697
- }
1698
- result += line[i];
1699
- i++;
1700
- }
1701
- return result;
1702
- }
1703
- function wrapBareUrls(text) {
1704
- const lines = text.split("\n");
1705
- const out = [];
1706
- let inCodeBlock = false;
1707
- for (const line of lines) {
1708
- if (line.trimStart().startsWith("```")) {
1709
- inCodeBlock = !inCodeBlock;
1710
- out.push(line);
1711
- continue;
1712
- }
1713
- out.push(inCodeBlock ? line : wrapBareUrlsOnLine(line));
1714
- }
1715
- return out.join("\n");
1716
- }
1717
- function ensureBlockSpacing(text) {
1718
- const codeBlockPattern = /^```/;
1719
- const listItemPattern = /^[-*•]\s|^\d+\.\s/;
1720
- const lines = text.split("\n");
1721
- const result = [];
1722
- let inCodeBlock = false;
1723
- for (let i = 0; i < lines.length; i++) {
1724
- const line = lines[i];
1725
- const isCodeFence = codeBlockPattern.test(line.trimStart());
1726
- if (isCodeFence) {
1727
- if (!inCodeBlock) {
1728
- const prev2 = result.length > 0 ? result[result.length - 1] : void 0;
1729
- if (prev2 !== void 0 && prev2.trim() !== "") {
1730
- result.push("");
1731
- }
1732
- }
1733
- inCodeBlock = !inCodeBlock;
1734
- result.push(line);
1735
- continue;
1736
- }
1737
- if (inCodeBlock) {
1738
- result.push(line);
1739
- continue;
1740
- }
1741
- const prev = result.length > 0 ? result[result.length - 1] : void 0;
1742
- if (prev !== void 0 && prev.trim() !== "" && line.trim() !== "" && !(listItemPattern.test(prev.trimStart()) && listItemPattern.test(line.trimStart()))) {
1743
- result.push("");
1744
- }
1745
- result.push(line);
1746
- }
1747
- return result.join("\n");
1748
- }
1749
- function normalizeSlackReplyMarkdown(text) {
1750
- let normalized = text.replace(/\r\n?/g, "\n").replace(/[ \t]+$/gm, "");
1751
- normalized = wrapBareUrls(normalized);
1752
- normalized = ensureBlockSpacing(normalized);
1753
- return normalized.replace(/\n{3,}/g, "\n\n").trim();
1754
- }
1755
- function normalizeSlackStatusText(text) {
1756
- const trimmed = text.trim();
1757
- if (!trimmed) {
1758
- return "";
1759
- }
1760
- return truncateStatusText(trimmed.replace(/(?:\.\s*)+$/, "").trim());
1761
- }
1762
-
1763
- // src/chat/slack/output.ts
1764
- var MAX_INLINE_CHARS = 2200;
1765
- var MAX_INLINE_LINES = 45;
1766
- var CONTINUED_MARKER = "\n\n[Continued below]";
1767
- function countSlackLines(text) {
1768
- if (!text) {
1769
- return 0;
1770
- }
1771
- return text.split("\n").length;
1772
- }
1773
- function fitsInlineBudget(text, maxChars = MAX_INLINE_CHARS, maxLines = MAX_INLINE_LINES) {
1774
- return text.length <= maxChars && countSlackLines(text) <= maxLines;
1775
- }
1776
- function findSplitIndex(text, maxChars) {
1777
- if (text.length <= maxChars) {
1778
- return text.length;
1779
- }
1780
- const bounded = text.slice(0, maxChars);
1781
- const newlineIndex = bounded.lastIndexOf("\n");
1782
- if (newlineIndex > 0) {
1783
- return newlineIndex;
1784
- }
1785
- const spaceIndex = bounded.lastIndexOf(" ");
1786
- if (spaceIndex > 0) {
1787
- return spaceIndex;
1788
- }
1789
- return maxChars;
1790
- }
1791
- function splitByLineBudget(text, maxLines) {
1792
- if (maxLines <= 0) {
1793
- return "";
1794
- }
1795
- const lines = text.split("\n");
1796
- if (lines.length <= maxLines) {
1797
- return text;
1798
- }
1799
- return lines.slice(0, maxLines).join("\n");
1800
- }
1801
- function reserveInlineBudgetForSuffix(suffix, maxChars = MAX_INLINE_CHARS, maxLines = MAX_INLINE_LINES) {
1802
- return {
1803
- maxChars: Math.max(1, maxChars - suffix.length),
1804
- maxLines: Math.max(1, maxLines - Math.max(0, countSlackLines(suffix) - 1))
1805
- };
1806
- }
1807
- function forceSplitBudget(text, budget) {
1808
- const lineCount = countSlackLines(text);
1809
- return {
1810
- maxChars: text.length <= budget.maxChars ? Math.max(1, text.length - 1) : budget.maxChars,
1811
- maxLines: lineCount <= budget.maxLines ? Math.max(1, lineCount - 1) : budget.maxLines
1812
- };
1813
- }
1814
- function getFenceContinuation(text) {
1815
- let open;
1816
- for (const line of text.split("\n")) {
1817
- const trimmed = line.trimStart();
1818
- const openerMatch = trimmed.match(/^(`{3,}|~{3,})(.*)$/);
1819
- if (!openerMatch) {
1820
- continue;
1821
- }
1822
- if (!open) {
1823
- open = {
1824
- fence: openerMatch[1],
1825
- openerLine: trimmed
1826
- };
1827
- continue;
1828
- }
1829
- if (new RegExp(`^${open.fence}\\s*$`).test(trimmed)) {
1830
- open = void 0;
1831
- }
1832
- }
1833
- if (!open) {
1834
- return null;
1835
- }
1836
- return {
1837
- closeSuffix: text.endsWith("\n") ? open.fence : `
1838
- ${open.fence}`,
1839
- reopenPrefix: `${open.openerLine}
1840
- `
1841
- };
1842
- }
1843
- function appendSlackSuffix(text, marker) {
1844
- const carryover = getFenceContinuation(text);
1845
- return `${text}${carryover?.closeSuffix ?? ""}${marker}`;
1846
- }
1847
- function stripTrailingContinuationMarker(text) {
1848
- return text.endsWith(CONTINUED_MARKER) ? text.slice(0, -CONTINUED_MARKER.length) : text;
1849
- }
1850
- function takeSlackContinuationChunk(text, budget) {
1851
- let { prefix, rest } = takeSlackInlinePrefix(text, budget);
1852
- if (!rest) {
1853
- ({ prefix, rest } = takeSlackInlinePrefix(
1854
- text,
1855
- forceSplitBudget(text, budget)
1856
- ));
1857
- }
1858
- let carryover = rest ? getFenceContinuation(prefix) : null;
1859
- if (!carryover) {
1860
- return { prefix, rest };
1861
- }
1862
- const carryoverBudget = reserveInlineBudgetForSuffix(
1863
- `${carryover.closeSuffix}${CONTINUED_MARKER}`
1864
- );
1865
- ({ prefix, rest } = takeSlackInlinePrefix(text, carryoverBudget));
1866
- if (!rest) {
1867
- ({ prefix, rest } = takeSlackInlinePrefix(
1868
- text,
1869
- forceSplitBudget(text, carryoverBudget)
1870
- ));
1871
- }
1872
- carryover = rest ? getFenceContinuation(prefix) : null;
1873
- if (!carryover) {
1874
- return { prefix, rest };
1875
- }
1876
- return {
1877
- prefix,
1878
- rest: `${carryover.reopenPrefix}${rest}`
1879
- };
1880
- }
1881
- function takeSlackContinuationPrefix(text, options) {
1882
- const budget = {
1883
- maxChars: options?.maxChars ?? getSlackContinuationBudget().maxChars,
1884
- maxLines: options?.maxLines ?? getSlackContinuationBudget().maxLines
1885
- };
1886
- const { prefix, rest } = (() => {
1887
- if (options?.forceSplit) {
1888
- return takeSlackContinuationChunk(text, budget);
1889
- }
1890
- const initial = takeSlackInlinePrefix(text, budget);
1891
- return initial.rest ? takeSlackContinuationChunk(text, budget) : initial;
1892
- })();
1893
- return {
1894
- prefix,
1895
- renderedPrefix: rest ? appendSlackSuffix(prefix, CONTINUED_MARKER) : prefix,
1896
- rest
1897
- };
1898
- }
1899
- function takeSlackInlinePrefix(text, options) {
1900
- const maxChars = options?.maxChars ?? MAX_INLINE_CHARS;
1901
- const maxLines = options?.maxLines ?? MAX_INLINE_LINES;
1902
- const normalized = text.replace(/\r\n?/g, "\n");
1903
- if (!normalized) {
1904
- return { prefix: "", rest: "" };
1905
- }
1906
- if (fitsInlineBudget(normalized, maxChars, maxLines)) {
1907
- return { prefix: normalized, rest: "" };
1908
- }
1909
- const lineBounded = splitByLineBudget(normalized, maxLines);
1910
- const cutIndex = findSplitIndex(lineBounded, maxChars);
1911
- const prefix = lineBounded.slice(0, cutIndex).trimEnd();
1912
- if (prefix) {
1913
- return {
1914
- prefix,
1915
- rest: normalized.slice(prefix.length).trimStart()
1916
- };
1917
- }
1918
- const hardPrefix = normalized.slice(0, Math.max(1, maxChars)).trimEnd();
1919
- return {
1920
- prefix: hardPrefix || normalized.slice(0, Math.max(1, maxChars)),
1921
- rest: normalized.slice(hardPrefix.length || Math.max(1, maxChars)).trimStart()
1922
- };
1923
- }
1924
- function splitSlackReplyText(text, options) {
1925
- const normalized = normalizeSlackReplyMarkdown(text);
1926
- if (!normalized) {
1927
- return [];
1928
- }
1929
- const chunks = [];
1930
- const continuationBudget = reserveInlineBudgetForSuffix(CONTINUED_MARKER);
1931
- let remaining = normalized;
1932
- while (remaining) {
1933
- const fitsFinalChunk = options?.interrupted ? fitsInlineBudget(appendSlackSuffix(remaining, getInterruptionMarker())) : fitsInlineBudget(remaining);
1934
- if (fitsFinalChunk) {
1935
- chunks.push(
1936
- options?.interrupted ? appendSlackSuffix(remaining, getInterruptionMarker()) : remaining
1937
- );
1938
- break;
1939
- }
1940
- const { renderedPrefix, rest } = takeSlackContinuationPrefix(remaining, {
1941
- ...continuationBudget,
1942
- forceSplit: true
1943
- });
1944
- chunks.push(renderedPrefix);
1945
- remaining = rest;
1946
- }
1947
- if (chunks.length === 2) {
1948
- chunks[0] = stripTrailingContinuationMarker(chunks[0] ?? "");
1949
- }
1950
- return chunks;
1951
- }
1952
- function getSlackContinuationBudget() {
1953
- return reserveInlineBudgetForSuffix(CONTINUED_MARKER);
1954
- }
1955
- function buildSlackOutputMessage(text) {
1956
- const normalized = normalizeSlackReplyMarkdown(text);
1957
- if (!normalized) {
1958
- throw new Error(
1959
- `Slack output normalized to empty content: original_length=${text.length} parsed_length=${normalized.length}`
1960
- );
1961
- }
1962
- return {
1963
- markdown: normalized
1964
- };
1965
- }
1966
- var slackOutputPolicy = {
1967
- maxInlineChars: MAX_INLINE_CHARS,
1968
- maxInlineLines: MAX_INLINE_LINES
1969
- };
1970
-
1971
- // src/chat/prompt.ts
1972
1540
  var DEFAULT_SOUL = "You are Junior, a practical and concise assistant.";
1973
1541
  function getLoggedMarkdownFiles() {
1974
1542
  const globalState = globalThis;
@@ -2802,160 +2370,6 @@ async function assemblePrompt(args) {
2802
2370
  };
2803
2371
  }
2804
2372
 
2805
- // src/chat/capabilities/catalog.ts
2806
- var cachedCatalog;
2807
- function getCapabilityCatalog() {
2808
- const signature = pluginCatalogRuntime.getSignature();
2809
- if (cachedCatalog?.signature === signature) return cachedCatalog;
2810
- const providers = pluginCatalogRuntime.getCapabilityProviders();
2811
- const capabilityToProvider = /* @__PURE__ */ new Map();
2812
- for (const provider of providers) {
2813
- for (const capability of provider.capabilities) {
2814
- if (capabilityToProvider.has(capability)) {
2815
- throw new Error(
2816
- `Duplicate capability registration for "${capability}"`
2817
- );
2818
- }
2819
- capabilityToProvider.set(capability, provider);
2820
- }
2821
- }
2822
- cachedCatalog = { signature, providers, capabilityToProvider };
2823
- return cachedCatalog;
2824
- }
2825
- var catalogLogged = false;
2826
- function logCapabilityCatalogLoadedOnce() {
2827
- if (catalogLogged) return;
2828
- catalogLogged = true;
2829
- const { providers } = getCapabilityCatalog();
2830
- const capabilityNames = providers.flatMap((p) => p.capabilities).sort();
2831
- const configKeys = [
2832
- ...new Set(providers.flatMap((p) => p.configKeys))
2833
- ].sort();
2834
- logInfo(
2835
- "capability_catalog_loaded",
2836
- {},
2837
- {
2838
- "app.capability.providers": providers.map((p) => p.provider),
2839
- "app.capability.count": capabilityNames.length,
2840
- "app.capability.names": capabilityNames,
2841
- "app.config.key_count": configKeys.length,
2842
- "app.config.keys": configKeys
2843
- },
2844
- "Loaded capability provider catalog"
2845
- );
2846
- }
2847
-
2848
- // src/chat/capabilities/router.ts
2849
- var ProviderCredentialRouter = class {
2850
- brokersByProvider;
2851
- constructor(input) {
2852
- this.brokersByProvider = input.brokersByProvider;
2853
- }
2854
- async issue(input) {
2855
- const broker = this.brokersByProvider[input.provider];
2856
- if (!broker) {
2857
- throw new Error(
2858
- `No credential broker registered for provider: ${input.provider}`
2859
- );
2860
- }
2861
- return await broker.issue({
2862
- context: input.context,
2863
- reason: input.reason
2864
- });
2865
- }
2866
- };
2867
-
2868
- // src/chat/credentials/user-token-store.ts
2869
- import {
2870
- pluginStoredTokensSchema
2871
- } from "@sentry/junior-plugin-api";
2872
- var storedTokensSchema = pluginStoredTokensSchema;
2873
-
2874
- // src/chat/credentials/state-adapter-token-store.ts
2875
- var KEY_PREFIX = "oauth-token";
2876
- var BUFFER_MS = 24 * 60 * 60 * 1e3;
2877
- var LONG_LIVED_TTL_MS = 365 * 24 * 60 * 60 * 1e3;
2878
- var REFRESH_LOCK_WAIT_MS = 3e4;
2879
- var REFRESH_LOCK_RETRY_MS = 100;
2880
- function tokenKey(userId, provider) {
2881
- return `${KEY_PREFIX}:${userId}:${provider}`;
2882
- }
2883
- function refreshLockKey(userId, provider) {
2884
- return `${tokenKey(userId, provider)}:refresh`;
2885
- }
2886
- var StateAdapterTokenStore = class {
2887
- state;
2888
- constructor(stateAdapter) {
2889
- this.state = stateAdapter;
2890
- }
2891
- async get(userId, provider) {
2892
- const stored = await this.state.get(tokenKey(userId, provider));
2893
- return stored === null || stored === void 0 ? void 0 : storedTokensSchema.parse(stored);
2894
- }
2895
- async set(userId, provider, tokens) {
2896
- const parsed = storedTokensSchema.parse(tokens);
2897
- const expiresAt = parsed.refreshTokenExpiresAt ?? parsed.expiresAt;
2898
- const ttlMs2 = expiresAt ? Math.max(expiresAt - Date.now() + BUFFER_MS, BUFFER_MS) : LONG_LIVED_TTL_MS;
2899
- await this.state.set(tokenKey(userId, provider), parsed, ttlMs2);
2900
- }
2901
- async delete(userId, provider) {
2902
- await this.state.delete(tokenKey(userId, provider));
2903
- }
2904
- /** Wait for the per-slot refresh gate so rotated refresh tokens are used once. */
2905
- async withRefresh(userId, provider, callback) {
2906
- const lockKey = refreshLockKey(userId, provider);
2907
- const deadline = Date.now() + REFRESH_LOCK_WAIT_MS;
2908
- while (true) {
2909
- const lock = await acquireActiveLock(this.state, lockKey);
2910
- if (lock) {
2911
- try {
2912
- return await callback();
2913
- } finally {
2914
- await this.state.releaseLock(lock);
2915
- }
2916
- }
2917
- if (Date.now() >= deadline) {
2918
- throw new Error(`Could not acquire OAuth token refresh lock`);
2919
- }
2920
- await sleep(REFRESH_LOCK_RETRY_MS);
2921
- }
2922
- }
2923
- };
2924
-
2925
- // src/chat/capabilities/factory.ts
2926
- var sandboxEgressRouters = /* @__PURE__ */ new WeakMap();
2927
- function createUserTokenStore() {
2928
- return new StateAdapterTokenStore(getStateAdapter());
2929
- }
2930
- function createProviderCredentialRouter(userTokenStore) {
2931
- logCapabilityCatalogLoadedOnce();
2932
- const brokersByProvider = {};
2933
- for (const plugin of pluginCatalogRuntime.getProviders()) {
2934
- const { name } = plugin.manifest;
2935
- if (!plugin.manifest.credentials && !plugin.manifest.apiHeaders) {
2936
- continue;
2937
- }
2938
- brokersByProvider[name] = pluginCatalogRuntime.createBroker(name, {
2939
- userTokenStore
2940
- });
2941
- }
2942
- return new ProviderCredentialRouter({ brokersByProvider });
2943
- }
2944
- function getSandboxEgressRouter() {
2945
- const stateAdapter = getStateAdapter();
2946
- let router = sandboxEgressRouters.get(stateAdapter);
2947
- if (!router) {
2948
- router = createProviderCredentialRouter(
2949
- new StateAdapterTokenStore(stateAdapter)
2950
- );
2951
- sandboxEgressRouters.set(stateAdapter, router);
2952
- }
2953
- return router;
2954
- }
2955
- async function issueProviderCredentialLease(input) {
2956
- return await getSandboxEgressRouter().issue(input);
2957
- }
2958
-
2959
2373
  // src/chat/capabilities/jr-rpc-command.ts
2960
2374
  import { Bash, defineCommand } from "just-bash";
2961
2375
  function commandResult(input) {
@@ -3185,672 +2599,137 @@ ${usage}
3185
2599
  }))
3186
2600
  },
3187
2601
  exitCode: 0
3188
- });
3189
- }
3190
- return commandResult({
3191
- stderr: `Usage:
3192
- ${usage}
3193
- `,
3194
- exitCode: 2
3195
- });
3196
- }
3197
- async function handlePluginsCommand(args) {
3198
- const usage = "jr-rpc plugins list";
3199
- const subverb = (args[0] ?? "").trim();
3200
- if (subverb !== "list" || args.length !== 1) {
3201
- return commandResult({
3202
- stderr: `Usage:
3203
- ${usage}
3204
- `,
3205
- exitCode: 2
3206
- });
3207
- }
3208
- const plugins = pluginCatalogRuntime.getProviders().map((plugin) => ({
3209
- name: plugin.manifest.name,
3210
- displayName: plugin.manifest.displayName,
3211
- description: plugin.manifest.description,
3212
- capabilities: [...plugin.manifest.capabilities],
3213
- configKeys: [...plugin.manifest.configKeys]
3214
- })).sort((left, right) => left.name.localeCompare(right.name));
3215
- return commandResult({
3216
- stdout: {
3217
- ok: true,
3218
- plugins
3219
- },
3220
- exitCode: 0
3221
- });
3222
- }
3223
- function createJrRpcCommand(deps) {
3224
- return defineCommand("jr-rpc", async (args) => {
3225
- const usage = [
3226
- "jr-rpc config get <key>",
3227
- "jr-rpc config set <key> <value> [--json]",
3228
- "jr-rpc config unset <key>",
3229
- "jr-rpc config list [--prefix <value>]",
3230
- "jr-rpc plugins list"
3231
- ].join("\n");
3232
- const verb = (args[0] ?? "").trim();
3233
- if (verb === "config") {
3234
- return handleConfigCommand(args.slice(1), deps);
3235
- }
3236
- if (verb === "plugins") {
3237
- return handlePluginsCommand(args.slice(1));
3238
- }
3239
- return commandResult({
3240
- stderr: `Unsupported jr-rpc command. Use:
3241
- ${usage}
3242
- `,
3243
- exitCode: 2
3244
- });
3245
- });
3246
- }
3247
- async function maybeExecuteJrRpcCustomCommand(command, deps) {
3248
- const normalized = command.trim();
3249
- if (!/^jr-rpc(?:\s|$)/.test(normalized)) {
3250
- return { handled: false };
3251
- }
3252
- const shell = new Bash({
3253
- customCommands: [createJrRpcCommand(deps)]
3254
- });
3255
- const execResult = await shell.exec(normalized);
3256
- return {
3257
- handled: true,
3258
- result: {
3259
- ok: execResult.exitCode === 0,
3260
- command: normalized,
3261
- cwd: "/",
3262
- exit_code: execResult.exitCode,
3263
- signal: null,
3264
- timed_out: false,
3265
- stdout: execResult.stdout,
3266
- stderr: execResult.stderr,
3267
- stdout_truncated: false,
3268
- stderr_truncated: false
3269
- }
3270
- };
3271
- }
3272
-
3273
- // src/chat/sandbox/command-result.ts
3274
- function formatSandboxCommandResult(params) {
3275
- return makeStructuredToolResult({
3276
- ok: params.ok,
3277
- status: params.ok ? "success" : "error",
3278
- target: params.command,
3279
- data: {
3280
- command: params.command,
3281
- cwd: params.cwd,
3282
- exit_code: params.exit_code,
3283
- signal: params.signal,
3284
- timed_out: params.timed_out,
3285
- aborted: Boolean(params.aborted),
3286
- stdout: params.stdout,
3287
- stderr: params.stderr,
3288
- stdout_truncated: params.stdout_truncated,
3289
- stderr_truncated: params.stderr_truncated
3290
- },
3291
- truncated: params.stdout_truncated || params.stderr_truncated,
3292
- ...!params.ok ? {
3293
- error: {
3294
- kind: params.aborted ? "outcome_unknown" : params.timed_out ? "timeout" : "nonzero_exit",
3295
- message: params.aborted ? "Command was interrupted before its outcome was confirmed. It may have produced side effects; reconcile external state before retrying or reporting failure." : params.stderr.trim() || `Command exited with code ${params.exit_code}`,
3296
- retryable: params.timed_out
3297
- }
3298
- } : {},
3299
- command: params.command,
3300
- cwd: params.cwd,
3301
- exit_code: params.exit_code,
3302
- signal: params.signal,
3303
- timed_out: params.timed_out,
3304
- aborted: Boolean(params.aborted),
3305
- stdout: params.stdout,
3306
- stderr: params.stderr,
3307
- stdout_truncated: params.stdout_truncated,
3308
- stderr_truncated: params.stderr_truncated,
3309
- ...params.auth_required ? { auth_required: params.auth_required } : {},
3310
- ...params.permission_denied ? { permission_denied: params.permission_denied } : {}
3311
- });
3312
- }
3313
-
3314
- // src/chat/sandbox/sandbox.ts
3315
- import fs4 from "fs/promises";
3316
-
3317
- // src/chat/oauth-flow.ts
3318
- import { randomBytes } from "crypto";
3319
- import {
3320
- sourceSchema
3321
- } from "@sentry/junior-plugin-api";
3322
-
3323
- // src/chat/slack/outbound.ts
3324
- var MAX_SLACK_MESSAGE_TEXT_CHARS = 4e4;
3325
- function requireSlackConversationId(channelId, action) {
3326
- const normalized = normalizeSlackConversationId(channelId);
3327
- if (!normalized) {
3328
- throw new Error(`${action} requires a valid channel ID`);
3329
- }
3330
- return normalized;
3331
- }
3332
- function requireSlackThreadTimestamp(threadTs, action) {
3333
- const normalized = threadTs.trim();
3334
- if (!normalized) {
3335
- throw new Error(`${action} requires a thread timestamp`);
3336
- }
3337
- return normalized;
3338
- }
3339
- function requireSlackMessageTimestamp(timestamp, action) {
3340
- const normalized = parseSlackMessageTs(timestamp);
3341
- if (!normalized) {
3342
- throw new Error(`${action} requires a target message timestamp`);
3343
- }
3344
- return normalized;
3345
- }
3346
- function requireSlackMessageText(text, action) {
3347
- if (text.trim().length === 0) {
3348
- throw new Error(`${action} requires non-empty text`);
3349
- }
3350
- if (text.length > MAX_SLACK_MESSAGE_TEXT_CHARS) {
3351
- throw new Error(
3352
- `${action} text exceeds Slack's 40000 character truncation limit`
3353
- );
3354
- }
3355
- return text;
3356
- }
3357
- async function getSlackMessagePermalink(args) {
3358
- try {
3359
- const response = await withSlackRetries(
3360
- () => getSlackClient().chat.getPermalink({
3361
- channel: args.channelId,
3362
- message_ts: args.messageTs
3363
- }),
3364
- 3,
3365
- {
3366
- action: "chat.getPermalink",
3367
- idempotent: true,
3368
- spanAttributes: {
3369
- "app.slack.channel_id": args.channelId,
3370
- "app.slack.message_ts": args.messageTs
3371
- }
3372
- }
3373
- );
3374
- return response.permalink;
3375
- } catch {
3376
- return void 0;
3377
- }
3378
- }
3379
- async function postSlackMessage(input) {
3380
- const channelId = requireSlackConversationId(
3381
- input.channelId,
3382
- "Slack message posting"
3383
- );
3384
- const text = requireSlackMessageText(input.text, "Slack message posting");
3385
- const threadTs = input.threadTs ? requireSlackThreadTimestamp(
3386
- input.threadTs,
3387
- "Slack thread message posting"
3388
- ) : void 0;
3389
- const response = await withSlackRetries(
3390
- () => getSlackClient().chat.postMessage({
3391
- channel: channelId,
3392
- text,
3393
- ...input.blocks?.length ? {
3394
- blocks: input.blocks
3395
- } : {},
3396
- ...threadTs ? { thread_ts: threadTs } : {}
3397
- }),
3398
- 3,
3399
- {
3400
- action: "chat.postMessage",
3401
- spanAttributes: {
3402
- "app.slack.channel_id": channelId,
3403
- ...threadTs ? { "app.slack.thread_ts": threadTs } : {}
3404
- }
3405
- }
3406
- );
3407
- const messageTs = parseSlackMessageTs(response.ts);
3408
- if (!messageTs) {
3409
- throw new Error("Slack message posted without ts");
3410
- }
3411
- return {
3412
- ts: messageTs,
3413
- ...input.includePermalink ? {
3414
- permalink: await getSlackMessagePermalink({
3415
- channelId,
3416
- messageTs
3417
- })
3418
- } : {}
3419
- };
3420
- }
3421
- async function postSlackEphemeralMessage(input) {
3422
- const channelId = requireSlackConversationId(
3423
- input.channelId,
3424
- "Slack ephemeral message posting"
3425
- );
3426
- const userId = parseSlackUserId(input.userId);
3427
- if (!userId) {
3428
- throw new Error("Slack ephemeral message posting requires a user ID");
3429
- }
3430
- const text = requireSlackMessageText(
3431
- input.text,
3432
- "Slack ephemeral message posting"
3433
- );
3434
- const threadTs = input.threadTs ? requireSlackThreadTimestamp(
3435
- input.threadTs,
3436
- "Slack ephemeral thread message posting"
3437
- ) : void 0;
3438
- const response = await withSlackRetries(
3439
- () => getSlackClient().chat.postEphemeral({
3440
- channel: channelId,
3441
- user: userId,
3442
- text,
3443
- ...threadTs ? { thread_ts: threadTs } : {}
3444
- }),
3445
- 3,
3446
- {
3447
- action: "chat.postEphemeral",
3448
- spanAttributes: {
3449
- "app.slack.channel_id": channelId,
3450
- "app.slack.user_id": userId,
3451
- ...threadTs ? { "app.slack.thread_ts": threadTs } : {}
3452
- }
3453
- }
3454
- );
3455
- return {
3456
- messageTs: response.message_ts
3457
- };
3458
- }
3459
- async function uploadFilesToConversation(input) {
3460
- const channelId = requireSlackConversationId(
3461
- input.channelId,
3462
- "Slack file upload"
3463
- );
3464
- const threadTs = input.threadTs ? requireSlackThreadTimestamp(input.threadTs, "Slack file upload") : void 0;
3465
- if (input.files.length === 0) {
3466
- throw new Error("Slack file upload requires at least one file");
3467
- }
3468
- const fileUploads = input.files.map((file) => {
3469
- const filename = file.filename.trim();
3470
- if (!filename) {
3471
- throw new Error(
3472
- "Slack file upload requires every file to have a filename"
3473
- );
3474
- }
3475
- return {
3476
- file: file.data,
3477
- filename
3478
- };
3479
- });
3480
- const response = await withSlackRetries(
3481
- () => getSlackClient().filesUploadV2({
3482
- channel_id: channelId,
3483
- ...threadTs ? { thread_ts: threadTs } : {},
3484
- file_uploads: fileUploads
3485
- }),
3486
- 3,
3487
- {
3488
- action: "filesUploadV2",
3489
- spanAttributes: {
3490
- "app.slack.channel_id": channelId,
3491
- ...threadTs ? { "app.slack.thread_ts": threadTs } : {}
3492
- }
3493
- }
3494
- );
3495
- return {
3496
- files: (response.files ?? []).flatMap(
3497
- (completion) => (completion.files ?? []).map((file) => file.id ? { id: file.id } : {})
3498
- )
3499
- };
3500
- }
3501
- async function addReactionToMessage(input) {
3502
- const channelId = requireSlackConversationId(
3503
- input.channelId,
3504
- "Slack reaction"
3505
- );
3506
- const timestamp = requireSlackMessageTimestamp(
3507
- input.timestamp,
3508
- "Slack reaction"
3509
- );
3510
- const emoji = normalizeSlackEmojiName(input.emoji);
3511
- if (!emoji) {
3512
- throw new Error("Slack reaction requires a valid emoji alias name");
3513
- }
3514
- try {
3515
- await withSlackRetries(
3516
- () => getSlackClient().reactions.add({
3517
- channel: channelId,
3518
- timestamp,
3519
- name: emoji
3520
- }),
3521
- 3,
3522
- {
3523
- action: "reactions.add",
3524
- idempotent: true,
3525
- spanAttributes: {
3526
- "app.slack.channel_id": channelId,
3527
- "app.slack.message_ts": timestamp,
3528
- "app.slack.reaction": emoji
3529
- }
3530
- }
3531
- );
3532
- } catch (error) {
3533
- if (error instanceof SlackActionError && error.code === "already_reacted") {
3534
- return { ok: true };
3535
- }
3536
- throw error;
3537
- }
3538
- return { ok: true };
3539
- }
3540
- async function removeReactionFromMessage(input) {
3541
- const channelId = requireSlackConversationId(
3542
- input.channelId,
3543
- "Slack reaction removal"
3544
- );
3545
- const timestamp = requireSlackMessageTimestamp(
3546
- input.timestamp,
3547
- "Slack reaction removal"
3548
- );
3549
- const emoji = normalizeSlackEmojiName(input.emoji);
3550
- if (!emoji) {
3551
- throw new Error("Slack reaction removal requires a valid emoji alias name");
3552
- }
3553
- try {
3554
- await withSlackRetries(
3555
- () => getSlackClient().reactions.remove({
3556
- channel: channelId,
3557
- timestamp,
3558
- name: emoji
3559
- }),
3560
- 3,
3561
- {
3562
- action: "reactions.remove",
3563
- idempotent: true,
3564
- spanAttributes: {
3565
- "app.slack.channel_id": channelId,
3566
- "app.slack.message_ts": timestamp,
3567
- "app.slack.reaction": emoji
3568
- }
3569
- }
3570
- );
3571
- } catch (error) {
3572
- if (error instanceof SlackActionError && error.code === "no_reaction") {
3573
- return { ok: true };
3574
- }
3575
- throw error;
3576
- }
3577
- return { ok: true };
3578
- }
3579
-
3580
- // src/chat/oauth-authorization-message.ts
3581
- function formatOAuthAuthorizationMessage(args) {
3582
- return `${formatSlackLink(args.authorizationUrl, args.label)}. ${args.completionText}`;
3583
- }
3584
-
3585
- // src/chat/oauth-flow.ts
3586
- var OAUTH_STATE_TTL_MS = 10 * 60 * 1e3;
3587
- function optionalString(value) {
3588
- return typeof value === "string" ? value : void 0;
3589
- }
3590
- function parseOAuthStatePayload(value) {
3591
- if (!isRecord(value)) {
3592
- return void 0;
3593
- }
3594
- if (typeof value.userId !== "string" || typeof value.provider !== "string") {
3595
- return void 0;
3596
- }
3597
- const destination = parseDestination(value.destination);
3598
- if (value.destination !== void 0 && !destination) {
3599
- return void 0;
3600
- }
3601
- const source = value.source === void 0 ? void 0 : sourceSchema.safeParse(value.source);
3602
- if (value.source !== void 0 && (!source || !source.success)) {
3603
- return void 0;
3604
- }
3605
- return {
3606
- userId: value.userId,
3607
- provider: value.provider,
3608
- ...optionalString(value.channelId) ? { channelId: optionalString(value.channelId) } : {},
3609
- ...destination ? { destination } : {},
3610
- ...source?.success ? { source: source.data } : {},
3611
- ...optionalString(value.threadTs) ? { threadTs: optionalString(value.threadTs) } : {},
3612
- ...optionalString(value.resumeConversationId) ? { resumeConversationId: optionalString(value.resumeConversationId) } : {},
3613
- ...optionalString(value.resumeSessionId) ? { resumeSessionId: optionalString(value.resumeSessionId) } : {},
3614
- ...optionalString(value.scope) ? { scope: optionalString(value.scope) } : {}
3615
- };
3616
- }
3617
- function formatProviderLabel(provider) {
3618
- const displayName = pluginCatalogRuntime.getDisplayName(provider);
3619
- if (!displayName) {
3620
- throw new Error(`Unknown plugin provider display name: "${provider}"`);
3621
- }
3622
- return displayName;
3623
- }
3624
- function resolveBaseUrl() {
3625
- const explicit = process.env.JUNIOR_BASE_URL?.trim();
3626
- if (explicit) return explicit;
3627
- const vercelProd = process.env.VERCEL_PROJECT_PRODUCTION_URL?.trim();
3628
- if (vercelProd) return `https://${vercelProd}`;
3629
- const vercelUrl = process.env.VERCEL_URL?.trim();
3630
- if (vercelUrl) return `https://${vercelUrl}`;
3631
- return void 0;
3632
- }
3633
- async function deliverPrivateMessage(input) {
3634
- let client;
3635
- try {
3636
- client = getSlackClient();
3637
- } catch {
3638
- logWarn(
3639
- "oauth_private_delivery_skip",
3640
- {},
3641
- { "app.reason": "missing_bot_token" },
3642
- "Skipped private message delivery \u2014 no SLACK_BOT_TOKEN"
3643
- );
3644
- return false;
3645
- }
3646
- if (input.channelId) {
3647
- try {
3648
- if (isDmChannel(input.channelId)) {
3649
- await postSlackMessage({
3650
- channelId: input.channelId,
3651
- text: input.text,
3652
- threadTs: input.threadTs
3653
- });
3654
- } else {
3655
- await postSlackEphemeralMessage({
3656
- channelId: input.channelId,
3657
- userId: input.userId,
3658
- text: input.text,
3659
- threadTs: input.threadTs
3660
- });
3661
- }
3662
- return "in_context";
3663
- } catch (error) {
3664
- logWarn(
3665
- "oauth_private_delivery_failed",
3666
- {},
3667
- {
3668
- "app.slack.error": error instanceof Error ? error.message : String(error),
3669
- "app.slack.channel": input.channelId
3670
- },
3671
- "Private message delivery failed, falling back to DM"
3672
- );
3673
- }
3674
- }
3675
- try {
3676
- const dmChannelId = (await withSlackRetries(
3677
- () => client.conversations.open({ users: input.userId }),
3678
- 3,
3679
- {
3680
- action: "conversations.open",
3681
- spanAttributes: { "app.slack.user_id": input.userId }
3682
- }
3683
- )).channel?.id;
3684
- if (!dmChannelId) {
3685
- logWarn(
3686
- "oauth_dm_fallback_failed",
3687
- {},
3688
- { "app.reason": "no_dm_channel_id" },
3689
- "conversations.open returned no channel ID"
3690
- );
3691
- return false;
3692
- }
3693
- await postSlackMessage({ channelId: dmChannelId, text: input.text });
3694
- return "fallback_dm";
3695
- } catch (error) {
3696
- logWarn(
3697
- "oauth_dm_fallback_failed",
3698
- {},
3699
- {
3700
- "app.slack.error": error instanceof Error ? error.message : String(error)
3701
- },
3702
- "DM fallback delivery failed"
3703
- );
3704
- return false;
2602
+ });
3705
2603
  }
2604
+ return commandResult({
2605
+ stderr: `Usage:
2606
+ ${usage}
2607
+ `,
2608
+ exitCode: 2
2609
+ });
3706
2610
  }
3707
- async function startOAuthFlow(provider, input) {
3708
- const providerConfig = pluginCatalogRuntime.getOAuthConfig(provider);
3709
- if (!providerConfig) {
3710
- return {
3711
- ok: false,
3712
- error: `Provider "${provider}" does not support OAuth authorization`
3713
- };
3714
- }
3715
- const clientId = process.env[providerConfig.clientIdEnv]?.trim();
3716
- if (!clientId) {
3717
- return {
3718
- ok: false,
3719
- error: `Missing ${providerConfig.clientIdEnv} environment variable`
3720
- };
3721
- }
3722
- const baseUrl = resolveBaseUrl();
3723
- if (!baseUrl) {
3724
- return {
3725
- ok: false,
3726
- error: "Cannot determine base URL (set JUNIOR_BASE_URL or deploy to Vercel)"
3727
- };
2611
+ async function handlePluginsCommand(args) {
2612
+ const usage = "jr-rpc plugins list";
2613
+ const subverb = (args[0] ?? "").trim();
2614
+ if (subverb !== "list" || args.length !== 1) {
2615
+ return commandResult({
2616
+ stderr: `Usage:
2617
+ ${usage}
2618
+ `,
2619
+ exitCode: 2
2620
+ });
3728
2621
  }
3729
- const state = randomBytes(32).toString("hex");
3730
- const requestedScope = input.scope ?? providerConfig.scope;
3731
- await getStateAdapter().set(
3732
- `oauth-state:${state}`,
3733
- {
3734
- userId: input.actorId,
3735
- provider,
3736
- ...input.channelId ? { channelId: input.channelId } : {},
3737
- ...input.destination ? { destination: input.destination } : {},
3738
- ...input.source ? { source: input.source } : {},
3739
- ...input.threadTs ? { threadTs: input.threadTs } : {},
3740
- ...input.resumeConversationId ? { resumeConversationId: input.resumeConversationId } : {},
3741
- ...input.resumeSessionId ? { resumeSessionId: input.resumeSessionId } : {},
3742
- ...requestedScope ? { scope: requestedScope } : {}
2622
+ const plugins = pluginCatalogRuntime.getProviders().map((plugin) => ({
2623
+ name: plugin.manifest.name,
2624
+ displayName: plugin.manifest.displayName,
2625
+ description: plugin.manifest.description,
2626
+ capabilities: [...plugin.manifest.capabilities],
2627
+ configKeys: [...plugin.manifest.configKeys]
2628
+ })).sort((left, right) => left.name.localeCompare(right.name));
2629
+ return commandResult({
2630
+ stdout: {
2631
+ ok: true,
2632
+ plugins
3743
2633
  },
3744
- OAUTH_STATE_TTL_MS
3745
- );
3746
- const authorizeParams = new URLSearchParams({
3747
- client_id: clientId,
3748
- state,
3749
- redirect_uri: `${baseUrl}${providerConfig.callbackPath}`,
3750
- response_type: "code"
2634
+ exitCode: 0
3751
2635
  });
3752
- if (requestedScope) {
3753
- authorizeParams.set("scope", requestedScope);
3754
- }
3755
- for (const [key, value] of Object.entries(
3756
- providerConfig.authorizeParams ?? {}
3757
- )) {
3758
- authorizeParams.set(key, value);
2636
+ }
2637
+ function createJrRpcCommand(deps) {
2638
+ return defineCommand("jr-rpc", async (args) => {
2639
+ const usage = [
2640
+ "jr-rpc config get <key>",
2641
+ "jr-rpc config set <key> <value> [--json]",
2642
+ "jr-rpc config unset <key>",
2643
+ "jr-rpc config list [--prefix <value>]",
2644
+ "jr-rpc plugins list"
2645
+ ].join("\n");
2646
+ const verb = (args[0] ?? "").trim();
2647
+ if (verb === "config") {
2648
+ return handleConfigCommand(args.slice(1), deps);
2649
+ }
2650
+ if (verb === "plugins") {
2651
+ return handlePluginsCommand(args.slice(1));
2652
+ }
2653
+ return commandResult({
2654
+ stderr: `Unsupported jr-rpc command. Use:
2655
+ ${usage}
2656
+ `,
2657
+ exitCode: 2
2658
+ });
2659
+ });
2660
+ }
2661
+ async function maybeExecuteJrRpcCustomCommand(command, deps) {
2662
+ const normalized = command.trim();
2663
+ if (!/^jr-rpc(?:\s|$)/.test(normalized)) {
2664
+ return { handled: false };
3759
2665
  }
3760
- logInfo(
3761
- "jr_rpc_oauth_start",
3762
- {},
3763
- {
3764
- "app.credential.provider": provider,
3765
- ...input.activeSkillName ? { "app.skill.name": input.activeSkillName } : {}
3766
- },
3767
- "Initiated OAuth authorization code flow"
3768
- );
3769
- const authorizationUrl = `${providerConfig.authorizeEndpoint}?${authorizeParams.toString()}`;
2666
+ const shell = new Bash({
2667
+ customCommands: [createJrRpcCommand(deps)]
2668
+ });
2669
+ const execResult = await shell.exec(normalized);
3770
2670
  return {
3771
- ok: true,
3772
- delivery: await deliverPrivateMessage({
3773
- channelId: input.channelId,
3774
- threadTs: input.threadTs,
3775
- userId: input.actorId,
3776
- text: formatOAuthAuthorizationMessage({
3777
- authorizationUrl,
3778
- label: `Click here to link your ${formatProviderLabel(provider)} account`,
3779
- completionText: "Once you've authorized, you'll see a confirmation in Slack."
3780
- })
3781
- })
2671
+ handled: true,
2672
+ result: {
2673
+ ok: execResult.exitCode === 0,
2674
+ command: normalized,
2675
+ cwd: "/",
2676
+ exit_code: execResult.exitCode,
2677
+ signal: null,
2678
+ timed_out: false,
2679
+ stdout: execResult.stdout,
2680
+ stderr: execResult.stderr,
2681
+ stdout_truncated: false,
2682
+ stderr_truncated: false
2683
+ }
3782
2684
  };
3783
2685
  }
3784
2686
 
3785
- // src/chat/sandbox/egress/session.ts
3786
- import { createHmac, randomUUID, timingSafeEqual } from "crypto";
3787
-
3788
- // src/chat/sandbox/egress/schemas.ts
3789
- import { z as z3 } from "zod";
3790
- import {
3791
- pluginAuthorizationSchema,
3792
- pluginCredentialHeaderTransformSchema,
3793
- pluginGrantSchema,
3794
- pluginProviderAccountSchema
3795
- } from "@sentry/junior-plugin-api";
3796
- var finiteNumberSchema = z3.number().refine(Number.isFinite);
3797
- var httpStatusSchema = z3.number().int().min(100).max(599);
3798
- var providerNameSchema = z3.string().regex(/^[a-z][a-z0-9-]*$/);
3799
- var credentialSignalKindSchema = z3.enum(["auth_required", "unavailable"]);
3800
- var sandboxEgressGrantSchema = pluginGrantSchema;
3801
- var sandboxEgressCredentialContextSchema = z3.object({
3802
- credentials: credentialContextSchema,
3803
- egressId: z3.string().min(1),
3804
- expiresAtMs: finiteNumberSchema,
3805
- contextId: z3.string().min(1)
3806
- }).strict();
3807
- var sandboxEgressCredentialLeaseSchema = z3.object({
3808
- account: pluginProviderAccountSchema.optional(),
3809
- authorization: pluginAuthorizationSchema.optional(),
3810
- grant: sandboxEgressGrantSchema,
3811
- provider: providerNameSchema,
3812
- expiresAt: z3.string().min(1),
3813
- headerTransforms: z3.array(pluginCredentialHeaderTransformSchema).min(1)
3814
- }).strict();
3815
- var sandboxEgressAuthRequiredSignalSchema = z3.object({
3816
- authorization: pluginAuthorizationSchema.optional(),
3817
- grant: sandboxEgressGrantSchema,
3818
- kind: credentialSignalKindSchema.default("auth_required"),
3819
- provider: providerNameSchema,
3820
- message: z3.string().optional(),
3821
- createdAtMs: finiteNumberSchema
3822
- }).strict().superRefine((signal, ctx) => {
3823
- if (signal.authorization && signal.authorization.provider !== signal.provider) {
3824
- ctx.addIssue({
3825
- code: z3.ZodIssueCode.custom,
3826
- message: "Auth signal authorization provider must match provider",
3827
- path: ["authorization", "provider"]
3828
- });
3829
- }
3830
- });
3831
- var sandboxEgressPermissionDeniedSignalSchema = z3.object({
3832
- account: pluginProviderAccountSchema.optional(),
3833
- acceptedPermissions: z3.string().optional(),
3834
- grant: sandboxEgressGrantSchema,
3835
- message: z3.string().min(1),
3836
- provider: providerNameSchema,
3837
- source: z3.literal("upstream"),
3838
- sso: z3.string().optional(),
3839
- status: httpStatusSchema,
3840
- upstreamHost: z3.string().min(1),
3841
- upstreamPath: z3.string().min(1),
3842
- createdAtMs: finiteNumberSchema
3843
- }).strict();
3844
- function parseSandboxEgressAuthRequiredSignal(value) {
3845
- const result = sandboxEgressAuthRequiredSignalSchema.safeParse(value);
3846
- return result.success ? result.data : void 0;
3847
- }
3848
- function parseSandboxEgressPermissionDeniedSignal(value) {
3849
- const result = sandboxEgressPermissionDeniedSignalSchema.safeParse(value);
3850
- return result.success ? result.data : void 0;
2687
+ // src/chat/sandbox/command-result.ts
2688
+ function formatSandboxCommandResult(params) {
2689
+ return makeStructuredToolResult({
2690
+ ok: params.ok,
2691
+ status: params.ok ? "success" : "error",
2692
+ target: params.command,
2693
+ data: {
2694
+ command: params.command,
2695
+ cwd: params.cwd,
2696
+ exit_code: params.exit_code,
2697
+ signal: params.signal,
2698
+ timed_out: params.timed_out,
2699
+ aborted: Boolean(params.aborted),
2700
+ stdout: params.stdout,
2701
+ stderr: params.stderr,
2702
+ stdout_truncated: params.stdout_truncated,
2703
+ stderr_truncated: params.stderr_truncated
2704
+ },
2705
+ truncated: params.stdout_truncated || params.stderr_truncated,
2706
+ ...!params.ok ? {
2707
+ error: {
2708
+ kind: params.aborted ? "outcome_unknown" : params.timed_out ? "timeout" : "nonzero_exit",
2709
+ message: params.aborted ? "Command was interrupted before its outcome was confirmed. It may have produced side effects; reconcile external state before retrying or reporting failure." : params.stderr.trim() || `Command exited with code ${params.exit_code}`,
2710
+ retryable: params.timed_out
2711
+ }
2712
+ } : {},
2713
+ command: params.command,
2714
+ cwd: params.cwd,
2715
+ exit_code: params.exit_code,
2716
+ signal: params.signal,
2717
+ timed_out: params.timed_out,
2718
+ aborted: Boolean(params.aborted),
2719
+ stdout: params.stdout,
2720
+ stderr: params.stderr,
2721
+ stdout_truncated: params.stdout_truncated,
2722
+ stderr_truncated: params.stderr_truncated,
2723
+ ...params.auth_required ? { auth_required: params.auth_required } : {},
2724
+ ...params.permission_denied ? { permission_denied: params.permission_denied } : {}
2725
+ });
3851
2726
  }
3852
2727
 
2728
+ // src/chat/sandbox/sandbox.ts
2729
+ import fs4 from "fs/promises";
2730
+
3853
2731
  // src/chat/sandbox/egress/session.ts
2732
+ import { createHmac, randomUUID, timingSafeEqual } from "crypto";
3854
2733
  var SANDBOX_EGRESS_PROXY_PATH = "/api/internal/sandbox-egress";
3855
2734
  var SANDBOX_EGRESS_TOKEN_VERSION = "v1";
3856
2735
  var SANDBOX_EGRESS_HMAC_CONTEXT = "junior.sandbox_egress.v1";
@@ -5107,6 +3986,16 @@ async function collectFiles(params) {
5107
3986
  }
5108
3987
 
5109
3988
  // src/chat/tools/sandbox/text-edits.ts
3989
+ var MAX_DIFF_LINE_CHARS = 4e3;
3990
+ function truncateDiffLine(value) {
3991
+ if (value.length <= MAX_DIFF_LINE_CHARS) {
3992
+ return { line: value, truncated: false };
3993
+ }
3994
+ return {
3995
+ line: `${value.slice(0, MAX_DIFF_LINE_CHARS)}... [line truncated]`,
3996
+ truncated: true
3997
+ };
3998
+ }
5110
3999
  function detectLineEnding(value) {
5111
4000
  return value.includes("\r\n") ? "\r\n" : "\n";
5112
4001
  }
@@ -5194,27 +4083,35 @@ function buildCompactDiff(oldContent, newContent) {
5194
4083
  const oldContextEnd = Math.min(oldLines.length - 1, oldSuffix + 3);
5195
4084
  const width = String(Math.max(oldLines.length, newLines.length)).length;
5196
4085
  const output = [];
4086
+ let lineTruncated = false;
4087
+ const pushLine = (value) => {
4088
+ const bounded2 = truncateDiffLine(value);
4089
+ output.push(bounded2.line);
4090
+ lineTruncated ||= bounded2.truncated;
4091
+ };
5197
4092
  if (contextStart > 0) {
5198
- output.push(` ${"".padStart(width)} ...`);
4093
+ pushLine(` ${"".padStart(width)} ...`);
5199
4094
  }
5200
4095
  for (let index = contextStart; index < prefix; index += 1) {
5201
- output.push(` ${String(index + 1).padStart(width)} ${oldLines[index]}`);
4096
+ pushLine(` ${String(index + 1).padStart(width)} ${oldLines[index]}`);
5202
4097
  }
5203
4098
  for (let index = prefix; index <= oldSuffix; index += 1) {
5204
- output.push(`-${String(index + 1).padStart(width)} ${oldLines[index]}`);
4099
+ pushLine(`-${String(index + 1).padStart(width)} ${oldLines[index]}`);
5205
4100
  }
5206
4101
  for (let index = prefix; index <= newSuffix; index += 1) {
5207
- output.push(`+${String(index + 1).padStart(width)} ${newLines[index]}`);
4102
+ pushLine(`+${String(index + 1).padStart(width)} ${newLines[index]}`);
5208
4103
  }
5209
4104
  for (let index = newSuffix + 1; index <= newContextEnd; index += 1) {
5210
- output.push(` ${String(index + 1).padStart(width)} ${newLines[index]}`);
4105
+ pushLine(` ${String(index + 1).padStart(width)} ${newLines[index]}`);
5211
4106
  }
5212
4107
  if (newContextEnd < newLines.length - 1 || oldContextEnd < oldLines.length - 1) {
5213
- output.push(` ${"".padStart(width)} ...`);
4108
+ pushLine(` ${"".padStart(width)} ...`);
5214
4109
  }
4110
+ const bounded = truncateText(output.join("\n"), MAX_TEXT_CHARS);
5215
4111
  return {
5216
- diff: output.join("\n"),
5217
- firstChangedLine: firstChangedLine(oldContent, newContent)
4112
+ diff: bounded.content,
4113
+ firstChangedLine: firstChangedLine(oldContent, newContent),
4114
+ truncated: lineTruncated || bounded.truncated
5218
4115
  };
5219
4116
  }
5220
4117
  function validateAndApplyTextEdits(content, edits, targetName) {
@@ -5281,10 +4178,10 @@ function validateAndApplyTextEdits(content, edits, targetName) {
5281
4178
  }
5282
4179
 
5283
4180
  // src/chat/tools/sandbox/edit-file.ts
5284
- import { z as z5 } from "zod";
4181
+ import { z as z4 } from "zod";
5285
4182
 
5286
4183
  // src/chat/tool-support/zod-tool.ts
5287
- import { z as z4 } from "zod";
4184
+ import { z as z3 } from "zod";
5288
4185
  function isContentOnlyToolResult(value) {
5289
4186
  return value !== null && typeof value === "object" && Array.isArray(value.content) && !("details" in value);
5290
4187
  }
@@ -5308,7 +4205,7 @@ function zodTool(definition) {
5308
4205
  const { inputSchema, outputSchema, prepareArguments, execute, ...toolDef } = definition;
5309
4206
  let modelInputSchema;
5310
4207
  try {
5311
- modelInputSchema = z4.toJSONSchema(inputSchema);
4208
+ modelInputSchema = z3.toJSONSchema(inputSchema);
5312
4209
  } catch (error) {
5313
4210
  throw new TypeError(
5314
4211
  "zodTool() inputSchema must be representable as JSON Schema.",
@@ -5318,7 +4215,7 @@ function zodTool(definition) {
5318
4215
  let modelOutputSchema;
5319
4216
  if (outputSchema) {
5320
4217
  try {
5321
- modelOutputSchema = z4.toJSONSchema(outputSchema);
4218
+ modelOutputSchema = z3.toJSONSchema(outputSchema);
5322
4219
  } catch (error) {
5323
4220
  throw new TypeError(
5324
4221
  "zodTool() outputSchema must be representable as JSON Schema.",
@@ -5443,7 +4340,6 @@ async function editFile(params) {
5443
4340
  status: "success",
5444
4341
  target: params.path,
5445
4342
  data: {
5446
- diff: diff.diff,
5447
4343
  first_changed_line: diff.firstChangedLine,
5448
4344
  path: params.path,
5449
4345
  replacements: params.edits.length
@@ -5451,23 +4347,24 @@ async function editFile(params) {
5451
4347
  diff: diff.diff,
5452
4348
  first_changed_line: diff.firstChangedLine,
5453
4349
  path: params.path,
5454
- replacements: params.edits.length
4350
+ replacements: params.edits.length,
4351
+ truncated: diff.truncated
5455
4352
  });
5456
4353
  }
5457
- var editReplacementSchema = z5.object({
5458
- oldText: z5.string().min(1).describe(
4354
+ var editReplacementSchema = z4.object({
4355
+ oldText: z4.string().min(1).describe(
5459
4356
  "Exact text to replace. It must be unique in the original file and must not overlap another edit."
5460
4357
  ),
5461
- newText: z5.string().describe("Replacement text for this edit.")
4358
+ newText: z4.string().describe("Replacement text for this edit.")
5462
4359
  });
5463
4360
  function createEditFileTool() {
5464
4361
  return zodTool({
5465
4362
  description: "Edit one sandbox workspace file with exact text replacements. Use for precise changes to existing files; prefer this over writeFile for targeted changes. Each oldText must match exactly, be unique, and not overlap another edit. Returns a diff. Multiple changes to the same file: use one edits[] call.",
5466
4363
  prepareArguments: prepareEditFileArguments,
5467
4364
  executionMode: "sequential",
5468
- inputSchema: z5.object({
5469
- path: z5.string().min(1).describe("Path to edit in the sandbox workspace."),
5470
- edits: z5.array(editReplacementSchema).min(1).describe(
4365
+ inputSchema: z4.object({
4366
+ path: z4.string().min(1).describe("Path to edit in the sandbox workspace."),
4367
+ edits: z4.array(editReplacementSchema).min(1).describe(
5471
4368
  "Exact replacements matched against the original file, not incrementally."
5472
4369
  )
5473
4370
  }),
@@ -5482,7 +4379,7 @@ function createEditFileTool() {
5482
4379
 
5483
4380
  // src/chat/tools/sandbox/find-files.ts
5484
4381
  import path5 from "path";
5485
- import { z as z6 } from "zod";
4382
+ import { z as z5 } from "zod";
5486
4383
  var DEFAULT_FIND_LIMIT = 1e3;
5487
4384
  async function findFiles(params) {
5488
4385
  if (!params.pattern.trim()) {
@@ -5542,14 +4439,14 @@ function createFindFilesTool() {
5542
4439
  return zodTool({
5543
4440
  description: "Find sandbox workspace files by glob pattern. Returns bounded paths relative to the search root and skips dependency/cache directories.",
5544
4441
  annotations: { readOnlyHint: true, destructiveHint: false },
5545
- inputSchema: z6.object({
5546
- pattern: z6.string().min(1).describe(
4442
+ inputSchema: z5.object({
4443
+ pattern: z5.string().min(1).describe(
5547
4444
  "Glob pattern to match, for example '*.ts', '**/*.json', or 'src/**/*.test.ts'."
5548
4445
  ),
5549
- path: z6.string().min(1).describe(
4446
+ path: z5.string().min(1).describe(
5550
4447
  "Directory or file path in the sandbox workspace. Defaults to the workspace root."
5551
4448
  ).optional(),
5552
- limit: z6.coerce.number().int().min(1).describe("Maximum number of file paths to return. Defaults to 1000.").optional()
4449
+ limit: z5.coerce.number().int().min(1).describe("Maximum number of file paths to return. Defaults to 1000.").optional()
5553
4450
  }),
5554
4451
  outputSchema: juniorToolResultSchema,
5555
4452
  execute: async () => {
@@ -5562,12 +4459,12 @@ function createFindFilesTool() {
5562
4459
 
5563
4460
  // src/chat/tools/sandbox/grep.ts
5564
4461
  import path6 from "path";
5565
- import { z as z7 } from "zod";
4462
+ import { z as z6 } from "zod";
5566
4463
  var DEFAULT_GREP_LIMIT = 100;
5567
4464
  var MAX_GREP_LINE_CHARS = 500;
5568
- var booleanInput = (description) => z7.preprocess(
4465
+ var booleanInput = (description) => z6.preprocess(
5569
4466
  (value) => value === "true" ? true : value === "false" ? false : value,
5570
- z7.boolean()
4467
+ z6.boolean()
5571
4468
  ).describe(description);
5572
4469
  function truncateGrepLine(value) {
5573
4470
  if (value.length <= MAX_GREP_LINE_CHARS) {
@@ -5716,20 +4613,20 @@ function createGrepTool() {
5716
4613
  return zodTool({
5717
4614
  description: "Search sandbox workspace file contents. Returns bounded matching lines with file paths and line numbers. Respects path/glob filters and includes truncation notices.",
5718
4615
  annotations: { readOnlyHint: true, destructiveHint: false },
5719
- inputSchema: z7.object({
5720
- pattern: z7.string().min(1).describe("Regex pattern or literal text to search for."),
5721
- path: z7.string().min(1).describe(
4616
+ inputSchema: z6.object({
4617
+ pattern: z6.string().min(1).describe("Regex pattern or literal text to search for."),
4618
+ path: z6.string().min(1).describe(
5722
4619
  "Directory or file path in the sandbox workspace. Defaults to the workspace root."
5723
4620
  ).optional(),
5724
- glob: z7.string().min(1).describe("Optional glob filter such as '*.ts' or '**/*.test.ts'.").optional(),
4621
+ glob: z6.string().min(1).describe("Optional glob filter such as '*.ts' or '**/*.test.ts'.").optional(),
5725
4622
  ignoreCase: booleanInput(
5726
4623
  "Whether matching should ignore case."
5727
4624
  ).optional(),
5728
4625
  literal: booleanInput(
5729
4626
  "Treat pattern as literal text instead of regex."
5730
4627
  ).optional(),
5731
- context: z7.coerce.number().int().min(0).describe("Number of surrounding context lines to include.").optional(),
5732
- limit: z7.coerce.number().int().min(1).describe("Maximum matches to return. Defaults to 100.").optional()
4628
+ context: z6.coerce.number().int().min(0).describe("Number of surrounding context lines to include.").optional(),
4629
+ limit: z6.coerce.number().int().min(1).describe("Maximum matches to return. Defaults to 100.").optional()
5733
4630
  }),
5734
4631
  outputSchema: juniorToolResultSchema,
5735
4632
  execute: async () => {
@@ -5740,7 +4637,7 @@ function createGrepTool() {
5740
4637
 
5741
4638
  // src/chat/tools/sandbox/list-dir.ts
5742
4639
  import path7 from "path";
5743
- import { z as z8 } from "zod";
4640
+ import { z as z7 } from "zod";
5744
4641
  var DEFAULT_LIST_LIMIT = 500;
5745
4642
  async function listDir(params) {
5746
4643
  const dirPath = resolveWorkspacePath(params.path);
@@ -5821,11 +4718,11 @@ function createListDirTool() {
5821
4718
  return zodTool({
5822
4719
  description: "List a sandbox workspace directory. Returns sorted entries with '/' suffixes for directories and bounded truncation notices.",
5823
4720
  annotations: { readOnlyHint: true, destructiveHint: false },
5824
- inputSchema: z8.object({
5825
- path: z8.string().min(1).describe(
4721
+ inputSchema: z7.object({
4722
+ path: z7.string().min(1).describe(
5826
4723
  "Directory path in the sandbox workspace. Defaults to the workspace root."
5827
4724
  ).optional(),
5828
- limit: z8.coerce.number().int().min(1).describe("Maximum entries to return. Defaults to 500.").optional()
4725
+ limit: z7.coerce.number().int().min(1).describe("Maximum entries to return. Defaults to 500.").optional()
5829
4726
  }),
5830
4727
  outputSchema: juniorToolResultSchema,
5831
4728
  execute: async () => {
@@ -5837,7 +4734,7 @@ function createListDirTool() {
5837
4734
  }
5838
4735
 
5839
4736
  // src/chat/tools/sandbox/read-file.ts
5840
- import { z as z9 } from "zod";
4737
+ import { z as z8 } from "zod";
5841
4738
 
5842
4739
  // src/chat/tool-support/text-range-result.ts
5843
4740
  var DEFAULT_READ_LIMIT = 1e3;
@@ -5912,10 +4809,10 @@ function createReadFileTool() {
5912
4809
  return zodTool({
5913
4810
  description: "Read a bounded line range from a file in the sandbox workspace. Use when you need exact file contents to verify facts or make edits safely. Prefer grep/findFiles/listDir for broad discovery.",
5914
4811
  annotations: { readOnlyHint: true, destructiveHint: false },
5915
- inputSchema: z9.object({
5916
- path: z9.string().min(1).describe("Path to the file in the sandbox workspace."),
5917
- offset: z9.coerce.number().int().min(1).describe("1-indexed line number to start reading from.").optional(),
5918
- limit: z9.coerce.number().int().min(1).describe("Maximum number of lines to read. Defaults to 1000.").optional()
4812
+ inputSchema: z8.object({
4813
+ path: z8.string().min(1).describe("Path to the file in the sandbox workspace."),
4814
+ offset: z8.coerce.number().int().min(1).describe("1-indexed line number to start reading from.").optional(),
4815
+ limit: z8.coerce.number().int().min(1).describe("Maximum number of lines to read. Defaults to 1000.").optional()
5919
4816
  }),
5920
4817
  outputSchema: juniorToolResultSchema,
5921
4818
  execute: async () => {
@@ -5980,6 +4877,30 @@ function createSandbox(options) {
5980
4877
  });
5981
4878
  return token;
5982
4879
  };
4880
+ const clearEgressSignals = async (egressId) => {
4881
+ if (!options.egressSignals) {
4882
+ await clearSandboxEgressSignals(egressId);
4883
+ return;
4884
+ }
4885
+ await options.egressSignals.clear(
4886
+ sandboxEgressCredentialTokenFor(egressId)
4887
+ );
4888
+ };
4889
+ const consumeEgressSignals = async (egressId) => {
4890
+ if (options.egressSignals) {
4891
+ return await options.egressSignals.consume(
4892
+ sandboxEgressCredentialTokenFor(egressId)
4893
+ );
4894
+ }
4895
+ const [authRequired, permissionDenied] = await Promise.all([
4896
+ consumeSandboxEgressAuthRequiredSignal(egressId),
4897
+ consumeSandboxEgressPermissionDeniedSignal(egressId)
4898
+ ]);
4899
+ return {
4900
+ ...authRequired ? { authRequired } : {},
4901
+ ...permissionDenied ? { permissionDenied } : {}
4902
+ };
4903
+ };
5983
4904
  const runtime = createSandboxRuntime({
5984
4905
  sandboxRef: options.sandboxRef,
5985
4906
  skills: options.skills,
@@ -6023,7 +4944,7 @@ function createSandbox(options) {
6023
4944
  "app.sandbox.command_length": command.length
6024
4945
  });
6025
4946
  const { bash: executeBash, sessionId: activeSessionId } = await runtime.tools();
6026
- await clearSandboxEgressSignals(activeSessionId);
4947
+ await clearEgressSignals(activeSessionId);
6027
4948
  const result = await withSandboxToolSpan(
6028
4949
  "bash",
6029
4950
  "process.exec",
@@ -6061,8 +4982,7 @@ function createSandbox(options) {
6061
4982
  }
6062
4983
  }
6063
4984
  );
6064
- const authRequired = await consumeSandboxEgressAuthRequiredSignal(activeSessionId);
6065
- const permissionDenied = await consumeSandboxEgressPermissionDeniedSignal(activeSessionId);
4985
+ const { authRequired, permissionDenied } = await consumeEgressSignals(activeSessionId);
6066
4986
  return formatSandboxCommandResult({
6067
4987
  ok: result.exitCode === 0,
6068
4988
  command,
@@ -6381,25 +5301,25 @@ function createSandbox(options) {
6381
5301
 
6382
5302
  // src/chat/tools/sandbox/file-uploads.ts
6383
5303
  import path8 from "path";
6384
- import { z as z10 } from "zod";
5304
+ import { z as z9 } from "zod";
6385
5305
  var MAX_SANDBOX_FILE_UPLOAD_BYTES = 10 * 1024 * 1024;
6386
5306
  var SANDBOX_ARTIFACTS_DIR = "/tmp/junior/artifacts";
6387
- var sandboxFileReferenceSchema = z10.object({
6388
- path: z10.string().min(1).describe(
5307
+ var sandboxFileReferenceSchema = z9.object({
5308
+ path: z9.string().min(1).describe(
6389
5309
  "Exact source path of an existing sandbox file. Preserve paths returned by other tools unchanged; do not move or rewrite them before sending."
6390
5310
  ),
6391
- filename: z10.string().min(1).nullable().optional().describe(
5311
+ filename: z9.string().min(1).nullable().optional().describe(
6392
5312
  "Optional filename override shown to the recipient. Null is treated as omitted."
6393
5313
  ),
6394
- mimeType: z10.string().min(1).nullable().optional().describe("Optional MIME type override. Null is treated as omitted."),
6395
- bytes: z10.number().int().nonnegative().nullable().optional().describe(
5314
+ mimeType: z9.string().min(1).nullable().optional().describe("Optional MIME type override. Null is treated as omitted."),
5315
+ bytes: z9.number().int().nonnegative().nullable().optional().describe(
6396
5316
  "Optional known file size returned by a producing tool. Null is treated as omitted; sending tools validate the file contents directly."
6397
5317
  )
6398
5318
  });
6399
5319
  var generatedArtifactFileRefSchema = sandboxFileReferenceSchema.extend({
6400
- filename: z10.string().min(1),
6401
- mimeType: z10.string().min(1).optional(),
6402
- bytes: z10.number().int().nonnegative()
5320
+ filename: z9.string().min(1),
5321
+ mimeType: z9.string().min(1).optional(),
5322
+ bytes: z9.number().int().nonnegative()
6403
5323
  }).strict();
6404
5324
  var MIME_BY_EXTENSION = {
6405
5325
  ".png": "image/png",
@@ -6563,6 +5483,7 @@ function createAgentSandbox(options) {
6563
5483
  referenceFiles: listReferenceFiles(),
6564
5484
  traceContext: options.traceContext,
6565
5485
  tracePropagation: options.tracePropagation,
5486
+ egressSignals: options.egressSignals,
6566
5487
  credentialEgress: options.credentialEgress,
6567
5488
  prepare: options.prepareSandbox,
6568
5489
  onSandboxRefChanged: async (sandboxRef) => {
@@ -7302,13 +6223,13 @@ function serializeMcpPayload(payload, privacy, options = {}) {
7302
6223
  }
7303
6224
 
7304
6225
  // src/chat/tools/sandbox/bash.ts
7305
- import { z as z11 } from "zod";
6226
+ import { z as z10 } from "zod";
7306
6227
  function createBashTool2() {
7307
6228
  return zodTool({
7308
6229
  description: "Run a bash command inside the isolated sandbox workspace. Use this for repository inspection/execution tasks that need shell access. Do not use for network-sensitive or destructive actions unless explicitly required.",
7309
- inputSchema: z11.object({
7310
- command: z11.string().min(1).describe("Bash command to run inside the sandbox."),
7311
- timeoutMs: z11.coerce.number().int().min(1e3).describe(
6230
+ inputSchema: z10.object({
6231
+ command: z10.string().min(1).describe("Bash command to run inside the sandbox."),
6232
+ timeoutMs: z10.coerce.number().int().min(1e3).describe(
7312
6233
  "Optional command timeout in milliseconds. Use for commands that may hang."
7313
6234
  ).optional()
7314
6235
  }),
@@ -7322,14 +6243,14 @@ function createBashTool2() {
7322
6243
  }
7323
6244
 
7324
6245
  // src/chat/tools/web/image-generate.ts
7325
- import { z as z12 } from "zod";
6246
+ import { z as z11 } from "zod";
7326
6247
  var imageGenerateOutputSchema = juniorToolResultSchema.extend({
7327
- model: z12.string(),
7328
- prompt: z12.string(),
7329
- enrichedPrompt: z12.string(),
7330
- image_count: z12.number().int().nonnegative(),
7331
- images: z12.array(generatedArtifactFileRefSchema),
7332
- delivery: z12.string()
6248
+ model: z11.string(),
6249
+ prompt: z11.string(),
6250
+ enrichedPrompt: z11.string(),
6251
+ image_count: z11.number().int().nonnegative(),
6252
+ images: z11.array(generatedArtifactFileRefSchema),
6253
+ delivery: z11.string()
7333
6254
  }).strict();
7334
6255
  var ENRICHMENT_SYSTEM_PROMPT = `You are an image prompt enrichment agent. Your job is to rewrite image generation requests to reflect a specific visual identity and mood.
7335
6256
 
@@ -7390,8 +6311,8 @@ function parseImageGenerationError(status, body, model) {
7390
6311
  function createImageGenerateTool(hooks, options, deps = {}) {
7391
6312
  return zodTool({
7392
6313
  description: "Generate images from a prompt. Use when the user wants to visually show or represent something \u2014 feelings, concepts, art, humor, or any visual idea. Also use for explicit image creation requests.",
7393
- inputSchema: z12.object({
7394
- prompt: z12.string().min(1).max(4e3).describe("Image generation prompt.")
6314
+ inputSchema: z11.object({
6315
+ prompt: z11.string().min(1).max(4e3).describe("Image generation prompt.")
7395
6316
  }),
7396
6317
  outputSchema: imageGenerateOutputSchema,
7397
6318
  execute: async ({ prompt }) => {
@@ -7477,7 +6398,7 @@ function createImageGenerateTool(hooks, options, deps = {}) {
7477
6398
  }
7478
6399
 
7479
6400
  // src/chat/tools/skill/call-mcp-tool.ts
7480
- import { z as z13 } from "zod";
6401
+ import { z as z12 } from "zod";
7481
6402
  function resolveMcpArguments(input) {
7482
6403
  const extraKeys = Object.keys(input).filter(
7483
6404
  (key) => key !== "tool_name" && key !== "arguments"
@@ -7513,9 +6434,9 @@ function missingToolMessage(toolName, provider) {
7513
6434
  function createCallMcpToolTool(mcpToolManager) {
7514
6435
  return zodTool({
7515
6436
  description: "Call an active MCP tool by exact tool_name. Use searchMcpTools to discover tool names and schemas; copy required provider fields into arguments. Do not call with only tool_name unless the discovered tool has no arguments. Authorization is handled by the runtime when required.",
7516
- inputSchema: z13.object({
7517
- tool_name: z13.string().min(1).describe("Exact MCP tool_name from searchMcpTools."),
7518
- arguments: z13.record(z13.string(), z13.unknown()).describe(
6437
+ inputSchema: z12.object({
6438
+ tool_name: z12.string().min(1).describe("Exact MCP tool_name from searchMcpTools."),
6439
+ arguments: z12.record(z12.string(), z12.unknown()).describe(
7519
6440
  'Arguments matching the disclosed MCP tool schema, for example { "query": "..." } when searchMcpTools shows query is required.'
7520
6441
  ).optional()
7521
6442
  }).passthrough(),
@@ -7556,7 +6477,7 @@ function createCallMcpToolTool(mcpToolManager) {
7556
6477
  }
7557
6478
 
7558
6479
  // src/chat/tools/skill/load-skill.ts
7559
- import { z as z14 } from "zod";
6480
+ import { z as z13 } from "zod";
7560
6481
  function toLoadedSkill(result, availableSkills) {
7561
6482
  if (result.ok !== true || typeof result.skill_name !== "string" || typeof result.description !== "string" || typeof result.skill_dir !== "string" || typeof result.instructions !== "string") {
7562
6483
  return null;
@@ -7605,8 +6526,8 @@ async function loadSkillFromHost(availableSkills, skillName) {
7605
6526
  function createLoadSkillTool(availableSkills, options) {
7606
6527
  return zodTool({
7607
6528
  description: "Load a skill by name for this turn. The result includes working_directory; resolve skill paths there and run skill-owned bash commands from there or with absolute paths. When the result includes mcp_provider, use searchMcpTools before callMcpTool. Use when a request clearly matches a known skill.",
7608
- inputSchema: z14.object({
7609
- skill_name: z14.string().min(1).describe("Skill name to load, without the leading slash.")
6529
+ inputSchema: z13.object({
6530
+ skill_name: z13.string().min(1).describe("Skill name to load, without the leading slash.")
7610
6531
  }),
7611
6532
  outputSchema: juniorToolResultSchema,
7612
6533
  execute: async ({ skill_name }) => {
@@ -7624,7 +6545,7 @@ function createLoadSkillTool(availableSkills, options) {
7624
6545
  }
7625
6546
 
7626
6547
  // src/chat/tools/skill/search-mcp-tools.ts
7627
- import { z as z15 } from "zod";
6548
+ import { z as z14 } from "zod";
7628
6549
 
7629
6550
  // src/chat/tool-support/schema-summary.ts
7630
6551
  function getSchemaProperties(schema) {
@@ -7754,37 +6675,37 @@ function toActiveMcpCatalogSummaries(toolDefs) {
7754
6675
  // src/chat/tools/skill/search-mcp-tools.ts
7755
6676
  var DEFAULT_MAX_RESULTS = 5;
7756
6677
  var MAX_RESULTS = 20;
7757
- var providerSummarySchema = z15.object({
7758
- provider: z15.string(),
7759
- description: z15.string(),
7760
- active: z15.boolean()
6678
+ var providerSummarySchema = z14.object({
6679
+ provider: z14.string(),
6680
+ description: z14.string(),
6681
+ active: z14.boolean()
7761
6682
  }).strict();
7762
- var mcpCallExampleSchema = z15.object({
7763
- tool_name: z15.string(),
7764
- arguments: z15.record(z15.string(), z15.string())
6683
+ var mcpCallExampleSchema = z14.object({
6684
+ tool_name: z14.string(),
6685
+ arguments: z14.record(z14.string(), z14.string())
7765
6686
  }).strict();
7766
- var exposedToolSummarySchema = z15.object({
7767
- tool_name: z15.string(),
7768
- mcp_tool_name: z15.string(),
7769
- provider: z15.string(),
7770
- title: z15.string().optional(),
7771
- description: z15.string(),
7772
- signature: z15.string(),
6687
+ var exposedToolSummarySchema = z14.object({
6688
+ tool_name: z14.string(),
6689
+ mcp_tool_name: z14.string(),
6690
+ provider: z14.string(),
6691
+ title: z14.string().optional(),
6692
+ description: z14.string(),
6693
+ signature: z14.string(),
7773
6694
  call: mcpCallExampleSchema,
7774
- input_schema: z15.record(z15.string(), z15.unknown()),
7775
- input_schema_summary: z15.string(),
7776
- output_schema: z15.record(z15.string(), z15.unknown()).optional(),
7777
- annotations: z15.record(z15.string(), z15.unknown()).optional()
6695
+ input_schema: z14.record(z14.string(), z14.unknown()),
6696
+ input_schema_summary: z14.string(),
6697
+ output_schema: z14.record(z14.string(), z14.unknown()).optional(),
6698
+ annotations: z14.record(z14.string(), z14.unknown()).optional()
7778
6699
  }).strict();
7779
6700
  var searchMcpToolsOutputSchema = juniorToolResultSchema.extend({
7780
- query: z15.string().nullable(),
7781
- provider: z15.string().nullable(),
7782
- total_active_tools: z15.number().int().nonnegative(),
7783
- returned_tools: z15.number().int().nonnegative(),
7784
- execution_tool: z15.literal("callMcpTool"),
6701
+ query: z14.string().nullable(),
6702
+ provider: z14.string().nullable(),
6703
+ total_active_tools: z14.number().int().nonnegative(),
6704
+ returned_tools: z14.number().int().nonnegative(),
6705
+ execution_tool: z14.literal("callMcpTool"),
7785
6706
  execution_example: mcpCallExampleSchema,
7786
- available_providers: z15.array(providerSummarySchema),
7787
- tools: z15.array(exposedToolSummarySchema)
6707
+ available_providers: z14.array(providerSummarySchema),
6708
+ tools: z14.array(exposedToolSummarySchema)
7788
6709
  }).strict();
7789
6710
  function normalize(value) {
7790
6711
  return value.toLowerCase().replace(/[^a-z0-9_]+/g, " ").trim();
@@ -7904,14 +6825,14 @@ function searchProviderCatalog(providers, query) {
7904
6825
  function createSearchMcpToolsTool(mcpToolManager) {
7905
6826
  return zodTool({
7906
6827
  description: "List or search MCP providers and active MCP tools. When provider is supplied and not yet active, Junior connects to it on demand and returns tool descriptors including schemas. Without provider, returns active tools plus matching configured providers without connecting. Use when choosing a provider tool or when callMcpTool arguments are unclear.",
7907
- inputSchema: z15.object({
7908
- query: z15.string().min(1).describe(
6828
+ inputSchema: z14.object({
6829
+ query: z14.string().min(1).describe(
7909
6830
  "Optional search terms describing the MCP tool or arguments needed."
7910
6831
  ).optional(),
7911
- provider: z15.string().min(1).describe(
6832
+ provider: z14.string().min(1).describe(
7912
6833
  "Optional provider name to list or search within. If configured but not yet connected, Junior activates it on demand."
7913
6834
  ).optional(),
7914
- max_results: z15.number().int().min(1).max(MAX_RESULTS).describe("Maximum matching tool descriptors to return.").optional()
6835
+ max_results: z14.number().int().min(1).max(MAX_RESULTS).describe("Maximum matching tool descriptors to return.").optional()
7915
6836
  }).strict(),
7916
6837
  outputSchema: searchMcpToolsOutputSchema,
7917
6838
  privateTraceResult: (result) => ({
@@ -7966,12 +6887,12 @@ function createSearchMcpToolsTool(mcpToolManager) {
7966
6887
  }
7967
6888
 
7968
6889
  // src/chat/tools/runtime/report-progress.ts
7969
- import { z as z16 } from "zod";
6890
+ import { z as z15 } from "zod";
7970
6891
  function createReportProgressTool() {
7971
6892
  return zodTool({
7972
6893
  description: "Update the user-visible assistant loading message with a short progress phase. For every non-trivial turn, call this early with the initial major work phase, then call it again only when the major phase meaningfully changes. Messages must be written in sentence case with a present-participle verb (e.g. 'Searching docs', 'Reviewing results', 'Running checks'). Skip trivial direct answers, generic filler, and minor substeps.",
7973
- inputSchema: z16.object({
7974
- message: z16.string().min(1).describe("Short user-facing progress message.")
6894
+ inputSchema: z15.object({
6895
+ message: z15.string().min(1).describe("Short user-facing progress message.")
7975
6896
  }),
7976
6897
  outputSchema: juniorToolResultSchema,
7977
6898
  execute: async () => ({
@@ -7982,34 +6903,34 @@ function createReportProgressTool() {
7982
6903
  }
7983
6904
 
7984
6905
  // src/chat/tools/resource-events.ts
7985
- import { z as z18 } from "zod";
6906
+ import { z as z17 } from "zod";
7986
6907
 
7987
6908
  // src/chat/resource-events/store.ts
7988
6909
  import { createHash } from "crypto";
7989
6910
  import { destinationSchema } from "@sentry/junior-plugin-api";
7990
- import { z as z17 } from "zod";
6911
+ import { z as z16 } from "zod";
7991
6912
  var RESOURCE_EVENT_PREFIX = "junior:resource_event_subscription";
7992
6913
  var INDEX_LOCK_TTL_MS = 1e4;
7993
6914
  var SUBSCRIPTION_LOCK_TTL_MS = 1e4;
7994
6915
  var SUBSCRIPTION_LOCK_WAIT_MS = 1e4;
7995
6916
  var SUBSCRIPTION_LOCK_RETRY_MS = 25;
7996
6917
  var SUBSCRIPTION_LOCK_HEARTBEAT_MS = 3e3;
7997
- var subscriptionStatusSchema = z17.enum(["active", "cancelled", "completed"]);
7998
- var subscriptionIdIndexSchema = z17.array(z17.string().min(1));
7999
- var subscriptionSchema = z17.object({
8000
- conversationId: z17.string().min(1),
8001
- createdAtMs: z17.number().finite(),
6918
+ var subscriptionStatusSchema = z16.enum(["active", "cancelled", "completed"]);
6919
+ var subscriptionIdIndexSchema = z16.array(z16.string().min(1));
6920
+ var subscriptionSchema = z16.object({
6921
+ conversationId: z16.string().min(1),
6922
+ createdAtMs: z16.number().finite(),
8002
6923
  destination: destinationSchema,
8003
- events: z17.array(z17.string().min(1)).min(1),
8004
- expiresAtMs: z17.number().finite(),
8005
- id: z17.string().min(1),
8006
- intent: z17.string().min(1),
8007
- label: z17.string().min(1),
8008
- provider: z17.string().min(1),
8009
- resourceRef: z17.string().min(1),
8010
- resourceType: z17.string().min(1),
6924
+ events: z16.array(z16.string().min(1)).min(1),
6925
+ expiresAtMs: z16.number().finite(),
6926
+ id: z16.string().min(1),
6927
+ intent: z16.string().min(1),
6928
+ label: z16.string().min(1),
6929
+ provider: z16.string().min(1),
6930
+ resourceRef: z16.string().min(1),
6931
+ resourceType: z16.string().min(1),
8011
6932
  status: subscriptionStatusSchema,
8012
- updatedAtMs: z17.number().finite()
6933
+ updatedAtMs: z16.number().finite()
8013
6934
  }).strict();
8014
6935
  function digest(value) {
8015
6936
  return createHash("sha256").update(value).digest("hex").slice(0, 32);
@@ -8365,18 +7286,18 @@ var RESOURCE_WATCH_TOOL_SOURCE = {
8365
7286
  id: "resource-watches",
8366
7287
  description: "Inspect or stop resource watches for the current conversation."
8367
7288
  };
8368
- var subscribeInputSchema = z18.object({
8369
- resourceRef: z18.string().describe("Opaque resource ref copied from a subscribable tool result."),
8370
- provider: z18.string().describe("Provider that owns the resource ref."),
8371
- resourceType: z18.string().describe("Provider-defined resource type from the subscribable hint."),
8372
- label: z18.string().describe("Human-readable resource label from the subscribable hint."),
8373
- events: z18.array(z18.string()).min(1).describe(
7289
+ var subscribeInputSchema = z17.object({
7290
+ resourceRef: z17.string().describe("Opaque resource ref copied from a subscribable tool result."),
7291
+ provider: z17.string().describe("Provider that owns the resource ref."),
7292
+ resourceType: z17.string().describe("Provider-defined resource type from the subscribable hint."),
7293
+ label: z17.string().describe("Human-readable resource label from the subscribable hint."),
7294
+ events: z17.array(z17.string()).min(1).describe(
8374
7295
  "High-signal event names to deliver to this conversation when they occur."
8375
7296
  ),
8376
- intent: z18.string().describe(
7297
+ intent: z17.string().describe(
8377
7298
  "Concise reason this conversation wants these events, used when an event arrives."
8378
7299
  ),
8379
- ttlMs: z18.coerce.number().describe(
7300
+ ttlMs: z17.coerce.number().describe(
8380
7301
  "How long to keep the subscription active. Defaults to 14 days and is capped at 30 days."
8381
7302
  ).optional()
8382
7303
  });
@@ -8469,7 +7390,7 @@ function createListResourceEventSubscriptionsTool(context) {
8469
7390
  description: "List active resource event subscriptions for the current conversation.",
8470
7391
  exposure: "deferred",
8471
7392
  source: RESOURCE_WATCH_TOOL_SOURCE,
8472
- inputSchema: z18.object({}),
7393
+ inputSchema: z17.object({}),
8473
7394
  outputSchema: juniorToolResultSchema,
8474
7395
  async execute() {
8475
7396
  const conversationId = requireConversationContext(context);
@@ -8502,7 +7423,7 @@ function createStopWatchingResourcesTool(context) {
8502
7423
  description: "Stop every resource watch for the current conversation. Infer the user's intent from context instead of requiring a special command, and call this tool before confirming that watching stopped.",
8503
7424
  exposure: "deferred",
8504
7425
  source: RESOURCE_WATCH_TOOL_SOURCE,
8505
- inputSchema: z18.object({}),
7426
+ inputSchema: z17.object({}),
8506
7427
  outputSchema: juniorToolResultSchema,
8507
7428
  async execute() {
8508
7429
  const conversationId = requireConversationContext(context);
@@ -8609,11 +7530,11 @@ async function listThreadReplies(input) {
8609
7530
  }
8610
7531
 
8611
7532
  // src/chat/slack/timestamp-param.ts
8612
- import { z as z19 } from "zod";
7533
+ import { z as z18 } from "zod";
8613
7534
  function slackTimestampParam(description) {
8614
- return z19.preprocess(
7535
+ return z18.preprocess(
8615
7536
  (value) => typeof value === "number" && Number.isFinite(value) ? String(value) : value,
8616
- z19.string().min(1)
7537
+ z18.string().min(1)
8617
7538
  ).describe(description);
8618
7539
  }
8619
7540
  function optionalSlackTimestampParam(description) {
@@ -8644,10 +7565,10 @@ function parseRequiredSlackTimestampParam(field, value) {
8644
7565
  }
8645
7566
 
8646
7567
  // src/chat/slack/tools/channel-list-messages.ts
8647
- import { z as z20 } from "zod";
8648
- var booleanInput2 = (description) => z20.preprocess(
7568
+ import { z as z19 } from "zod";
7569
+ var booleanInput2 = (description) => z19.preprocess(
8649
7570
  (value) => value === "true" ? true : value === "false" ? false : value,
8650
- z20.boolean()
7571
+ z19.boolean()
8651
7572
  ).describe(description);
8652
7573
  function normalizeRangeTimestamp(field, value, targetChannelId) {
8653
7574
  if (value === void 0) {
@@ -8674,9 +7595,9 @@ function createSlackChannelListMessagesTool(context) {
8674
7595
  return zodTool({
8675
7596
  description: "List channel messages from Slack history in the active channel context. Use when the user asks for recent or historical channel context outside this thread. Do not use for live monitoring or when current thread context already answers the question.",
8676
7597
  annotations: { readOnlyHint: true, destructiveHint: false },
8677
- inputSchema: z20.object({
8678
- limit: z20.coerce.number().int().min(1).max(1e3).describe("Maximum number of messages to return across pages.").optional(),
8679
- cursor: z20.string().min(1).describe("Optional cursor to continue from a prior call.").optional(),
7598
+ inputSchema: z19.object({
7599
+ limit: z19.coerce.number().int().min(1).max(1e3).describe("Maximum number of messages to return across pages.").optional(),
7600
+ cursor: z19.string().min(1).describe("Optional cursor to continue from a prior call.").optional(),
8680
7601
  oldest: optionalSlackTimestampParam(
8681
7602
  "Optional oldest message timestamp (Slack ts) for range filtering."
8682
7603
  ),
@@ -8686,7 +7607,7 @@ function createSlackChannelListMessagesTool(context) {
8686
7607
  inclusive: booleanInput2(
8687
7608
  "Whether oldest/latest bounds should be inclusive."
8688
7609
  ).optional(),
8689
- max_pages: z20.coerce.number().int().min(1).max(10).describe("Maximum number of API pages to traverse in a single call.").optional()
7610
+ max_pages: z19.coerce.number().int().min(1).max(10).describe("Maximum number of API pages to traverse in a single call.").optional()
8690
7611
  }),
8691
7612
  outputSchema: juniorToolResultSchema,
8692
7613
  execute: async ({
@@ -8760,20 +7681,20 @@ function createSlackChannelListMessagesTool(context) {
8760
7681
  }
8761
7682
 
8762
7683
  // src/chat/slack/tools/conversation-search.ts
8763
- import { z as z21 } from "zod";
7684
+ import { z as z20 } from "zod";
8764
7685
  var DEFAULT_LIMIT = 5;
8765
7686
  var conversationSearchOutputSchema = juniorToolResultSchema.extend({
8766
- query: z21.string(),
8767
- count: z21.number().int().nonnegative(),
8768
- threads: z21.array(
8769
- z21.object({
8770
- conversation_id: z21.string().min(1),
8771
- thread_ts: z21.string().min(1),
8772
- message_id: z21.string().min(1),
8773
- message_role: z21.enum(["assistant", "user"]),
8774
- message_timestamp: z21.string().datetime(),
8775
- excerpt: z21.string(),
8776
- permalink: z21.string().url().optional()
7687
+ query: z20.string(),
7688
+ count: z20.number().int().nonnegative(),
7689
+ threads: z20.array(
7690
+ z20.object({
7691
+ conversation_id: z20.string().min(1),
7692
+ thread_ts: z20.string().min(1),
7693
+ message_id: z20.string().min(1),
7694
+ message_role: z20.enum(["assistant", "user"]),
7695
+ message_timestamp: z20.string().datetime(),
7696
+ excerpt: z20.string(),
7697
+ permalink: z20.string().url().optional()
8777
7698
  }).strict()
8778
7699
  )
8779
7700
  });
@@ -8786,9 +7707,9 @@ function createSlackConversationSearchTool(scope, currentConversationId, deps =
8786
7707
  description: "Search retained public Junior conversation threads in the current Slack workspace."
8787
7708
  },
8788
7709
  annotations: { readOnlyHint: true, destructiveHint: false },
8789
- inputSchema: z21.object({
8790
- query: z21.string().trim().min(1).max(200).describe("Words or a short phrase to find in prior conversations."),
8791
- limit: z21.number().int().min(1).max(10).describe("Maximum number of prior conversation threads to return.").nullable().optional()
7710
+ inputSchema: z20.object({
7711
+ query: z20.string().trim().min(1).max(200).describe("Words or a short phrase to find in prior conversations."),
7712
+ limit: z20.number().int().min(1).max(10).describe("Maximum number of prior conversation threads to return.").nullable().optional()
8792
7713
  }),
8793
7714
  outputSchema: conversationSearchOutputSchema,
8794
7715
  execute: async ({ query, limit }) => {
@@ -8835,27 +7756,27 @@ function createSlackConversationSearchTool(scope, currentConversationId, deps =
8835
7756
  }
8836
7757
 
8837
7758
  // src/chat/slack/tools/public-search.ts
8838
- import { z as z22 } from "zod";
7759
+ import { z as z21 } from "zod";
8839
7760
  var DEFAULT_LIMIT2 = 10;
8840
- var optionalTimestampSchema = z22.preprocess(
7761
+ var optionalTimestampSchema = z21.preprocess(
8841
7762
  (value) => typeof value === "string" && value.trim() === "" ? void 0 : value,
8842
- z22.coerce.number().int().nonnegative().describe("Optional Unix timestamp bound.").optional()
7763
+ z21.coerce.number().int().nonnegative().describe("Optional Unix timestamp bound.").optional()
8843
7764
  );
8844
- var searchMessageSchema = z22.object({
8845
- author_name: z22.string().optional(),
8846
- author_user_id: z22.string().optional(),
8847
- channel_id: z22.string().min(1),
8848
- channel_name: z22.string().optional(),
8849
- message_ts: z22.string().min(1),
8850
- content: z22.string(),
8851
- is_author_bot: z22.boolean().optional(),
8852
- permalink: z22.string().url()
7765
+ var searchMessageSchema = z21.object({
7766
+ author_name: z21.string().optional(),
7767
+ author_user_id: z21.string().optional(),
7768
+ channel_id: z21.string().min(1),
7769
+ channel_name: z21.string().optional(),
7770
+ message_ts: z21.string().min(1),
7771
+ content: z21.string(),
7772
+ is_author_bot: z21.boolean().optional(),
7773
+ permalink: z21.string().url()
8853
7774
  });
8854
7775
  var publicSearchOutputSchema = juniorToolResultSchema.extend({
8855
- query: z22.string(),
8856
- count: z22.number().int().nonnegative(),
8857
- messages: z22.array(searchMessageSchema),
8858
- next_cursor: z22.string().optional()
7776
+ query: z21.string(),
7777
+ count: z21.number().int().nonnegative(),
7778
+ messages: z21.array(searchMessageSchema),
7779
+ next_cursor: z21.string().optional()
8859
7780
  });
8860
7781
  function normalizeMessage(value) {
8861
7782
  const parsed = searchMessageSchema.safeParse(value);
@@ -8877,8 +7798,8 @@ function createSlackPublicSearchTool(actionToken) {
8877
7798
  return zodTool({
8878
7799
  description: "Search public Slack channel messages across the current workspace. Use when the user asks about company activity, announcements, public mentions, or context outside the active channel. Search only when requested or clearly needed, prefer focused keywords and time bounds, and cite returned permalinks. This never searches private channels or DMs.",
8879
7800
  annotations: { readOnlyHint: true, destructiveHint: false },
8880
- inputSchema: z22.object({
8881
- query: z22.string().trim().min(1).max(500).describe(
7801
+ inputSchema: z21.object({
7802
+ query: z21.string().trim().min(1).max(500).describe(
8882
7803
  "A focused Slack search query, including Slack search filters when useful."
8883
7804
  ),
8884
7805
  after: optionalTimestampSchema.describe(
@@ -8887,10 +7808,10 @@ function createSlackPublicSearchTool(actionToken) {
8887
7808
  before: optionalTimestampSchema.describe(
8888
7809
  "Optional Unix timestamp upper bound."
8889
7810
  ),
8890
- cursor: z22.string().min(1).describe("Cursor for the next result page.").optional(),
8891
- limit: z22.coerce.number().int().min(1).max(20).describe("Maximum results to return; Slack allows at most 20.").optional(),
8892
- sort: z22.enum(["score", "timestamp"]).describe("Rank by relevance or timestamp.").optional(),
8893
- sort_dir: z22.enum(["asc", "desc"]).describe("Sort direction.").optional()
7811
+ cursor: z21.string().min(1).describe("Cursor for the next result page.").optional(),
7812
+ limit: z21.coerce.number().int().min(1).max(20).describe("Maximum results to return; Slack allows at most 20.").optional(),
7813
+ sort: z21.enum(["score", "timestamp"]).describe("Rank by relevance or timestamp.").optional(),
7814
+ sort_dir: z21.enum(["asc", "desc"]).describe("Sort direction.").optional()
8894
7815
  }),
8895
7816
  outputSchema: publicSearchOutputSchema,
8896
7817
  execute: async ({
@@ -8956,7 +7877,7 @@ function createSlackPublicSearchTool(actionToken) {
8956
7877
  }
8957
7878
 
8958
7879
  // src/chat/slack/tools/message-add-reaction.ts
8959
- import { z as z23 } from "zod";
7880
+ import { z as z22 } from "zod";
8960
7881
 
8961
7882
  // src/chat/tools/idempotency.ts
8962
7883
  function stableSerialize(value) {
@@ -8980,8 +7901,8 @@ function createOperationKey(toolName, input) {
8980
7901
  function createSlackMessageAddReactionTool(context, state) {
8981
7902
  return zodTool({
8982
7903
  description: "Add an emoji reaction to the current inbound Slack message. Use when the user asks for a reaction on the current message without another target. Provide a Slack emoji alias name (for example `thumbsup`, `white_check_mark`, or `thumbsup::skin-tone-6`), not a unicode emoji glyph. The target message is injected by runtime context; do not use this for arbitrary historical messages.",
8983
- inputSchema: z23.object({
8984
- emoji: z23.string().min(1).max(64).describe(
7904
+ inputSchema: z22.object({
7905
+ emoji: z22.string().min(1).max(64).describe(
8985
7906
  "Slack emoji alias name to react with (for example `thumbsup`, `white_check_mark`, or `thumbsup::skin-tone-6`). Optional surrounding colons are allowed."
8986
7907
  )
8987
7908
  }),
@@ -9032,18 +7953,18 @@ function createSlackMessageAddReactionTool(context, state) {
9032
7953
 
9033
7954
  // src/chat/slack/tools/send-files.ts
9034
7955
  import { createHash as createHash2 } from "crypto";
9035
- import { z as z24 } from "zod";
9036
- var sendFilesDataSchema = z24.object({
9037
- channel_id: z24.string().min(1),
9038
- deduplicated: z24.boolean().optional(),
9039
- file_count: z24.number().int().nonnegative(),
9040
- file_ids: z24.array(z24.string().min(1)).optional(),
9041
- thread_ts: z24.string().min(1)
7956
+ import { z as z23 } from "zod";
7957
+ var sendFilesDataSchema = z23.object({
7958
+ channel_id: z23.string().min(1),
7959
+ deduplicated: z23.boolean().optional(),
7960
+ file_count: z23.number().int().nonnegative(),
7961
+ file_ids: z23.array(z23.string().min(1)).optional(),
7962
+ thread_ts: z23.string().min(1)
9042
7963
  });
9043
7964
  var sendFilesResultSchema = juniorToolResultSchema.extend({
9044
- ok: z24.literal(true),
9045
- status: z24.literal("success"),
9046
- target: z24.string().min(1),
7965
+ ok: z23.literal(true),
7966
+ status: z23.literal("success"),
7967
+ target: z23.string().min(1),
9047
7968
  data: sendFilesDataSchema
9048
7969
  });
9049
7970
  function normalizeFiles(files) {
@@ -9068,8 +7989,8 @@ function fileOperationInput(files) {
9068
7989
  function createSendFilesTool(context, state, materializeFile) {
9069
7990
  return zodTool({
9070
7991
  description: "Send one or more sandbox files into the active Slack conversation. Use when the user asks to attach, send, or share files here, in this conversation, or in this thread. Do not use for ordinary assistant text, top-level channel posts, other named channels, inline @mentions, or pinging mentioned users.",
9071
- inputSchema: z24.object({
9072
- files: z24.array(sandboxFileReferenceSchema).min(1).describe(
7992
+ inputSchema: z23.object({
7993
+ files: z23.array(sandboxFileReferenceSchema).min(1).describe(
9073
7994
  "One or more existing sandbox files. Objects returned by imageGenerate or webFetch can be passed unchanged."
9074
7995
  )
9075
7996
  }),
@@ -9373,13 +8294,13 @@ function storedCanvasUrl(state, canvasId) {
9373
8294
  }
9374
8295
 
9375
8296
  // src/chat/slack/tools/canvas/create.ts
9376
- import { z as z25 } from "zod";
8297
+ import { z as z24 } from "zod";
9377
8298
  function createSlackCanvasCreateTool(context, state) {
9378
8299
  return zodTool({
9379
8300
  description: "Create a Slack canvas for long-form output in the active assistant context channel. Use when the answer is better as a reusable document than a thread reply: long-form research, timelines, bios/profiles, structured notes, plans, comparisons, or anything likely to exceed one compact Slack reply. After creating it, reply with one or two short sentences plus the canvas link; do not recap the canvas contents. Do not use for short answers that fit cleanly in one normal thread reply.",
9380
- inputSchema: z25.object({
9381
- title: z25.string().min(1).max(160).describe("Canvas title."),
9382
- markdown: z25.string().min(1).describe("Canvas markdown body content.")
8301
+ inputSchema: z24.object({
8302
+ title: z24.string().min(1).max(160).describe("Canvas title."),
8303
+ markdown: z24.string().min(1).describe("Canvas markdown body content.")
9383
8304
  }),
9384
8305
  outputSchema: juniorToolResultSchema,
9385
8306
  execute: async ({ title, markdown }) => {
@@ -9442,35 +8363,35 @@ function createSlackCanvasCreateTool(context, state) {
9442
8363
  }
9443
8364
 
9444
8365
  // src/chat/slack/tools/canvas/edit.ts
9445
- import { z as z26 } from "zod";
8366
+ import { z as z25 } from "zod";
9446
8367
  function prepareCanvasEditArguments(input) {
9447
8368
  return prepareTextReplacementArguments(input);
9448
8369
  }
9449
- var editReplacementSchema2 = z26.object({
9450
- oldText: z26.string().min(1).describe(
8370
+ var editReplacementSchema2 = z25.object({
8371
+ oldText: z25.string().min(1).describe(
9451
8372
  "Exact Canvas markdown to replace. It must be unique in the current Canvas body and must not overlap another edit."
9452
8373
  ),
9453
- newText: z26.string().describe("Replacement Canvas markdown for this edit.")
8374
+ newText: z25.string().describe("Replacement Canvas markdown for this edit.")
9454
8375
  });
9455
8376
  var slackCanvasEditOutputSchema = juniorToolResultSchema.extend({
9456
- canvas_id: z26.string().optional(),
9457
- title: z26.string().optional(),
9458
- permalink: z26.string().optional(),
9459
- diff: z26.string().optional(),
9460
- first_changed_line: z26.number().int().positive().optional(),
9461
- replacements: z26.number().int().nonnegative().optional(),
9462
- normalized_heading_count: z26.number().int().nonnegative().optional(),
9463
- summary: z26.string().optional(),
9464
- deduplicated: z26.boolean().optional()
8377
+ canvas_id: z25.string().optional(),
8378
+ title: z25.string().optional(),
8379
+ permalink: z25.string().optional(),
8380
+ diff: z25.string().optional(),
8381
+ first_changed_line: z25.number().int().positive().optional(),
8382
+ replacements: z25.number().int().nonnegative().optional(),
8383
+ normalized_heading_count: z25.number().int().nonnegative().optional(),
8384
+ summary: z25.string().optional(),
8385
+ deduplicated: z25.boolean().optional()
9465
8386
  }).strict();
9466
8387
  function createSlackCanvasEditTool(state) {
9467
8388
  return zodTool({
9468
8389
  description: "Edit one Slack canvas with exact markdown replacements. Use for precise changes to existing Canvas content; prefer this over slackCanvasWrite for targeted changes. Each oldText must match exactly, be unique, and not overlap another edit. Returns a diff. Multiple changes to the same canvas: use one edits[] call.",
9469
8390
  prepareArguments: prepareCanvasEditArguments,
9470
8391
  executionMode: "sequential",
9471
- inputSchema: z26.object({
9472
- canvas: z26.string().min(1).describe("Canvas/file ID (e.g. `F0ABCDEF`) or Slack canvas/docs URL."),
9473
- edits: z26.array(editReplacementSchema2).min(1).describe(
8392
+ inputSchema: z25.object({
8393
+ canvas: z25.string().min(1).describe("Canvas/file ID (e.g. `F0ABCDEF`) or Slack canvas/docs URL."),
8394
+ edits: z25.array(editReplacementSchema2).min(1).describe(
9474
8395
  "Exact replacements matched against the current Canvas body, not incrementally."
9475
8396
  )
9476
8397
  }),
@@ -9548,29 +8469,29 @@ function createSlackCanvasEditTool(state) {
9548
8469
  }
9549
8470
 
9550
8471
  // src/chat/slack/tools/canvas/read.ts
9551
- import { z as z27 } from "zod";
8472
+ import { z as z26 } from "zod";
9552
8473
  var slackCanvasReadOutputSchema = juniorToolResultSchema.extend({
9553
- canvas_id: z27.string().optional(),
9554
- title: z27.string().optional(),
9555
- permalink: z27.string().optional(),
9556
- mimetype: z27.string().optional(),
9557
- filetype: z27.string().optional(),
9558
- original_byte_length: z27.number().int().nonnegative().optional(),
9559
- content: z27.string().optional(),
9560
- start_line: z27.number().int().positive().optional(),
9561
- end_line: z27.number().int().nonnegative().optional(),
9562
- total_lines: z27.number().int().nonnegative().optional()
8474
+ canvas_id: z26.string().optional(),
8475
+ title: z26.string().optional(),
8476
+ permalink: z26.string().optional(),
8477
+ mimetype: z26.string().optional(),
8478
+ filetype: z26.string().optional(),
8479
+ original_byte_length: z26.number().int().nonnegative().optional(),
8480
+ content: z26.string().optional(),
8481
+ start_line: z26.number().int().positive().optional(),
8482
+ end_line: z26.number().int().nonnegative().optional(),
8483
+ total_lines: z26.number().int().nonnegative().optional()
9563
8484
  }).strict();
9564
8485
  function createSlackCanvasReadTool() {
9565
8486
  return zodTool({
9566
8487
  description: "Read a bounded line range from a Slack canvas as markdown. Use when you need exact Canvas contents to verify facts or make edits safely. Do not use for generic web pages \u2014 use webFetch for those.",
9567
8488
  annotations: { readOnlyHint: true, destructiveHint: false },
9568
- inputSchema: z27.object({
9569
- canvas: z27.string().min(1).describe(
8489
+ inputSchema: z26.object({
8490
+ canvas: z26.string().min(1).describe(
9570
8491
  "Canvas/file ID (e.g. `F0ABCDEF`) or Slack canvas/docs URL (e.g. `https://team.slack.com/docs/T.../F...`)."
9571
8492
  ),
9572
- offset: z27.coerce.number().int().min(1).describe("1-indexed line number to start reading from.").optional(),
9573
- limit: z27.coerce.number().int().min(1).describe("Maximum number of lines to read. Defaults to 1000.").optional()
8493
+ offset: z26.coerce.number().int().min(1).describe("1-indexed line number to start reading from.").optional(),
8494
+ limit: z26.coerce.number().int().min(1).describe("Maximum number of lines to read. Defaults to 1000.").optional()
9574
8495
  }),
9575
8496
  outputSchema: slackCanvasReadOutputSchema,
9576
8497
  execute: async ({ canvas, offset, limit }) => {
@@ -9627,14 +8548,14 @@ function createSlackCanvasReadTool() {
9627
8548
  }
9628
8549
 
9629
8550
  // src/chat/slack/tools/canvas/write.ts
9630
- import { z as z28 } from "zod";
8551
+ import { z as z27 } from "zod";
9631
8552
  function createSlackCanvasWriteTool(state) {
9632
8553
  return zodTool({
9633
8554
  description: "Write UTF-8 markdown content to a Slack canvas. Use for deliberate full-Canvas replacement after validation; use slackCanvasEdit for targeted changes to existing canvas content.",
9634
8555
  executionMode: "sequential",
9635
- inputSchema: z28.object({
9636
- canvas: z28.string().min(1).describe("Canvas/file ID (e.g. `F0ABCDEF`) or Slack canvas/docs URL."),
9637
- content: z28.string().describe("UTF-8 markdown content to write.")
8556
+ inputSchema: z27.object({
8557
+ canvas: z27.string().min(1).describe("Canvas/file ID (e.g. `F0ABCDEF`) or Slack canvas/docs URL."),
8558
+ content: z27.string().describe("UTF-8 markdown content to write.")
9638
8559
  }),
9639
8560
  outputSchema: juniorToolResultSchema,
9640
8561
  execute: async ({ canvas, content }) => {
@@ -9870,12 +8791,12 @@ async function updateListItem(input) {
9870
8791
  }
9871
8792
 
9872
8793
  // src/chat/slack/id-param.ts
9873
- import { z as z29 } from "zod";
8794
+ import { z as z28 } from "zod";
9874
8795
  function slackChannelIdParam(description) {
9875
- return z29.string().min(1).describe(description);
8796
+ return z28.string().min(1).describe(description);
9876
8797
  }
9877
8798
  function slackUserIdParam(description) {
9878
- return z29.string().min(1).describe(description);
8799
+ return z28.string().min(1).describe(description);
9879
8800
  }
9880
8801
  function parseRequiredSlackChannelIdParam(field, value) {
9881
8802
  const channelId = parseSlackChannelId(value);
@@ -9899,16 +8820,16 @@ function parseRequiredSlackUserIdParam(field, value) {
9899
8820
  }
9900
8821
 
9901
8822
  // src/chat/slack/tools/list/add-items.ts
9902
- import { z as z30 } from "zod";
8823
+ import { z as z29 } from "zod";
9903
8824
  function createSlackListAddItemsTool(state) {
9904
8825
  return zodTool({
9905
8826
  description: "Add tasks to the active Slack list tracked in artifact context. Use when the user wants actionable items recorded in the current thread list. Do not use when no list exists and list creation was not requested.",
9906
- inputSchema: z30.object({
9907
- items: z30.array(z30.string().min(1)).min(1).max(25).describe("List item titles to create."),
8827
+ inputSchema: z29.object({
8828
+ items: z29.array(z29.string().min(1)).min(1).max(25).describe("List item titles to create."),
9908
8829
  assignee_user_id: slackUserIdParam(
9909
8830
  "Optional Slack user ID assigned to all created items."
9910
8831
  ).optional(),
9911
- due_date: z30.string().regex(/^\d{4}-\d{2}-\d{2}$/).describe("Optional due date in YYYY-MM-DD format.").optional()
8832
+ due_date: z29.string().regex(/^\d{4}-\d{2}-\d{2}$/).describe("Optional due date in YYYY-MM-DD format.").optional()
9912
8833
  }),
9913
8834
  outputSchema: juniorToolResultSchema,
9914
8835
  execute: async ({ items, assignee_user_id, due_date }) => {
@@ -9962,12 +8883,12 @@ function createSlackListAddItemsTool(state) {
9962
8883
  }
9963
8884
 
9964
8885
  // src/chat/slack/tools/list/create.ts
9965
- import { z as z31 } from "zod";
8886
+ import { z as z30 } from "zod";
9966
8887
  function createSlackListCreateTool(state) {
9967
8888
  return zodTool({
9968
8889
  description: "Create a Slack todo list for action tracking. Use when the user needs structured tasks with ownership/completion tracking. Do not use for one-off notes without task management needs.",
9969
- inputSchema: z31.object({
9970
- name: z31.string().min(1).max(160).describe("Name for the new Slack list.")
8890
+ inputSchema: z30.object({
8891
+ name: z30.string().min(1).max(160).describe("Name for the new Slack list.")
9971
8892
  }),
9972
8893
  outputSchema: juniorToolResultSchema,
9973
8894
  execute: async ({ name }) => {
@@ -9999,13 +8920,13 @@ function createSlackListCreateTool(state) {
9999
8920
  }
10000
8921
 
10001
8922
  // src/chat/slack/tools/list/get-items.ts
10002
- import { z as z32 } from "zod";
8923
+ import { z as z31 } from "zod";
10003
8924
  function createSlackListGetItemsTool(state) {
10004
8925
  return zodTool({
10005
8926
  description: "Read items from the active Slack list tracked in artifact context. Use when the user asks for task status, open items, or list contents. Do not use when list state is already known from the immediately prior result.",
10006
8927
  annotations: { readOnlyHint: true, destructiveHint: false },
10007
- inputSchema: z32.object({
10008
- limit: z32.coerce.number().int().min(1).max(200).describe("Maximum number of list items to return.").optional()
8928
+ inputSchema: z31.object({
8929
+ limit: z31.coerce.number().int().min(1).max(200).describe("Maximum number of list items to return.").optional()
10009
8930
  }),
10010
8931
  outputSchema: juniorToolResultSchema,
10011
8932
  execute: async ({ limit }) => {
@@ -10030,21 +8951,21 @@ function createSlackListGetItemsTool(state) {
10030
8951
  }
10031
8952
 
10032
8953
  // src/chat/slack/tools/list/update-item.ts
10033
- import { z as z33 } from "zod";
10034
- var booleanInput3 = (description) => z33.preprocess(
8954
+ import { z as z32 } from "zod";
8955
+ var booleanInput3 = (description) => z32.preprocess(
10035
8956
  (value) => value === "true" ? true : value === "false" ? false : value,
10036
- z33.boolean()
8957
+ z32.boolean()
10037
8958
  ).describe(description);
10038
- var updateListItemInputSchema = z33.union([
10039
- z33.object({
10040
- item_id: z33.string().min(1).describe("ID of the Slack list item to update."),
8959
+ var updateListItemInputSchema = z32.union([
8960
+ z32.object({
8961
+ item_id: z32.string().min(1).describe("ID of the Slack list item to update."),
10041
8962
  completed: booleanInput3("Optional completion status update."),
10042
- title: z33.string().min(1).describe("Optional new item title.").optional()
8963
+ title: z32.string().min(1).describe("Optional new item title.").optional()
10043
8964
  }),
10044
- z33.object({
10045
- item_id: z33.string().min(1).describe("ID of the Slack list item to update."),
8965
+ z32.object({
8966
+ item_id: z32.string().min(1).describe("ID of the Slack list item to update."),
10046
8967
  completed: booleanInput3("Optional completion status update.").optional(),
10047
- title: z33.string().min(1).describe("Optional new item title.")
8968
+ title: z32.string().min(1).describe("Optional new item title.")
10048
8969
  })
10049
8970
  ]);
10050
8971
  function createSlackListUpdateItemTool(state) {
@@ -10120,7 +9041,7 @@ async function checkSlackChannelReadAccess(args) {
10120
9041
  }
10121
9042
 
10122
9043
  // src/chat/slack/tools/thread-read.ts
10123
- import { z as z34 } from "zod";
9044
+ import { z as z33 } from "zod";
10124
9045
 
10125
9046
  // src/chat/slack/tools/slack-message-url.ts
10126
9047
  var SLACK_HOST_PATTERN = /^[a-z0-9-]+\.slack(?:-gov)?\.com$/;
@@ -10293,8 +9214,8 @@ function createSlackThreadReadTool(context, deps = {}) {
10293
9214
  return zodTool({
10294
9215
  description: "Read a Slack thread from a shared Slack message archive URL or explicit channel + timestamp. Use when the user shares a Slack message link (https://*.slack.com/archives/...) and you need the referenced message and its thread context. Only the current conversation and public channels Junior has seen in this workspace are readable.",
10295
9216
  annotations: { readOnlyHint: true, destructiveHint: false },
10296
- inputSchema: z34.object({
10297
- url: z34.string().min(1).describe(
9217
+ inputSchema: z33.object({
9218
+ url: z33.string().min(1).describe(
10298
9219
  "Slack message archive URL, e.g. https://workspace.slack.com/archives/C123/p1700000000123456"
10299
9220
  ).optional(),
10300
9221
  channel_id: slackChannelIdParam(
@@ -10303,8 +9224,8 @@ function createSlackThreadReadTool(context, deps = {}) {
10303
9224
  ts: slackTimestampParam(
10304
9225
  "Slack message timestamp (e.g. 1700000000.123456). May be the thread root or any message in the thread."
10305
9226
  ).optional(),
10306
- limit: z34.coerce.number().int().min(1).max(1e3).describe("Maximum number of thread messages to fetch.").optional(),
10307
- max_pages: z34.coerce.number().int().min(1).max(10).describe("Maximum number of Slack API pages to traverse.").optional()
9227
+ limit: z33.coerce.number().int().min(1).max(1e3).describe("Maximum number of thread messages to fetch.").optional(),
9228
+ max_pages: z33.coerce.number().int().min(1).max(10).describe("Maximum number of Slack API pages to traverse.").optional()
10308
9229
  }),
10309
9230
  outputSchema: juniorToolResultSchema,
10310
9231
  execute: async ({ url, channel_id, ts, limit, max_pages }) => {
@@ -10560,29 +9481,29 @@ async function searchSlackUsers(options) {
10560
9481
  }
10561
9482
 
10562
9483
  // src/chat/slack/tools/user-lookup.ts
10563
- import { z as z35 } from "zod";
10564
- var booleanInput4 = (description) => z35.preprocess(
9484
+ import { z as z34 } from "zod";
9485
+ var booleanInput4 = (description) => z34.preprocess(
10565
9486
  (value) => value === "true" ? true : value === "false" ? false : value,
10566
- z35.boolean()
9487
+ z34.boolean()
10567
9488
  ).describe(description);
10568
9489
  function createSlackUserLookupTool() {
10569
9490
  return zodTool({
10570
9491
  description: "Look up Slack user profiles by user ID, email, or name search. Use when you need to identify a user, resolve cross-platform identity, or look up profile details like title or status. Returns profile fields including custom fields. For user ID lookup, pass a Slack user ID (e.g. U039RR91S). For search, pass a name query.",
10571
9492
  annotations: { readOnlyHint: true, destructiveHint: false },
10572
- inputSchema: z35.object({
9493
+ inputSchema: z34.object({
10573
9494
  user_id: slackUserIdParam(
10574
9495
  "Slack user ID to look up (e.g. U039RR91S). Mutually exclusive with email and query."
10575
9496
  ).optional(),
10576
- email: z35.string().min(3).describe(
9497
+ email: z34.string().min(3).describe(
10577
9498
  "Email address to look up. Mutually exclusive with user_id and query."
10578
9499
  ).optional(),
10579
- query: z35.string().min(2).describe(
9500
+ query: z34.string().min(2).describe(
10580
9501
  "Name to search for (matches against username, display name, real name). Mutually exclusive with user_id and email."
10581
9502
  ).optional(),
10582
- limit: z35.coerce.number().int().min(1).max(20).describe(
9503
+ limit: z34.coerce.number().int().min(1).max(20).describe(
10583
9504
  "Maximum number of results to return for name search. Defaults to 10."
10584
9505
  ).optional(),
10585
- max_pages: z35.coerce.number().int().min(1).max(5).describe(
9506
+ max_pages: z34.coerce.number().int().min(1).max(5).describe(
10586
9507
  "Maximum number of Slack API pages to scan for name search. Defaults to 3."
10587
9508
  ).optional(),
10588
9509
  include_bots: booleanInput4(
@@ -10686,18 +9607,18 @@ function createSlackUserLookupTool() {
10686
9607
  }
10687
9608
 
10688
9609
  // src/chat/tools/system-time.ts
10689
- import { z as z36 } from "zod";
9610
+ import { z as z35 } from "zod";
10690
9611
  var systemTimeOutputSchema = juniorToolResultSchema.extend({
10691
- unix_ms: z36.number(),
10692
- iso_utc: z36.string(),
10693
- iso_local: z36.string(),
10694
- timezone_offset_minutes: z36.number()
9612
+ unix_ms: z35.number(),
9613
+ iso_utc: z35.string(),
9614
+ iso_local: z35.string(),
9615
+ timezone_offset_minutes: z35.number()
10695
9616
  });
10696
9617
  function createSystemTimeTool() {
10697
9618
  return zodTool({
10698
9619
  description: "Return current system time in UTC and local ISO formats. Use when the user asks for current time/date context. Do not use as a substitute for historical or timezone-conversion research.",
10699
9620
  annotations: { readOnlyHint: true, destructiveHint: false },
10700
- inputSchema: z36.object({}),
9621
+ inputSchema: z35.object({}),
10701
9622
  outputSchema: systemTimeOutputSchema,
10702
9623
  privateTraceResult: (result) => ({
10703
9624
  ok: result.ok,
@@ -10726,10 +9647,10 @@ function createSystemTimeTool() {
10726
9647
  }
10727
9648
 
10728
9649
  // src/chat/tools/handoff/tool.ts
10729
- import { z as z37 } from "zod";
9650
+ import { z as z36 } from "zod";
10730
9651
  var HANDOFF_TOOL_NAME = "handoff";
10731
9652
  function createHandoffTool(handoff) {
10732
- const profileSchema = z37.enum(handoff.profiles);
9653
+ const profileSchema = z36.enum(handoff.profiles);
10733
9654
  const handoffResultSchema = juniorToolResultSchema.extend({
10734
9655
  model_profile: profileSchema
10735
9656
  });
@@ -10737,7 +9658,7 @@ function createHandoffTool(handoff) {
10737
9658
  return zodTool({
10738
9659
  description: `Switch to another execution profile and continue the same task. Profiles: ${profileNames}. Call this as the only tool.`,
10739
9660
  executionMode: "sequential",
10740
- inputSchema: z37.object({
9661
+ inputSchema: z36.object({
10741
9662
  profile: profileSchema.describe("Target execution profile")
10742
9663
  }).strict(),
10743
9664
  outputSchema: handoffResultSchema,
@@ -10760,7 +9681,7 @@ function createHandoffTool(handoff) {
10760
9681
  }
10761
9682
 
10762
9683
  // src/chat/tools/web/fetch-tool.ts
10763
- import { z as z38 } from "zod";
9684
+ import { z as z37 } from "zod";
10764
9685
 
10765
9686
  // src/chat/tools/web/constants.ts
10766
9687
  var USER_AGENT = "junior-bot/0.1";
@@ -11210,9 +10131,9 @@ function createWebFetchTool(hooks, options = {}) {
11210
10131
  destructiveHint: false,
11211
10132
  openWorldHint: true
11212
10133
  },
11213
- inputSchema: z38.object({
11214
- url: z38.string().min(1).describe("HTTP(S) URL to fetch."),
11215
- max_chars: z38.coerce.number().int().min(500).max(MAX_FETCH_CHARS).describe("Optional maximum number of extracted characters to return.").optional()
10134
+ inputSchema: z37.object({
10135
+ url: z37.string().min(1).describe("HTTP(S) URL to fetch."),
10136
+ max_chars: z37.coerce.number().int().min(500).max(MAX_FETCH_CHARS).describe("Optional maximum number of extracted characters to return.").optional()
11216
10137
  }),
11217
10138
  outputSchema: juniorToolResultSchema,
11218
10139
  execute: async ({ url, max_chars }) => {
@@ -11273,7 +10194,7 @@ function createWebFetchTool(hooks, options = {}) {
11273
10194
  }
11274
10195
 
11275
10196
  // src/chat/tools/web/search.ts
11276
- import { z as z39 } from "zod";
10197
+ import { z as z38 } from "zod";
11277
10198
  import { generateText } from "ai";
11278
10199
  import { createGatewayProvider } from "@ai-sdk/gateway";
11279
10200
  var SEARCH_TIMEOUT_MS = 6e4;
@@ -11322,9 +10243,9 @@ function createWebSearchTool(modelId, override) {
11322
10243
  destructiveHint: false,
11323
10244
  openWorldHint: true
11324
10245
  },
11325
- inputSchema: z39.object({
11326
- query: z39.string().min(1).max(500).describe("Search query."),
11327
- max_results: z39.coerce.number().int().min(1).max(MAX_RESULTS2).describe("Max results to return.").optional()
10246
+ inputSchema: z38.object({
10247
+ query: z38.string().min(1).max(500).describe("Search query."),
10248
+ max_results: z38.coerce.number().int().min(1).max(MAX_RESULTS2).describe("Max results to return.").optional()
11328
10249
  }),
11329
10250
  outputSchema: juniorToolResultSchema,
11330
10251
  execute: async ({ query, max_results }) => {
@@ -11393,14 +10314,14 @@ function createWebSearchTool(modelId, override) {
11393
10314
  }
11394
10315
 
11395
10316
  // src/chat/tools/sandbox/write-file.ts
11396
- import { z as z40 } from "zod";
10317
+ import { z as z39 } from "zod";
11397
10318
  function createWriteFileTool() {
11398
10319
  return zodTool({
11399
10320
  description: "Write UTF-8 content to a file in the sandbox workspace. Use for intentional file creation or deliberate full-file replacement after validation; use editFile instead for targeted changes to existing files. Do not use for exploratory analysis-only turns.",
11400
10321
  executionMode: "sequential",
11401
- inputSchema: z40.object({
11402
- path: z40.string().min(1).describe("Path to write in the sandbox workspace."),
11403
- content: z40.string().describe("UTF-8 file content to write.")
10322
+ inputSchema: z39.object({
10323
+ path: z39.string().min(1).describe("Path to write in the sandbox workspace."),
10324
+ content: z39.string().describe("UTF-8 file content to write.")
11404
10325
  }),
11405
10326
  outputSchema: juniorToolResultSchema,
11406
10327
  execute: async () => {
@@ -11759,11 +10680,11 @@ function replaceGeneratedTextContent(content, text) {
11759
10680
  ...withoutFirstGeneratedTextContent(content)
11760
10681
  ];
11761
10682
  }
11762
- function isRecord4(value) {
10683
+ function isRecord3(value) {
11763
10684
  return Boolean(value && typeof value === "object" && !Array.isArray(value));
11764
10685
  }
11765
10686
  function hasContinuation(value) {
11766
- return isRecord4(value) && isRecord4(value.continuation);
10687
+ return isRecord3(value) && isRecord3(value.continuation);
11767
10688
  }
11768
10689
  function stringField(record, key) {
11769
10690
  const value = record[key];
@@ -11778,7 +10699,7 @@ function stringListField(record, key) {
11778
10699
  return Array.isArray(value) ? value.filter((item) => typeof item === "string") : [];
11779
10700
  }
11780
10701
  function accountText(value) {
11781
- if (!isRecord4(value)) {
10702
+ if (!isRecord3(value)) {
11782
10703
  return void 0;
11783
10704
  }
11784
10705
  const label = stringField(value, "label") || stringField(value, "id");
@@ -11789,7 +10710,7 @@ function accountText(value) {
11789
10710
  return id && id !== label ? `${label} (${id})` : label;
11790
10711
  }
11791
10712
  function upstreamPermissionDeniedText(value) {
11792
- if (!isRecord4(value) || !isRecord4(value.permission_denied)) {
10713
+ if (!isRecord3(value) || !isRecord3(value.permission_denied)) {
11793
10714
  return void 0;
11794
10715
  }
11795
10716
  const signal = value.permission_denied;
@@ -11804,7 +10725,7 @@ function upstreamPermissionDeniedText(value) {
11804
10725
  if (!provider || !message || !upstreamHost || !upstreamPath || !status) {
11805
10726
  return void 0;
11806
10727
  }
11807
- const grant = isRecord4(signal.grant) ? signal.grant : {};
10728
+ const grant = isRecord3(signal.grant) ? signal.grant : {};
11808
10729
  const grantName = stringField(grant, "name");
11809
10730
  const grantAccess = stringField(grant, "access");
11810
10731
  const grantReason = stringField(grant, "reason");
@@ -11927,9 +10848,6 @@ function pluginAuthRequiredSignal(details) {
11927
10848
  ...parsedSignal.authorization ? { authorization: parsedSignal.authorization } : {}
11928
10849
  };
11929
10850
  }
11930
- function authorizationId(args) {
11931
- return `${args.sessionId}:${args.kind}:${args.provider}`;
11932
- }
11933
10851
  function createPluginAuthOrchestration(input) {
11934
10852
  let pendingPause;
11935
10853
  const startAuthorizationPause = async (provider, options) => {
@@ -11966,7 +10884,8 @@ function createPluginAuthOrchestration(input) {
11966
10884
  threadTs: input.threadTs,
11967
10885
  ...options?.scope ? { scope: options.scope } : {},
11968
10886
  resumeConversationId: input.conversationId,
11969
- resumeSessionId: input.sessionId
10887
+ resumeSessionId: input.sessionId,
10888
+ authorization: input.authorization
11970
10889
  });
11971
10890
  if (!oauthResult.ok) {
11972
10891
  throw new Error(oauthResult.error);
@@ -12144,17 +11063,17 @@ function handleToolExecutionError(error, toolName, toolCallId, shouldTrace, trac
12144
11063
  }
12145
11064
 
12146
11065
  // src/chat/tools/execute-tool.ts
12147
- import { z as z41 } from "zod";
11066
+ import { z as z40 } from "zod";
12148
11067
  var EXECUTE_TOOL_NAME = "executeTool";
12149
11068
  function createExecuteToolTool() {
12150
11069
  return zodTool({
12151
11070
  description: "Execute any catalog tool by exact tool_name from searchTools or a tool-result execution hint. Put tool-specific parameters inside arguments.",
12152
11071
  executionMode: "sequential",
12153
- inputSchema: z41.object({
12154
- tool_name: z41.string().min(1).describe(
11072
+ inputSchema: z40.object({
11073
+ tool_name: z40.string().min(1).describe(
12155
11074
  "Exact catalog tool_name returned by searchTools or a tool-result execution hint."
12156
11075
  ),
12157
- arguments: z41.record(z41.string(), z41.unknown()).describe(
11076
+ arguments: z40.record(z40.string(), z40.unknown()).describe(
12158
11077
  'Arguments matching the selected catalog tool schema, for example { "query": "..." }.'
12159
11078
  ).optional()
12160
11079
  }).strict(),
@@ -12194,42 +11113,42 @@ function planToolExposure(tools) {
12194
11113
  }
12195
11114
 
12196
11115
  // src/chat/tools/search-tools.ts
12197
- import { z as z42 } from "zod";
11116
+ import { z as z41 } from "zod";
12198
11117
  var SEARCH_TOOLS_NAME = "searchTools";
12199
11118
  var DEFAULT_MAX_RESULTS2 = 5;
12200
11119
  var MAX_RESULTS3 = 20;
12201
11120
  var MODEL_VISIBLE_DESCRIPTION_CAP = 180;
12202
- var searchToolsSourceSchema = z42.object({
12203
- id: z42.string(),
12204
- description: z42.string()
11121
+ var searchToolsSourceSchema = z41.object({
11122
+ id: z41.string(),
11123
+ description: z41.string()
12205
11124
  }).strict();
12206
- var toolCallExampleSchema = z42.object({
12207
- tool_name: z42.string(),
12208
- arguments: z42.record(z42.string(), z42.string())
11125
+ var toolCallExampleSchema = z41.object({
11126
+ tool_name: z41.string(),
11127
+ arguments: z41.record(z41.string(), z41.string())
12209
11128
  }).strict();
12210
- var searchToolsToolSchema = z42.object({
12211
- tool_name: z42.string(),
12212
- description: z42.string(),
12213
- exposure: z42.enum(["direct", "deferred", "modelOnly", "hidden"]),
12214
- source: z42.string().optional(),
12215
- signature: z42.string(),
11129
+ var searchToolsToolSchema = z41.object({
11130
+ tool_name: z41.string(),
11131
+ description: z41.string(),
11132
+ exposure: z41.enum(["direct", "deferred", "modelOnly", "hidden"]),
11133
+ source: z41.string().optional(),
11134
+ signature: z41.string(),
12216
11135
  call: toolCallExampleSchema,
12217
- input_schema: z42.unknown(),
12218
- input_schema_summary: z42.string(),
12219
- call_notes: z42.array(z42.string()),
12220
- annotations: z42.record(z42.string(), z42.unknown())
11136
+ input_schema: z41.unknown(),
11137
+ input_schema_summary: z41.string(),
11138
+ call_notes: z41.array(z41.string()),
11139
+ annotations: z41.record(z41.string(), z41.unknown())
12221
11140
  }).strict();
12222
11141
  var searchToolsOutputSchema = juniorToolResultSchema.extend({
12223
- query: z42.string().nullable(),
12224
- source: z42.string().nullable(),
12225
- sources: z42.array(searchToolsSourceSchema),
12226
- total_catalog_tools: z42.number().int().nonnegative(),
12227
- total_eligible_tools: z42.number().int().nonnegative(),
12228
- total_matches: z42.number().int().nonnegative(),
12229
- returned_tools: z42.number().int().nonnegative(),
12230
- execution_tool: z42.literal("executeTool"),
11142
+ query: z41.string().nullable(),
11143
+ source: z41.string().nullable(),
11144
+ sources: z41.array(searchToolsSourceSchema),
11145
+ total_catalog_tools: z41.number().int().nonnegative(),
11146
+ total_eligible_tools: z41.number().int().nonnegative(),
11147
+ total_matches: z41.number().int().nonnegative(),
11148
+ returned_tools: z41.number().int().nonnegative(),
11149
+ execution_tool: z41.literal("executeTool"),
12231
11150
  execution_example: toolCallExampleSchema,
12232
- tools: z42.array(searchToolsToolSchema)
11151
+ tools: z41.array(searchToolsToolSchema)
12233
11152
  }).strict();
12234
11153
  function normalize2(value) {
12235
11154
  return value.toLowerCase().replace(/[^a-z0-9_]+/g, " ").trim();
@@ -12421,14 +11340,14 @@ function createSearchToolsTool(catalogTools) {
12421
11340
  return zodTool({
12422
11341
  description: renderSearchToolsDescription(knownSources),
12423
11342
  annotations: { readOnlyHint: true, destructiveHint: false },
12424
- inputSchema: z42.object({
12425
- query: z42.string().nullable().describe(
11343
+ inputSchema: z41.object({
11344
+ query: z41.string().nullable().describe(
12426
11345
  "Optional search terms describing the tool, owner, action, or arguments needed. Empty string lists catalog tools."
12427
11346
  ).optional(),
12428
- source: z42.string().nullable().describe(
11347
+ source: z41.string().nullable().describe(
12429
11348
  "Optional source id to search within, such as a plugin source returned in sources."
12430
11349
  ).optional(),
12431
- max_results: z42.number().int().min(1).max(MAX_RESULTS3).nullable().describe("Maximum matching catalog tool descriptors to return.").optional()
11350
+ max_results: z41.number().int().min(1).max(MAX_RESULTS3).nullable().describe("Maximum matching catalog tool descriptors to return.").optional()
12432
11351
  }).strict(),
12433
11352
  outputSchema: searchToolsOutputSchema,
12434
11353
  privateTraceResult: (result) => ({
@@ -12675,570 +11594,43 @@ function createPiAgentTools(tools, sandbox, spanContext, onStatus, sandboxTools,
12675
11594
  params: parsed,
12676
11595
  signal,
12677
11596
  setSpanAttributes: setSpanAttributes2,
12678
- toolDef,
12679
- toolName
12680
- });
12681
- } catch (error) {
12682
- await notifyToolResult({
12683
- error: error instanceof Error ? error.message : String(error),
12684
- ok: false,
12685
- params: executionParams,
12686
- toolCallId,
12687
- toolName: executionToolName
12688
- });
12689
- if (error instanceof AuthorizationPauseError || error instanceof AuthorizationFlowDisabledError) {
12690
- throw error;
12691
- }
12692
- handleToolExecutionError(
12693
- error,
12694
- executionToolName,
12695
- toolCallId,
12696
- shouldTrace,
12697
- spanContext,
12698
- effectiveConversationPrivacy,
12699
- setSpanAttributes2
12700
- );
12701
- }
12702
- },
12703
- {
12704
- "gen_ai.operation.name": "execute_tool",
12705
- "gen_ai.tool.name": toolName,
12706
- "gen_ai.tool.description": toolDef.description,
12707
- "gen_ai.tool.type": "function",
12708
- ...toolArgumentsMetadata,
12709
- "gen_ai.tool.call.id": toolCallId,
12710
- ...toolArgumentsAttribute ? { "gen_ai.tool.call.arguments": toolArgumentsAttribute } : {}
12711
- }
12712
- );
12713
- }
12714
- }));
12715
- }
12716
-
12717
- // src/chat/mcp/oauth.ts
12718
- import { randomUUID as randomUUID3 } from "crypto";
12719
- import { StreamableHTTPClientTransport as StreamableHTTPClientTransport2 } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
12720
-
12721
- // src/chat/mcp/auth-store.ts
12722
- import {
12723
- sourceSchema as sourceSchema2
12724
- } from "@sentry/junior-plugin-api";
12725
- var MCP_AUTH_SESSION_PREFIX = "junior:mcp_oauth_attempt:v2";
12726
- var MCP_AUTH_CREDENTIALS_PREFIX = "junior:mcp_auth_credentials";
12727
- var MCP_AUTH_SESSION_INDEX_PREFIX = "junior:mcp_oauth_attempt_index:v2";
12728
- var MCP_SERVER_SESSION_PREFIX = "junior:mcp_server_session";
12729
- var MCP_AUTH_SESSION_TTL_MS = 24 * 60 * 60 * 1e3;
12730
- var MCP_AUTH_CREDENTIALS_TTL_MS = 30 * 24 * 60 * 60 * 1e3;
12731
- var MCP_SERVER_SESSION_TTL_MS = 24 * 60 * 60 * 1e3;
12732
- function sessionKey(authSessionId) {
12733
- return `${MCP_AUTH_SESSION_PREFIX}:${authSessionId}`;
12734
- }
12735
- function credentialsKey(userId, provider) {
12736
- return `${MCP_AUTH_CREDENTIALS_PREFIX}:${userId}:${provider}`;
12737
- }
12738
- function sessionIndexKey(userId, provider) {
12739
- return `${MCP_AUTH_SESSION_INDEX_PREFIX}:${userId}:${provider}`;
12740
- }
12741
- function serverSessionKey(userId, provider) {
12742
- return `${MCP_SERVER_SESSION_PREFIX}:${userId}:${provider}`;
12743
- }
12744
- function parseSessionIndex(value) {
12745
- if (typeof value !== "string") {
12746
- return [];
12747
- }
12748
- try {
12749
- const parsed = JSON.parse(value);
12750
- if (!Array.isArray(parsed)) {
12751
- return [];
12752
- }
12753
- return [
12754
- ...new Set(parsed.filter((id) => typeof id === "string"))
12755
- ];
12756
- } catch {
12757
- return [];
12758
- }
12759
- }
12760
- function parseMcpAuthSession(value) {
12761
- if (typeof value !== "string") {
12762
- return void 0;
12763
- }
12764
- try {
12765
- const parsed = JSON.parse(value);
12766
- if (!isRecord(parsed)) {
12767
- return void 0;
12768
- }
12769
- if (parsed.schemaVersion !== 2 || typeof parsed.authSessionId !== "string" || typeof parsed.provider !== "string" || typeof parsed.userId !== "string" || typeof parsed.conversationId !== "string" || typeof parsed.sessionId !== "string" || typeof parsed.userMessage !== "string" || typeof parsed.createdAtMs !== "number" || typeof parsed.updatedAtMs !== "number") {
12770
- return void 0;
12771
- }
12772
- const destination = parsed.destination === void 0 ? void 0 : parseDestination(parsed.destination);
12773
- if (parsed.destination !== void 0 && !destination) {
12774
- return void 0;
12775
- }
12776
- const source = parsed.source === void 0 ? void 0 : sourceSchema2.safeParse(parsed.source);
12777
- if (parsed.source !== void 0 && (!source || !source.success)) {
12778
- return void 0;
12779
- }
12780
- return {
12781
- schemaVersion: 2,
12782
- authSessionId: parsed.authSessionId,
12783
- provider: parsed.provider,
12784
- userId: parsed.userId,
12785
- conversationId: parsed.conversationId,
12786
- ...destination ? { destination } : {},
12787
- ...source?.success ? { source: source.data } : {},
12788
- sessionId: parsed.sessionId,
12789
- userMessage: parsed.userMessage,
12790
- createdAtMs: parsed.createdAtMs,
12791
- updatedAtMs: parsed.updatedAtMs,
12792
- ...typeof parsed.channelId === "string" ? { channelId: parsed.channelId } : {},
12793
- ...typeof parsed.threadTs === "string" ? { threadTs: parsed.threadTs } : {},
12794
- ...typeof parsed.toolChannelId === "string" ? { toolChannelId: parsed.toolChannelId } : {},
12795
- ...isRecord(parsed.configuration) ? { configuration: parsed.configuration } : {},
12796
- ...isRecord(parsed.artifactState) ? { artifactState: parsed.artifactState } : {},
12797
- ...typeof parsed.authorizationUrl === "string" ? { authorizationUrl: parsed.authorizationUrl } : {},
12798
- ...typeof parsed.codeVerifier === "string" ? { codeVerifier: parsed.codeVerifier } : {}
12799
- };
12800
- } catch {
12801
- return void 0;
12802
- }
12803
- }
12804
- function parseServerSession(value) {
12805
- if (typeof value !== "string") {
12806
- return void 0;
12807
- }
12808
- try {
12809
- const parsed = JSON.parse(value);
12810
- if (!isRecord(parsed) || typeof parsed.sessionId !== "string" || typeof parsed.updatedAtMs !== "number") {
12811
- return void 0;
12812
- }
12813
- return {
12814
- sessionId: parsed.sessionId,
12815
- updatedAtMs: parsed.updatedAtMs
12816
- };
12817
- } catch {
12818
- return void 0;
12819
- }
12820
- }
12821
- function parseStoredCredentials(value) {
12822
- if (typeof value !== "string") {
12823
- return void 0;
12824
- }
12825
- try {
12826
- const parsed = JSON.parse(value);
12827
- if (!isRecord(parsed)) {
12828
- return void 0;
12829
- }
12830
- return {
12831
- ...isRecord(parsed.clientInformation) ? {
12832
- clientInformation: parsed.clientInformation
12833
- } : {},
12834
- ...isRecord(parsed.discoveryState) ? {
12835
- discoveryState: parsed.discoveryState
12836
- } : {},
12837
- ...isRecord(parsed.tokens) ? { tokens: parsed.tokens } : {}
12838
- };
12839
- } catch {
12840
- return void 0;
12841
- }
12842
- }
12843
- async function getConnectedStateAdapter() {
12844
- const stateAdapter = getStateAdapter();
12845
- await stateAdapter.connect();
12846
- return stateAdapter;
12847
- }
12848
- async function getMcpAuthSession(authSessionId) {
12849
- const stateAdapter = await getConnectedStateAdapter();
12850
- return parseMcpAuthSession(await stateAdapter.get(sessionKey(authSessionId)));
12851
- }
12852
- async function putMcpAuthSession(session, ttlMs2 = MCP_AUTH_SESSION_TTL_MS) {
12853
- const stateAdapter = await getConnectedStateAdapter();
12854
- await stateAdapter.set(
12855
- sessionKey(session.authSessionId),
12856
- JSON.stringify(session),
12857
- ttlMs2
12858
- );
12859
- const nextIndex = parseSessionIndex(
12860
- await stateAdapter.get(sessionIndexKey(session.userId, session.provider))
12861
- );
12862
- if (!nextIndex.includes(session.authSessionId)) {
12863
- nextIndex.push(session.authSessionId);
12864
- }
12865
- await stateAdapter.set(
12866
- sessionIndexKey(session.userId, session.provider),
12867
- JSON.stringify(nextIndex),
12868
- ttlMs2
12869
- );
12870
- }
12871
- async function patchMcpAuthSession(authSessionId, patch) {
12872
- const current = await getMcpAuthSession(authSessionId);
12873
- if (!current) {
12874
- throw new Error(`Unknown MCP auth session: ${authSessionId}`);
12875
- }
12876
- const next = {
12877
- ...current,
12878
- ...patch,
12879
- schemaVersion: 2,
12880
- authSessionId: current.authSessionId,
12881
- provider: current.provider,
12882
- userId: current.userId,
12883
- conversationId: current.conversationId,
12884
- ...current.destination ? { destination: current.destination } : {},
12885
- sessionId: current.sessionId,
12886
- userMessage: current.userMessage,
12887
- createdAtMs: current.createdAtMs,
12888
- updatedAtMs: Date.now()
12889
- };
12890
- await putMcpAuthSession(next);
12891
- return next;
12892
- }
12893
- async function deleteMcpAuthSession(authSessionId) {
12894
- const stateAdapter = await getConnectedStateAdapter();
12895
- const current = parseMcpAuthSession(
12896
- await stateAdapter.get(sessionKey(authSessionId))
12897
- );
12898
- await stateAdapter.delete(sessionKey(authSessionId));
12899
- if (!current) {
12900
- return;
12901
- }
12902
- const nextIndex = parseSessionIndex(
12903
- await stateAdapter.get(sessionIndexKey(current.userId, current.provider))
12904
- ).filter((id) => id !== authSessionId);
12905
- if (nextIndex.length > 0) {
12906
- await stateAdapter.set(
12907
- sessionIndexKey(current.userId, current.provider),
12908
- JSON.stringify(nextIndex),
12909
- MCP_AUTH_SESSION_TTL_MS
12910
- );
12911
- return;
12912
- }
12913
- await stateAdapter.delete(sessionIndexKey(current.userId, current.provider));
12914
- }
12915
- async function deleteMcpAuthSessionsForUserProvider(userId, provider) {
12916
- const stateAdapter = await getConnectedStateAdapter();
12917
- const indexKey = sessionIndexKey(userId, provider);
12918
- const authSessionIds = parseSessionIndex(await stateAdapter.get(indexKey));
12919
- for (const authSessionId of authSessionIds) {
12920
- await stateAdapter.delete(sessionKey(authSessionId));
12921
- }
12922
- await stateAdapter.delete(indexKey);
12923
- }
12924
- async function getMcpStoredOAuthCredentials(userId, provider) {
12925
- const stateAdapter = await getConnectedStateAdapter();
12926
- return parseStoredCredentials(
12927
- await stateAdapter.get(credentialsKey(userId, provider))
12928
- );
12929
- }
12930
- async function putMcpStoredOAuthCredentials(userId, provider, value, ttlMs2 = MCP_AUTH_CREDENTIALS_TTL_MS) {
12931
- const stateAdapter = await getConnectedStateAdapter();
12932
- await stateAdapter.set(
12933
- credentialsKey(userId, provider),
12934
- JSON.stringify(value),
12935
- ttlMs2
12936
- );
12937
- }
12938
- async function deleteMcpStoredOAuthCredentials(userId, provider) {
12939
- const stateAdapter = await getConnectedStateAdapter();
12940
- await stateAdapter.delete(credentialsKey(userId, provider));
12941
- }
12942
- async function getMcpServerSessionId(userId, provider) {
12943
- const stateAdapter = await getConnectedStateAdapter();
12944
- return parseServerSession(
12945
- await stateAdapter.get(serverSessionKey(userId, provider))
12946
- )?.sessionId;
12947
- }
12948
- async function putMcpServerSessionId(userId, provider, sessionId, ttlMs2 = MCP_SERVER_SESSION_TTL_MS) {
12949
- const stateAdapter = await getConnectedStateAdapter();
12950
- await stateAdapter.set(
12951
- serverSessionKey(userId, provider),
12952
- JSON.stringify({
12953
- sessionId,
12954
- updatedAtMs: Date.now()
12955
- }),
12956
- ttlMs2
12957
- );
12958
- }
12959
- async function deleteMcpServerSessionId(userId, provider) {
12960
- const stateAdapter = await getConnectedStateAdapter();
12961
- await stateAdapter.delete(serverSessionKey(userId, provider));
12962
- }
12963
-
12964
- // src/chat/mcp/oauth-provider.ts
12965
- function createClientMetadata(callbackUrl) {
12966
- return {
12967
- client_name: "Junior MCP Client",
12968
- redirect_uris: [callbackUrl],
12969
- grant_types: ["authorization_code", "refresh_token"],
12970
- response_types: ["code"],
12971
- token_endpoint_auth_method: "none"
12972
- };
12973
- }
12974
- function clientAllowsRedirectUri(clientInformation, callbackUrl) {
12975
- const redirectUris = clientInformation.redirect_uris;
12976
- if (!Array.isArray(redirectUris)) {
12977
- return true;
12978
- }
12979
- return redirectUris.includes(callbackUrl);
12980
- }
12981
- var StateBackedMcpOAuthClientProvider = class {
12982
- constructor(authSessionId, callbackUrl, sessionContext, runCredentialMutation) {
12983
- this.authSessionId = authSessionId;
12984
- this.callbackUrl = callbackUrl;
12985
- this.sessionContext = sessionContext;
12986
- this.runCredentialMutation = runCredentialMutation;
12987
- this.clientMetadata = createClientMetadata(callbackUrl);
12988
- }
12989
- authSessionId;
12990
- callbackUrl;
12991
- sessionContext;
12992
- runCredentialMutation;
12993
- clientMetadata;
12994
- get redirectUrl() {
12995
- return this.callbackUrl;
12996
- }
12997
- async state() {
12998
- return this.authSessionId;
12999
- }
13000
- async clientInformation() {
13001
- const session = await this.getCredentialContext();
13002
- const credentials = await getMcpStoredOAuthCredentials(
13003
- session.userId,
13004
- session.provider
13005
- );
13006
- const clientInformation = credentials?.clientInformation;
13007
- if (!clientInformation) {
13008
- return void 0;
13009
- }
13010
- if (clientAllowsRedirectUri(clientInformation, this.callbackUrl)) {
13011
- return clientInformation;
13012
- }
13013
- return void 0;
13014
- }
13015
- async saveClientInformation(clientInformation) {
13016
- await this.mutateCredentials(async () => {
13017
- const session = await this.getCredentialContext();
13018
- const credentials = await getMcpStoredOAuthCredentials(
13019
- session.userId,
13020
- session.provider
13021
- ) ?? {};
13022
- await putMcpStoredOAuthCredentials(session.userId, session.provider, {
13023
- ...credentials,
13024
- clientInformation
13025
- });
13026
- });
13027
- }
13028
- async tokens() {
13029
- const session = await this.getCredentialContext();
13030
- const credentials = await getMcpStoredOAuthCredentials(
13031
- session.userId,
13032
- session.provider
13033
- );
13034
- return credentials?.tokens;
13035
- }
13036
- async saveTokens(tokens) {
13037
- await this.mutateCredentials(async () => {
13038
- const session = await this.getCredentialContext();
13039
- const credentials = await getMcpStoredOAuthCredentials(
13040
- session.userId,
13041
- session.provider
13042
- ) ?? {};
13043
- await putMcpStoredOAuthCredentials(session.userId, session.provider, {
13044
- ...credentials,
13045
- tokens
13046
- });
13047
- });
13048
- }
13049
- async redirectToAuthorization(authorizationUrl) {
13050
- const existing = await getMcpAuthSession(this.authSessionId);
13051
- if (existing?.authorizationUrl && existing.authorizationUrl !== authorizationUrl.toString()) {
13052
- throw new Error("MCP OAuth authorization attempt is already initialized");
13053
- }
13054
- await this.ensureSession({
13055
- authorizationUrl: authorizationUrl.toString()
13056
- });
13057
- }
13058
- async saveCodeVerifier(codeVerifier) {
13059
- const existing = await getMcpAuthSession(this.authSessionId);
13060
- if (existing?.codeVerifier && existing.codeVerifier !== codeVerifier) {
13061
- throw new Error("MCP OAuth authorization attempt is already initialized");
13062
- }
13063
- await this.ensureSession({ codeVerifier });
13064
- }
13065
- async codeVerifier() {
13066
- const session = await this.requireSession();
13067
- if (!session.codeVerifier) {
13068
- throw new Error("Missing MCP OAuth code verifier");
13069
- }
13070
- return session.codeVerifier;
13071
- }
13072
- async saveDiscoveryState(state) {
13073
- await this.mutateCredentials(async () => {
13074
- const session = await this.getCredentialContext();
13075
- const credentials = await getMcpStoredOAuthCredentials(
13076
- session.userId,
13077
- session.provider
13078
- ) ?? {};
13079
- await putMcpStoredOAuthCredentials(session.userId, session.provider, {
13080
- ...credentials,
13081
- discoveryState: state
13082
- });
13083
- });
13084
- }
13085
- async discoveryState() {
13086
- const session = await this.getCredentialContext();
13087
- const credentials = await getMcpStoredOAuthCredentials(
13088
- session.userId,
13089
- session.provider
13090
- );
13091
- return credentials?.discoveryState;
13092
- }
13093
- async invalidateCredentials(scope) {
13094
- await this.mutateCredentials(async () => {
13095
- const session = await this.getCredentialContext();
13096
- const credentials = await getMcpStoredOAuthCredentials(
13097
- session.userId,
13098
- session.provider
13099
- ) ?? {};
13100
- await putMcpStoredOAuthCredentials(session.userId, session.provider, {
13101
- ...scope === "tokens" || scope === "all" ? {} : credentials.tokens ? { tokens: credentials.tokens } : {},
13102
- ...scope === "client" || scope === "all" ? {} : credentials.clientInformation ? { clientInformation: credentials.clientInformation } : {},
13103
- ...scope === "discovery" || scope === "all" ? {} : credentials.discoveryState ? { discoveryState: credentials.discoveryState } : {}
13104
- });
13105
- });
13106
- }
13107
- async getMcpServerSessionId() {
13108
- const session = await this.getCredentialContext();
13109
- return await getMcpServerSessionId(session.userId, session.provider);
13110
- }
13111
- async saveMcpServerSessionId(sessionId) {
13112
- await this.mutateCredentials(async () => {
13113
- const session = await this.getCredentialContext();
13114
- if (!sessionId) {
13115
- await deleteMcpServerSessionId(session.userId, session.provider);
13116
- return;
13117
- }
13118
- await putMcpServerSessionId(session.userId, session.provider, sessionId);
13119
- });
13120
- }
13121
- /** Route shared credential writes through callback-owned freshness control. */
13122
- async mutateCredentials(mutation) {
13123
- return this.runCredentialMutation ? await this.runCredentialMutation(mutation) : await mutation();
13124
- }
13125
- async getCredentialContext() {
13126
- return this.sessionContext ?? await this.requireSession();
13127
- }
13128
- async ensureSession(patch) {
13129
- const existing = await getMcpAuthSession(this.authSessionId);
13130
- if (existing) {
13131
- return await patchMcpAuthSession(this.authSessionId, patch);
13132
- }
13133
- if (!this.sessionContext) {
13134
- throw new Error(`Unknown MCP auth session: ${this.authSessionId}`);
13135
- }
13136
- const now = Date.now();
13137
- const nextSession = {
13138
- schemaVersion: 2,
13139
- authSessionId: this.authSessionId,
13140
- ...this.sessionContext,
13141
- ...patch,
13142
- createdAtMs: now,
13143
- updatedAtMs: now
13144
- };
13145
- await putMcpAuthSession(nextSession);
13146
- return nextSession;
13147
- }
13148
- async requireSession() {
13149
- const session = await getMcpAuthSession(this.authSessionId);
13150
- if (!session) {
13151
- throw new Error(`Unknown MCP auth session: ${this.authSessionId}`);
13152
- }
13153
- return session;
13154
- }
13155
- };
13156
-
13157
- // src/chat/mcp/oauth.ts
13158
- function getMcpOAuthCallbackPath(provider) {
13159
- return `/api/oauth/callback/mcp/${provider}`;
13160
- }
13161
- function requirePluginWithMcp(provider) {
13162
- const plugin = pluginCatalogRuntime.getDefinition(provider);
13163
- if (!plugin?.manifest.mcp) {
13164
- throw new Error(`Plugin "${provider}" does not support MCP`);
13165
- }
13166
- return plugin;
13167
- }
13168
- async function createMcpOAuthClientProvider(input) {
13169
- requirePluginWithMcp(input.provider);
13170
- const baseUrl = resolveBaseUrl();
13171
- if (!baseUrl) {
13172
- throw new Error(
13173
- "Cannot determine base URL (set JUNIOR_BASE_URL or deploy to Vercel)"
13174
- );
13175
- }
13176
- const authSessionId = randomUUID3();
13177
- return new StateBackedMcpOAuthClientProvider(
13178
- authSessionId,
13179
- `${baseUrl}${getMcpOAuthCallbackPath(input.provider)}`,
13180
- {
13181
- provider: input.provider,
13182
- userId: input.userId,
13183
- conversationId: input.conversationId,
13184
- ...input.destination ? { destination: input.destination } : {},
13185
- ...input.source ? { source: input.source } : {},
13186
- sessionId: input.sessionId,
13187
- userMessage: input.userMessage,
13188
- ...input.channelId ? { channelId: input.channelId } : {},
13189
- ...input.threadTs ? { threadTs: input.threadTs } : {},
13190
- ...input.toolChannelId ? { toolChannelId: input.toolChannelId } : {},
13191
- ...input.configuration ? { configuration: input.configuration } : {},
13192
- ...input.artifactState ? { artifactState: input.artifactState } : {}
11597
+ toolDef,
11598
+ toolName
11599
+ });
11600
+ } catch (error) {
11601
+ await notifyToolResult({
11602
+ error: error instanceof Error ? error.message : String(error),
11603
+ ok: false,
11604
+ params: executionParams,
11605
+ toolCallId,
11606
+ toolName: executionToolName
11607
+ });
11608
+ if (error instanceof AuthorizationPauseError || error instanceof AuthorizationFlowDisabledError) {
11609
+ throw error;
11610
+ }
11611
+ handleToolExecutionError(
11612
+ error,
11613
+ executionToolName,
11614
+ toolCallId,
11615
+ shouldTrace,
11616
+ spanContext,
11617
+ effectiveConversationPrivacy,
11618
+ setSpanAttributes2
11619
+ );
11620
+ }
11621
+ },
11622
+ {
11623
+ "gen_ai.operation.name": "execute_tool",
11624
+ "gen_ai.tool.name": toolName,
11625
+ "gen_ai.tool.description": toolDef.description,
11626
+ "gen_ai.tool.type": "function",
11627
+ ...toolArgumentsMetadata,
11628
+ "gen_ai.tool.call.id": toolCallId,
11629
+ ...toolArgumentsAttribute ? { "gen_ai.tool.call.arguments": toolArgumentsAttribute } : {}
11630
+ }
11631
+ );
13193
11632
  }
13194
- );
13195
- }
13196
- async function finalizeMcpAuthorization(provider, authSessionId, authorizationCode, runCredentialMutation) {
13197
- const plugin = requirePluginWithMcp(provider);
13198
- const mcp = plugin.manifest.mcp;
13199
- if (!mcp) {
13200
- throw new Error(`Plugin "${provider}" does not support MCP`);
13201
- }
13202
- const session = await getMcpAuthSession(authSessionId);
13203
- if (!session) {
13204
- throw new Error(`Unknown MCP auth session: ${authSessionId}`);
13205
- }
13206
- if (session.provider !== provider) {
13207
- throw new Error(
13208
- `MCP auth session provider mismatch: expected "${provider}", got "${session.provider}"`
13209
- );
13210
- }
13211
- const baseUrl = resolveBaseUrl();
13212
- if (!baseUrl) {
13213
- throw new Error(
13214
- "Cannot determine base URL (set JUNIOR_BASE_URL or deploy to Vercel)"
13215
- );
13216
- }
13217
- const callbackUrl = `${baseUrl}${getMcpOAuthCallbackPath(provider)}`;
13218
- const authProvider = new StateBackedMcpOAuthClientProvider(
13219
- authSessionId,
13220
- callbackUrl,
13221
- void 0,
13222
- runCredentialMutation
13223
- );
13224
- const requestInit = {};
13225
- if (mcp.headers && Object.keys(mcp.headers).length > 0) {
13226
- requestInit.headers = new Headers(mcp.headers);
13227
- }
13228
- const transport = new StreamableHTTPClientTransport2(new URL(mcp.url), {
13229
- ...Object.keys(requestInit).length > 0 ? { requestInit } : {},
13230
- authProvider
13231
- });
13232
- try {
13233
- await transport.finishAuth(authorizationCode);
13234
- } finally {
13235
- await transport.close();
13236
- }
13237
- const nextSession = await getMcpAuthSession(authSessionId);
13238
- if (!nextSession) {
13239
- throw new Error(`Unknown MCP auth session: ${authSessionId}`);
13240
- }
13241
- return nextSession;
11633
+ }));
13242
11634
  }
13243
11635
 
13244
11636
  // src/chat/services/mcp-auth-orchestration.ts
@@ -13247,9 +11639,6 @@ var McpAuthorizationPauseError = class extends AuthorizationPauseError {
13247
11639
  super("mcp", provider, providerDisplayName, disposition, requestText);
13248
11640
  }
13249
11641
  };
13250
- function authorizationId2(args) {
13251
- return `${args.sessionId}:${args.kind}:${args.provider}`;
13252
- }
13253
11642
  function createMcpAuthOrchestration(input) {
13254
11643
  let pendingPause;
13255
11644
  const authSessionIdsByProvider = /* @__PURE__ */ new Map();
@@ -13274,7 +11663,8 @@ function createMcpAuthOrchestration(input) {
13274
11663
  ...input.threadTs ? { threadTs: input.threadTs } : {},
13275
11664
  ...input.toolChannelId ? { toolChannelId: input.toolChannelId } : {},
13276
11665
  configuration: input.getConfiguration(),
13277
- artifactState: input.getArtifactState()
11666
+ artifactState: input.getArtifactState(),
11667
+ createAuthorizationState: input.authorization?.createState
13278
11668
  });
13279
11669
  authSessionIdsByProvider.set(plugin.manifest.name, provider.authSessionId);
13280
11670
  return provider;
@@ -13334,15 +11724,16 @@ function createMcpAuthOrchestration(input) {
13334
11724
  );
13335
11725
  }
13336
11726
  await recordPendingAuth(nextPendingAuth);
13337
- const delivery = await deliverPrivateMessage({
11727
+ const authorizationRequest = {
11728
+ authorizationUrl: authSession.authorizationUrl,
11729
+ label: `Click here to link your ${providerLabel} MCP access`,
11730
+ completionText: "Once you've authorized, Junior will continue automatically."
11731
+ };
11732
+ const delivery = await deliverOAuthAuthorization(authorizationRequest, {
11733
+ authorization: input.authorization,
13338
11734
  channelId: authSession.channelId,
13339
11735
  threadTs: authSession.threadTs,
13340
- userId: authSession.userId,
13341
- text: formatOAuthAuthorizationMessage({
13342
- authorizationUrl: authSession.authorizationUrl,
13343
- label: `Click here to link your ${providerLabel} MCP access`,
13344
- completionText: "Once you've authorized, this thread will continue automatically."
13345
- })
11736
+ userId: authSession.userId
13346
11737
  });
13347
11738
  if (!delivery) {
13348
11739
  await deleteMcpAuthSession(authSessionId);
@@ -13365,7 +11756,7 @@ function createMcpAuthOrchestration(input) {
13365
11756
  kind: "mcp",
13366
11757
  provider,
13367
11758
  actorId,
13368
- authorizationId: authorizationId2({
11759
+ authorizationId: authorizationId({
13369
11760
  kind: "mcp",
13370
11761
  provider,
13371
11762
  sessionId
@@ -13389,197 +11780,7 @@ function createMcpAuthOrchestration(input) {
13389
11780
  }
13390
11781
 
13391
11782
  // src/chat/egress/plugin.ts
13392
- import { randomUUID as randomUUID4 } from "crypto";
13393
-
13394
- // src/chat/plugins/credential-hooks.ts
13395
- import {
13396
- pluginAuthorizationSchema as pluginAuthorizationSchema2,
13397
- pluginCredentialResultSchema,
13398
- pluginGrantSchema as pluginGrantSchema2,
13399
- pluginProviderAccountSchema as pluginProviderAccountSchema2
13400
- } from "@sentry/junior-plugin-api";
13401
- function parseSchema(schema, value, message) {
13402
- const result = schema.safeParse(value);
13403
- if (!result.success) {
13404
- throw new Error(message);
13405
- }
13406
- return result.data;
13407
- }
13408
- function parseAuthorization(value, pluginName) {
13409
- if (value === void 0) {
13410
- return void 0;
13411
- }
13412
- const authorization = parseSchema(
13413
- pluginAuthorizationSchema2,
13414
- value,
13415
- `Plugin "${pluginName}" grant authorization is invalid`
13416
- );
13417
- if (authorization.provider !== pluginName) {
13418
- throw new Error(
13419
- `Plugin "${pluginName}" grant authorization provider must match the issuing plugin`
13420
- );
13421
- }
13422
- return authorization;
13423
- }
13424
- function parseGrant(value, pluginName) {
13425
- return parseSchema(
13426
- pluginGrantSchema2,
13427
- value,
13428
- `Plugin "${pluginName}" grantForEgress returned an invalid grant`
13429
- );
13430
- }
13431
- function pluginFor(provider) {
13432
- return getPlugins().find((candidate) => candidate.manifest.name === provider);
13433
- }
13434
- function basePluginContext(plugin) {
13435
- const pluginName = plugin.manifest.name;
13436
- return {
13437
- plugin: { name: pluginName },
13438
- log: createPluginLogger(pluginName),
13439
- db: getDb()
13440
- };
13441
- }
13442
- function parseCredentialResult(value, pluginName) {
13443
- const result = parseSchema(
13444
- pluginCredentialResultSchema,
13445
- value,
13446
- `Plugin "${pluginName}" issueCredential result is invalid`
13447
- );
13448
- if (result.type === "lease") {
13449
- parseAuthorization(result.lease.authorization, pluginName);
13450
- return result;
13451
- }
13452
- if (result.type === "unavailable") {
13453
- return result;
13454
- }
13455
- parseAuthorization(result.authorization, pluginName);
13456
- return result;
13457
- }
13458
- async function selectPluginGrant(input) {
13459
- const plugin = pluginFor(input.provider);
13460
- const hook = plugin?.hooks?.grantForEgress;
13461
- if (!plugin || !hook) {
13462
- return void 0;
13463
- }
13464
- const result = await hook({
13465
- ...basePluginContext(plugin),
13466
- request: {
13467
- ...input.bodyText !== void 0 ? { bodyText: input.bodyText } : {},
13468
- method: input.method,
13469
- ...input.operation ? { operation: input.operation } : {},
13470
- url: input.upstreamUrl.toString()
13471
- }
13472
- });
13473
- return result === void 0 ? void 0 : parseGrant(result, plugin.manifest.name);
13474
- }
13475
- async function onPluginEgressResponse(input) {
13476
- const plugin = pluginFor(input.provider);
13477
- const hook = plugin?.hooks?.onEgressResponse;
13478
- if (!plugin || !hook) {
13479
- return {};
13480
- }
13481
- let permissionDenied;
13482
- await hook({
13483
- ...basePluginContext(plugin),
13484
- grant: input.grant,
13485
- permissionDenied(message) {
13486
- const trimmed = message.trim();
13487
- if (!trimmed) {
13488
- throw new Error(
13489
- `Plugin "${plugin.manifest.name}" onEgressResponse permissionDenied message is empty`
13490
- );
13491
- }
13492
- permissionDenied = { message: trimmed };
13493
- },
13494
- request: {
13495
- method: input.method,
13496
- ...input.operation ? { operation: input.operation } : {},
13497
- url: input.upstreamUrl.toString()
13498
- },
13499
- response: input.response
13500
- });
13501
- return permissionDenied ? { permissionDenied } : {};
13502
- }
13503
- function hasEgressCredentialHooks(provider) {
13504
- const hooks = pluginFor(provider)?.hooks;
13505
- return Boolean(hooks?.grantForEgress || hooks?.issueCredential);
13506
- }
13507
- async function resolvePluginOAuthAccount(input) {
13508
- const plugin = pluginFor(input.provider);
13509
- const hook = plugin?.hooks?.resolveOAuthAccount;
13510
- if (!plugin || !hook) {
13511
- return void 0;
13512
- }
13513
- const account = await hook({
13514
- ...basePluginContext(plugin),
13515
- tokens: input.tokens
13516
- });
13517
- return account === void 0 ? void 0 : parseSchema(
13518
- pluginProviderAccountSchema2,
13519
- account,
13520
- `Plugin "${plugin.manifest.name}" resolveOAuthAccount returned an invalid account`
13521
- );
13522
- }
13523
- async function issuePluginCredential(input) {
13524
- const plugin = pluginFor(input.provider);
13525
- const hook = plugin?.hooks?.issueCredential;
13526
- if (!plugin || !hook) {
13527
- throw new Error(`Plugin "${input.provider}" has no issueCredential hook`);
13528
- }
13529
- const currentUserId = "type" in input.actor ? input.actor.userId : void 0;
13530
- const credentialSubjectUserId = input.credentialSubject?.userId;
13531
- const result = await hook({
13532
- ...basePluginContext(plugin),
13533
- actor: input.actor,
13534
- grant: input.grant,
13535
- ...input.credentialSubject ? { credentialSubject: input.credentialSubject } : {},
13536
- tokens: {
13537
- ...currentUserId ? {
13538
- currentUser: {
13539
- userId: currentUserId,
13540
- get: async () => await input.userTokenStore.get(
13541
- currentUserId,
13542
- plugin.manifest.name
13543
- ),
13544
- set: async (tokens) => {
13545
- await input.userTokenStore.set(
13546
- currentUserId,
13547
- plugin.manifest.name,
13548
- tokens
13549
- );
13550
- },
13551
- withRefresh: async (callback) => await input.userTokenStore.withRefresh(
13552
- currentUserId,
13553
- plugin.manifest.name,
13554
- callback
13555
- )
13556
- }
13557
- } : {},
13558
- ...credentialSubjectUserId ? {
13559
- credentialSubject: {
13560
- userId: credentialSubjectUserId,
13561
- get: async () => await input.userTokenStore.get(
13562
- credentialSubjectUserId,
13563
- plugin.manifest.name
13564
- ),
13565
- set: async (tokens) => {
13566
- await input.userTokenStore.set(
13567
- credentialSubjectUserId,
13568
- plugin.manifest.name,
13569
- tokens
13570
- );
13571
- },
13572
- withRefresh: async (callback) => await input.userTokenStore.withRefresh(
13573
- credentialSubjectUserId,
13574
- plugin.manifest.name,
13575
- callback
13576
- )
13577
- }
13578
- } : {}
13579
- }
13580
- });
13581
- return parseCredentialResult(result, plugin.manifest.name);
13582
- }
11783
+ import { randomUUID as randomUUID3 } from "crypto";
13583
11784
 
13584
11785
  // src/chat/sandbox/egress/credentials.ts
13585
11786
  var HTTP_READ_METHODS = /* @__PURE__ */ new Set(["GET", "HEAD", "OPTIONS"]);
@@ -14352,8 +12553,8 @@ async function executeCredentialedEgressRequest(input) {
14352
12553
  function credentialContextForPluginEgress(credentialContext) {
14353
12554
  return {
14354
12555
  credentials: credentialContext,
14355
- contextId: randomUUID4(),
14356
- egressId: `plugin-egress:${randomUUID4()}`,
12556
+ contextId: randomUUID3(),
12557
+ egressId: `plugin-egress:${randomUUID3()}`,
14357
12558
  expiresAtMs: Date.now() + 30 * 60 * 1e3
14358
12559
  };
14359
12560
  }
@@ -14424,6 +12625,7 @@ async function wireAgentTools(args) {
14424
12625
  skills: args.availableSkills,
14425
12626
  traceContext: args.spanContext,
14426
12627
  tracePropagation: args.policy.sandboxTracePropagation,
12628
+ egressSignals: args.policy.sandboxEgressSignals,
14427
12629
  credentialEgress: args.routing.credentialContext,
14428
12630
  actor: args.currentActor,
14429
12631
  channelConfiguration: args.policy.channelConfiguration,
@@ -14454,7 +12656,8 @@ async function wireAgentTools(args) {
14454
12656
  args.artifactStatePatch
14455
12657
  ),
14456
12658
  recordPendingAuth: args.durability.recordPendingAuth,
14457
- authorizationFlowMode: args.policy.authorizationFlowMode
12659
+ authorizationFlowMode: args.policy.authorizationFlowMode,
12660
+ authorization: args.authorization
14458
12661
  });
14459
12662
  const pluginAuth = createPluginAuthOrchestration({
14460
12663
  abortAgent: args.abortAgent,
@@ -14469,7 +12672,8 @@ async function wireAgentTools(args) {
14469
12672
  pendingAuth: args.state.pendingAuth,
14470
12673
  recordPendingAuth: args.durability.recordPendingAuth,
14471
12674
  authorizationFlowMode: args.policy.authorizationFlowMode,
14472
- userTokenStore
12675
+ userTokenStore,
12676
+ authorization: args.authorization
14473
12677
  });
14474
12678
  const mcpToolManager = new McpToolManager(
14475
12679
  pluginCatalogRuntime.getMcpProviders(),
@@ -14860,16 +13064,24 @@ function createResumeState(args) {
14860
13064
  }
14861
13065
 
14862
13066
  // src/chat/services/context-compaction.ts
14863
- import {
14864
- estimateContextTokens,
14865
- estimateTokens
14866
- } from "@earendil-works/pi-agent-core";
14867
13067
  var RETAINED_USER_MESSAGE_TOKENS = 2e4;
14868
13068
  var MAX_SUMMARY_INPUT_CHARS = 8e4;
14869
13069
  var MAX_VISIBLE_CONTEXT_CHARS = 2e4;
14870
13070
  var MAX_SUMMARY_CHARS = 6e3;
14871
13071
  var MAX_RENDERED_MESSAGE_CHARS = 4e3;
14872
13072
  var OMITTED_OLDER_CONTEXT_NOTICE = "[older context omitted]";
13073
+ var ContextInputLimitExceededError = class extends Error {
13074
+ constructor(estimatedTokens, inputLimitTokens) {
13075
+ super(
13076
+ `Agent context is ${estimatedTokens} estimated input tokens, above the ${inputLimitTokens} token limit`
13077
+ );
13078
+ this.estimatedTokens = estimatedTokens;
13079
+ this.inputLimitTokens = inputLimitTokens;
13080
+ this.name = "ContextInputLimitExceededError";
13081
+ }
13082
+ estimatedTokens;
13083
+ inputLimitTokens;
13084
+ };
14873
13085
  function textPart(value) {
14874
13086
  if (value && typeof value === "object" && value.type === "text" && typeof value.text === "string") {
14875
13087
  return value.text;
@@ -15030,13 +13242,10 @@ async function summarizeContext(args, deps) {
15030
13242
  return summary.slice(0, MAX_SUMMARY_CHARS);
15031
13243
  }
15032
13244
  function estimateHistoryTokens(messages) {
15033
- const stripped = stripRuntimeTurnContext(messages);
15034
- const usageEstimate = estimateContextTokens(stripped).tokens;
15035
- const structuralEstimate = stripped.reduce(
15036
- (total, message) => total + estimateTokens(message),
13245
+ return messages.reduce(
13246
+ (total, message) => total + estimateTextTokens(JSON.stringify(message) ?? ""),
15037
13247
  0
15038
13248
  );
15039
- return Math.max(usageEstimate, structuralEstimate);
15040
13249
  }
15041
13250
  function buildReplacementProvenance(args) {
15042
13251
  return [
@@ -15060,11 +13269,11 @@ async function maybeCompactWithDeps(args, deps) {
15060
13269
  if ("reason" in source) {
15061
13270
  return { compacted: false, reason: source.reason };
15062
13271
  }
15063
- const triggerTokens = deps.autoCompactionTriggerTokens ?? getAgentContextCompactionTriggerTokens();
13272
+ const triggerTokens = deps.autoCompactionTriggerTokens ?? getAgentContextCompactionTriggerTokens(args.modelId);
15064
13273
  if (source.estimatedTokens <= triggerTokens) {
15065
13274
  return { compacted: false, reason: "below_threshold" };
15066
13275
  }
15067
- args.onCompactionStart?.();
13276
+ await args.onCompactionStart?.();
15068
13277
  let summary;
15069
13278
  try {
15070
13279
  summary = await summarizeContext(
@@ -15095,7 +13304,8 @@ async function maybeCompactWithDeps(args, deps) {
15095
13304
  }
15096
13305
  return await writeCompactedThreadContext(args, source.messages, summary, {
15097
13306
  estimatedTokens: source.estimatedTokens,
15098
- triggerTokens
13307
+ triggerTokens,
13308
+ inputLimitTokens: getAgentContextInputLimitTokens(args.modelId)
15099
13309
  });
15100
13310
  }
15101
13311
  async function writeCompactedThreadContext(args, sourceMessages, summary, context) {
@@ -15111,6 +13321,7 @@ async function writeCompactedThreadContext(args, sourceMessages, summary, contex
15111
13321
  userMessage(`${COMPACTION_SUMMARY_PREFIX}
15112
13322
  ${summary}`)
15113
13323
  ];
13324
+ const replacementInputTokens = estimateHistoryTokens(replacement);
15114
13325
  const replacementProvenance = buildReplacementProvenance({
15115
13326
  retained,
15116
13327
  sourceProvenance: sourceProjection.provenance
@@ -15121,6 +13332,17 @@ ${summary}`)
15121
13332
  type: "compaction",
15122
13333
  modelProfile: sourceProjection.modelProfile,
15123
13334
  modelId: modelIdForProfile(botConfig, sourceProjection.modelProfile),
13335
+ details: {
13336
+ reason: "capacity",
13337
+ estimatedInputTokens: context.estimatedTokens,
13338
+ replacementInputTokens,
13339
+ triggerTokens: context.triggerTokens ?? getAgentContextCompactionTriggerTokens(args.modelId),
13340
+ inputLimitTokens: context.inputLimitTokens,
13341
+ inputMessageCount: sourceMessages.length,
13342
+ retainedMessageCount: replacement.length - 1,
13343
+ summaryChars: summary.length
13344
+ },
13345
+ summary,
15124
13346
  replacementHistory: replacement.map((message, index) => {
15125
13347
  const sourceEventSeq = index < retained.length ? sourceProjection.seqs[retained[index].sourceIndex] : void 0;
15126
13348
  return {
@@ -15154,8 +13376,9 @@ async function compactContextForHandoff(args, deps) {
15154
13376
  if (!runtimeMessage) {
15155
13377
  throw new Error("Handoff requires the current runtime turn context");
15156
13378
  }
13379
+ const generatedSummary = await summarizeContext(args, deps);
15157
13380
  const summary = `${MODEL_HANDOFF_SUMMARY_PREFIX}
15158
- ${await summarizeContext(args, deps)}`;
13381
+ ${generatedSummary}`;
15159
13382
  const message = {
15160
13383
  ...runtimeMessage,
15161
13384
  content: [
@@ -15174,6 +13397,7 @@ ${await summarizeContext(args, deps)}`;
15174
13397
  modelId: args.target.modelId,
15175
13398
  ...args.target.reasoningLevel ? { reasoningLevel: args.target.reasoningLevel } : {},
15176
13399
  ...args.triggeringToolCallId ? { triggeringToolCallId: args.triggeringToolCallId } : {},
13400
+ summary: generatedSummary,
15177
13401
  replacementHistory: replacementMessages.map((replacementMessage) => ({
15178
13402
  message: replacementMessage,
15179
13403
  provenance: contextProvenance
@@ -15182,6 +13406,115 @@ ${await summarizeContext(args, deps)}`;
15182
13406
  });
15183
13407
  return messages;
15184
13408
  }
13409
+ async function compactActiveContextIfNeeded(args, deps) {
13410
+ const pendingMessages = args.pendingMessages ?? [];
13411
+ const source = loadCompactionSource([
13412
+ ...args.piMessages,
13413
+ ...pendingMessages.map((entry) => entry.message)
13414
+ ]);
13415
+ if ("reason" in source) {
13416
+ return { compacted: false, reason: source.reason };
13417
+ }
13418
+ const triggerTokens = getAgentContextCompactionTriggerTokens(args.modelId);
13419
+ if (source.estimatedTokens <= triggerTokens) {
13420
+ return { compacted: false, reason: "below_threshold" };
13421
+ }
13422
+ await args.onCompactionStart?.();
13423
+ const inputLimitTokens = getAgentContextInputLimitTokens(args.modelId);
13424
+ let summary;
13425
+ try {
13426
+ summary = await summarizeContext(
13427
+ {
13428
+ ...args,
13429
+ piMessages: args.piMessages
13430
+ },
13431
+ deps
13432
+ );
13433
+ } catch (error) {
13434
+ if (source.estimatedTokens >= inputLimitTokens) {
13435
+ throw new ContextInputLimitExceededError(
13436
+ source.estimatedTokens,
13437
+ inputLimitTokens
13438
+ );
13439
+ }
13440
+ logWarn(
13441
+ "active_context_compaction_summary_failed",
13442
+ {
13443
+ messageConversationId: args.metadata?.threadId,
13444
+ userId: args.metadata?.actorId,
13445
+ destinationName: args.metadata?.channelId,
13446
+ runId: args.metadata?.runId,
13447
+ assistantUserName: botConfig.userName,
13448
+ modelId: args.modelId
13449
+ },
13450
+ {
13451
+ "app.context_input_limit_tokens": inputLimitTokens,
13452
+ "app.context_tokens_estimated": source.estimatedTokens,
13453
+ "exception.message": error instanceof Error ? error.message : String(error)
13454
+ },
13455
+ "Active context compaction failed below the hard input limit"
13456
+ );
13457
+ return { compacted: false, reason: "summary_failed" };
13458
+ }
13459
+ const summaryText = `${COMPACTION_SUMMARY_PREFIX}
13460
+ ${summary}`;
13461
+ const runtimeMessage = retainRuntimeTurnContext(args.piMessages).at(-1);
13462
+ const summaryMessages = runtimeMessage ? [
13463
+ {
13464
+ ...runtimeMessage,
13465
+ content: [
13466
+ ...runtimeMessage.content,
13467
+ { type: "text", text: renderCurrentInstruction(summaryText) }
13468
+ ]
13469
+ }
13470
+ ] : [userMessage(renderCurrentInstruction(summaryText))];
13471
+ const messages = [
13472
+ ...summaryMessages,
13473
+ ...pendingMessages.map((entry) => entry.message)
13474
+ ];
13475
+ const replacementInputTokens = estimateHistoryTokens(messages);
13476
+ if (replacementInputTokens >= inputLimitTokens) {
13477
+ throw new ContextInputLimitExceededError(
13478
+ replacementInputTokens,
13479
+ inputLimitTokens
13480
+ );
13481
+ }
13482
+ const replacementMessages = stripRuntimeTurnContext(messages);
13483
+ args.signal?.throwIfAborted();
13484
+ await getConversationEventStore().replaceHistory(args.conversationId, {
13485
+ createdAtMs: Date.now(),
13486
+ data: {
13487
+ type: "compaction",
13488
+ modelProfile: args.modelProfile,
13489
+ modelId: args.modelId,
13490
+ details: {
13491
+ reason: "capacity",
13492
+ estimatedInputTokens: source.estimatedTokens,
13493
+ replacementInputTokens,
13494
+ triggerTokens,
13495
+ inputLimitTokens,
13496
+ inputMessageCount: source.messages.length,
13497
+ retainedMessageCount: pendingMessages.length,
13498
+ summaryChars: summary.length
13499
+ },
13500
+ summary,
13501
+ replacementHistory: replacementMessages.map((message, index) => ({
13502
+ message,
13503
+ provenance: index === 0 ? contextProvenance : pendingMessages[index - 1]?.provenance ?? contextProvenance
13504
+ }))
13505
+ }
13506
+ });
13507
+ setSpanAttributes({
13508
+ "app.compaction.input_messages": source.messages.length,
13509
+ "app.compaction.replacement_tokens": replacementInputTokens,
13510
+ "app.compaction.retained_messages": pendingMessages.length,
13511
+ "app.compaction.summary_chars": summary.length,
13512
+ "app.compaction.trigger_tokens": triggerTokens,
13513
+ "app.context_input_limit_tokens": inputLimitTokens,
13514
+ "app.context_tokens_estimated": source.estimatedTokens
13515
+ });
13516
+ return { compacted: true, piMessages: messages };
13517
+ }
15185
13518
 
15186
13519
  // src/chat/agent/index.ts
15187
13520
  var AGENT_ABORT_SETTLE_GRACE_MS = 5e3;
@@ -15267,6 +13600,7 @@ async function executeAgentRunInPrivacyContext(request, conversationPrivacy, run
15267
13600
  const state = request.state ?? {};
15268
13601
  const observers = request.observers ?? {};
15269
13602
  const delivery = request.delivery;
13603
+ const authorization = request.authorization;
15270
13604
  const durability = request.durability ?? {};
15271
13605
  signal?.throwIfAborted();
15272
13606
  assertRunRoutingConsistency(request);
@@ -15280,7 +13614,7 @@ async function executeAgentRunInPrivacyContext(request, conversationPrivacy, run
15280
13614
  let mcpToolManager;
15281
13615
  let connectedMcpProviders = /* @__PURE__ */ new Set();
15282
13616
  let turnUsage;
15283
- let handoffPhaseUsage;
13617
+ let priorPhaseUsage;
15284
13618
  const configuredReasoningLevel = policy.reasoningLevel ?? botConfig.reasoningLevel;
15285
13619
  let turnRoute = configuredReasoningLevel ? configuredTurnRoute(
15286
13620
  STANDARD_MODEL_PROFILE,
@@ -15331,6 +13665,7 @@ async function executeAgentRunInPrivacyContext(request, conversationPrivacy, run
15331
13665
  const projection = await openConversationProjection({ conversationId });
15332
13666
  activeModelProfile = projection.modelProfile;
15333
13667
  activeModelId = modelIdForProfile(botConfig, activeModelProfile);
13668
+ let durableModelProfile = projection.modelProfile;
15334
13669
  const shouldTrace = shouldEmitDevAgentTrace();
15335
13670
  const spanContext = { modelId: activeModelId };
15336
13671
  const availableSkills = await discoverRunSkills({
@@ -15435,7 +13770,30 @@ async function executeAgentRunInPrivacyContext(request, conversationPrivacy, run
15435
13770
  });
15436
13771
  const { routerBlocks, userContentParts } = buildPromptInput(input);
15437
13772
  const preAgentPromptMessages = () => existingSessionRecord?.piMessages ?? [...input.piMessages ?? []];
15438
- if (activeModelProfile === STANDARD_MODEL_PROFILE) {
13773
+ const storedTurnRoute = await loadTurnRoute({ conversationId, turnId });
13774
+ if (storedTurnRoute) {
13775
+ const resumedAfterHandoff = activeModelProfile !== STANDARD_MODEL_PROFILE && activeModelProfile !== storedTurnRoute.modelProfile;
13776
+ if (resumedAfterHandoff) {
13777
+ const activeProfileConfig = profileConfig(
13778
+ botConfig,
13779
+ activeModelProfile
13780
+ );
13781
+ const resumedReasoningLevel = existingSessionRecord?.reasoningLevel ? parseTurnReasoningLevel(existingSessionRecord.reasoningLevel) : void 0;
13782
+ turnRoute = {
13783
+ profile: activeModelProfile,
13784
+ reasoningLevel: activeProfileConfig.reasoningLevel ?? resumedReasoningLevel ?? storedTurnRoute.reasoningLevel,
13785
+ reason: `resumed_handoff:${storedTurnRoute.modelProfile}:${activeModelProfile}`
13786
+ };
13787
+ } else {
13788
+ turnRoute = {
13789
+ profile: storedTurnRoute.modelProfile,
13790
+ reasoningLevel: storedTurnRoute.reasoningLevel,
13791
+ ...storedTurnRoute.confidence !== void 0 ? { confidence: storedTurnRoute.confidence } : {},
13792
+ reason: `persisted:${storedTurnRoute.source}`,
13793
+ source: storedTurnRoute.source
13794
+ };
13795
+ }
13796
+ } else if (activeModelProfile === STANDARD_MODEL_PROFILE) {
15439
13797
  turnRoute = await selectTurnRoute({
15440
13798
  completeObject,
15441
13799
  conversationContext: input.conversationContext,
@@ -15450,7 +13808,11 @@ async function executeAgentRunInPrivacyContext(request, conversationPrivacy, run
15450
13808
  messageText: userInput,
15451
13809
  profiles: botConfig.profiles
15452
13810
  });
15453
- if (configuredReasoningLevel) {
13811
+ const routedProfileReasoningLevel = profileConfig(
13812
+ botConfig,
13813
+ turnRoute.profile
13814
+ ).reasoningLevel;
13815
+ if (configuredReasoningLevel && !routedProfileReasoningLevel) {
15454
13816
  turnRoute = {
15455
13817
  ...turnRoute,
15456
13818
  reasoningLevel: configuredReasoningLevel,
@@ -15459,12 +13821,29 @@ async function executeAgentRunInPrivacyContext(request, conversationPrivacy, run
15459
13821
  }
15460
13822
  } else {
15461
13823
  const activeProfileConfig = profileConfig(botConfig, activeModelProfile);
15462
- turnRoute = configuredTurnRoute(
15463
- activeModelProfile,
15464
- activeProfileConfig.reasoningLevel ?? policy.reasoningLevel ?? botConfig.reasoningLevel ?? "medium",
15465
- activeProfileConfig.reasoningLevel ? "profile" : policy.reasoningLevel ? "agent_config" : "default"
15466
- );
13824
+ const reasoningSource = activeProfileConfig.reasoningLevel ? "profile" : policy.reasoningLevel ? "agent_config" : "default";
13825
+ turnRoute = {
13826
+ profile: activeModelProfile,
13827
+ reasoningLevel: activeProfileConfig.reasoningLevel ?? policy.reasoningLevel ?? botConfig.reasoningLevel ?? "medium",
13828
+ reason: `inherited:${reasoningSource}`,
13829
+ source: "inherited"
13830
+ };
13831
+ }
13832
+ const routedModelProfile = turnRoute.profile;
13833
+ const routedModelId = modelIdForProfile(botConfig, routedModelProfile);
13834
+ if (!storedTurnRoute) {
13835
+ await recordTurnRoute({
13836
+ conversationId,
13837
+ turnId,
13838
+ modelProfile: routedModelProfile,
13839
+ modelId: routedModelId,
13840
+ reasoningLevel: turnRoute.reasoningLevel,
13841
+ ...turnRoute.confidence !== void 0 ? { confidence: turnRoute.confidence } : {},
13842
+ source: turnRoute.source ?? "configured"
13843
+ });
15467
13844
  }
13845
+ activeModelProfile = routedModelProfile;
13846
+ activeModelId = routedModelId;
15468
13847
  const generatedFiles = [];
15469
13848
  const artifactStatePatch = {};
15470
13849
  const toolCalls = [];
@@ -15477,17 +13856,20 @@ async function executeAgentRunInPrivacyContext(request, conversationPrivacy, run
15477
13856
  (profile) => profile !== STANDARD_MODEL_PROFILE && profile !== DEFAULT_HANDOFF_MODEL_PROFILE
15478
13857
  ).sort()
15479
13858
  ];
13859
+ const usageSinceCurrentBoundary = (messages) => {
13860
+ const usage = extractGenAiUsageSummary(
13861
+ ...messages.slice(runResume.beforeMessageCount).filter(isAssistantMessage)
13862
+ );
13863
+ return hasAgentTurnUsage(usage) ? usage : void 0;
13864
+ };
15480
13865
  const scheduleHandoff = async (args) => {
15481
- if (args.profile === activeModelProfile) {
13866
+ if (args.profile === durableModelProfile) {
15482
13867
  return;
15483
13868
  }
15484
13869
  const runtimeContext = retainRuntimeTurnContext(
15485
13870
  args.runtimeContextSourceMessages ?? args.sourceMessages
15486
13871
  );
15487
- const standardPhaseUsage = extractGenAiUsageSummary(
15488
- ...args.sourceMessages.slice(runResume.beforeMessageCount).filter(isAssistantMessage)
15489
- );
15490
- const phaseUsage = hasAgentTurnUsage(standardPhaseUsage) ? standardPhaseUsage : void 0;
13872
+ const phaseUsage = usageSinceCurrentBoundary(args.sourceMessages);
15491
13873
  const selectedProfile = profileConfig(botConfig, args.profile);
15492
13874
  const handoffReasoningLevel = selectedProfile.reasoningLevel ?? turnRoute.reasoningLevel;
15493
13875
  const target = {
@@ -15525,8 +13907,11 @@ async function executeAgentRunInPrivacyContext(request, conversationPrivacy, run
15525
13907
  runId
15526
13908
  }
15527
13909
  },
15528
- { completeText }
13910
+ {
13911
+ completeText: (args2) => completeText(args2)
13912
+ }
15529
13913
  );
13914
+ durableModelProfile = args.profile;
15530
13915
  if (handoffReasoningLevel !== turnRoute.reasoningLevel) {
15531
13916
  turnRoute = {
15532
13917
  ...turnRoute,
@@ -15534,7 +13919,7 @@ async function executeAgentRunInPrivacyContext(request, conversationPrivacy, run
15534
13919
  reason: `profile_reasoning_override:${args.profile}:${turnRoute.reason}`
15535
13920
  };
15536
13921
  }
15537
- handoffPhaseUsage = phaseUsage;
13922
+ priorPhaseUsage = addAgentTurnUsage(priorPhaseUsage, phaseUsage);
15538
13923
  pendingHandoff = {
15539
13924
  messages: handoffMessages,
15540
13925
  model: handoffModel,
@@ -15567,6 +13952,7 @@ async function executeAgentRunInPrivacyContext(request, conversationPrivacy, run
15567
13952
  connectedMcpProviders,
15568
13953
  conversationPrivacy,
15569
13954
  durability,
13955
+ authorization,
15570
13956
  generatedFiles,
15571
13957
  invokedSkill,
15572
13958
  observers,
@@ -15647,6 +14033,52 @@ async function executeAgentRunInPrivacyContext(request, conversationPrivacy, run
15647
14033
  thinkingLevel
15648
14034
  };
15649
14035
  };
14036
+ const applyActiveContextCompaction = async (messages, hookSignal, pendingMessages, clearSteeringQueueOnCommit = false) => {
14037
+ const compaction = await compactActiveContextIfNeeded(
14038
+ {
14039
+ conversationContext: input.conversationContext,
14040
+ conversationId,
14041
+ metadata: {
14042
+ threadId: conversationId,
14043
+ channelId: slackDestination?.channelId,
14044
+ actorId: slackActor?.userId,
14045
+ runId
14046
+ },
14047
+ modelId: activeModelId,
14048
+ modelProfile: activeModelProfile,
14049
+ onCompactionStart: () => observers.onStatus?.({ text: "Compacting context" }),
14050
+ pendingMessages,
14051
+ piMessages: messages,
14052
+ signal: hookSignal
14053
+ },
14054
+ {
14055
+ completeText: (args) => completeText(args)
14056
+ }
14057
+ );
14058
+ if (!compaction.compacted || !compaction.piMessages) {
14059
+ return void 0;
14060
+ }
14061
+ priorPhaseUsage = addAgentTurnUsage(
14062
+ priorPhaseUsage,
14063
+ usageSinceCurrentBoundary(messages)
14064
+ );
14065
+ const replacement = [...compaction.piMessages];
14066
+ await runResume.requireDurableInputCheckpoint(replacement);
14067
+ agent.state.messages = replacement;
14068
+ runResume.setBeforeMessageCount(replacement.length);
14069
+ runResume.setTurnStartMessageIndex(0);
14070
+ runResume.adoptCommittedBoundary(replacement);
14071
+ if (clearSteeringQueueOnCommit && pendingMessages?.length) {
14072
+ agent.clearSteeringQueue();
14073
+ }
14074
+ return {
14075
+ context: {
14076
+ systemPrompt: baseInstructions,
14077
+ messages: replacement,
14078
+ tools: agent.state.tools
14079
+ }
14080
+ };
14081
+ };
15650
14082
  let assistantMessageDeliveryError;
15651
14083
  const deliverAssistantMessage = async (message) => {
15652
14084
  const text = getAssistantMessageText(message);
@@ -15664,8 +14096,9 @@ async function executeAgentRunInPrivacyContext(request, conversationPrivacy, run
15664
14096
  };
15665
14097
  const drainSteeringMessages = async () => {
15666
14098
  if (!durability.drainSteeringMessages) {
15667
- return;
14099
+ return [];
15668
14100
  }
14101
+ const acceptedMessages = [];
15669
14102
  try {
15670
14103
  let steeredMessageCount = 0;
15671
14104
  await durability.drainSteeringMessages(async (messages) => {
@@ -15683,6 +14116,12 @@ async function executeAgentRunInPrivacyContext(request, conversationPrivacy, run
15683
14116
  for (const message of piMessages) {
15684
14117
  agent.steer(message);
15685
14118
  }
14119
+ acceptedMessages.push(
14120
+ ...piMessages.map((message, index) => ({
14121
+ message,
14122
+ provenance: messages[index].provenance
14123
+ }))
14124
+ );
15686
14125
  steeredMessageCount += piMessages.length;
15687
14126
  });
15688
14127
  if (steeredMessageCount > 0) {
@@ -15708,6 +14147,7 @@ async function executeAgentRunInPrivacyContext(request, conversationPrivacy, run
15708
14147
  "Agent turn steering message drain failed"
15709
14148
  );
15710
14149
  }
14150
+ return acceptedMessages;
15711
14151
  };
15712
14152
  const apiKeyOverride = getPiGatewayApiKey();
15713
14153
  let pendingPiHookError;
@@ -15731,15 +14171,27 @@ async function executeAgentRunInPrivacyContext(request, conversationPrivacy, run
15731
14171
  return void 0;
15732
14172
  },
15733
14173
  afterToolCall: async ({ result: result2 }, signal2) => runResume.timedOut && signal2?.aborted ? annotateTurnDeadlineToolResult(result2) : void 0,
15734
- prepareNextTurn: async () => {
15735
- const update = applyPendingHandoff();
15736
- await drainSteeringMessages();
15737
- const yieldError = runResume.prepareYieldIfDue(currentAgentMessages());
15738
- if (yieldError) {
15739
- pendingPiHookError = yieldError;
15740
- throw yieldError;
14174
+ prepareNextTurnWithContext: async (nextTurn, hookSignal) => {
14175
+ try {
14176
+ const handoffUpdate = applyPendingHandoff();
14177
+ const pendingMessages = await drainSteeringMessages();
14178
+ const capacityUpdate = await applyActiveContextCompaction(
14179
+ handoffUpdate ? currentAgentMessages() : nextTurn.context.messages,
14180
+ hookSignal,
14181
+ pendingMessages,
14182
+ true
14183
+ );
14184
+ const yieldError = runResume.prepareYieldIfDue(
14185
+ currentAgentMessages()
14186
+ );
14187
+ if (yieldError) {
14188
+ throw yieldError;
14189
+ }
14190
+ return capacityUpdate && handoffUpdate ? { ...handoffUpdate, ...capacityUpdate } : capacityUpdate ?? handoffUpdate;
14191
+ } catch (error) {
14192
+ pendingPiHookError = error instanceof Error ? error : new Error(String(error));
14193
+ throw error;
15741
14194
  }
15742
- return update;
15743
14195
  },
15744
14196
  initialState: {
15745
14197
  systemPrompt: baseInstructions,
@@ -15904,6 +14356,7 @@ async function executeAgentRunInPrivacyContext(request, conversationPrivacy, run
15904
14356
  };
15905
14357
  const requestedProfile = activeModelProfile === STANDARD_MODEL_PROFILE ? turnRoute.profile : void 0;
15906
14358
  let run;
14359
+ let handoffApplied = false;
15907
14360
  if (requestedProfile && requestedProfile !== STANDARD_MODEL_PROFILE) {
15908
14361
  const handoffAbortController = new AbortController();
15909
14362
  await runAgentStep(
@@ -15915,19 +14368,29 @@ async function executeAgentRunInPrivacyContext(request, conversationPrivacy, run
15915
14368
  }),
15916
14369
  () => handoffAbortController.abort()
15917
14370
  );
15918
- applyPendingHandoff();
15919
- if (shouldPromptAgent) {
15920
- await runResume.requireDurableInputCheckpoint([
15921
- ...agent.state.messages,
15922
- freshPromptMessage
15923
- ]);
15924
- run = agent.prompt(freshPromptMessage);
15925
- } else {
15926
- run = agent.continue();
15927
- }
15928
- } else {
15929
- run = shouldPromptAgent ? agent.prompt(freshPromptMessage) : agent.continue();
14371
+ handoffApplied = Boolean(applyPendingHandoff());
15930
14372
  }
14373
+ const compactionAbortController = new AbortController();
14374
+ const capacityUpdate = await runAgentStep(
14375
+ applyActiveContextCompaction(
14376
+ [...agent.state.messages],
14377
+ compactionAbortController.signal,
14378
+ shouldPromptAgent ? [
14379
+ {
14380
+ message: freshPromptMessage,
14381
+ provenance: instructionProvenanceFor(actor)
14382
+ }
14383
+ ] : void 0
14384
+ ),
14385
+ () => compactionAbortController.abort()
14386
+ );
14387
+ if (shouldPromptAgent && handoffApplied && !capacityUpdate) {
14388
+ await runResume.requireDurableInputCheckpoint([
14389
+ ...agent.state.messages,
14390
+ freshPromptMessage
14391
+ ]);
14392
+ }
14393
+ run = shouldPromptAgent && !capacityUpdate ? agent.prompt(freshPromptMessage) : agent.continue();
15931
14394
  let retryUsage;
15932
14395
  try {
15933
14396
  for (let attempt = 0; ; attempt += 1) {
@@ -15950,10 +14413,7 @@ async function executeAgentRunInPrivacyContext(request, conversationPrivacy, run
15950
14413
  retryUsage,
15951
14414
  currentUsage
15952
14415
  );
15953
- turnUsage = addAgentTurnUsage(
15954
- handoffPhaseUsage,
15955
- currentPhaseUsage
15956
- );
14416
+ turnUsage = addAgentTurnUsage(priorPhaseUsage, currentPhaseUsage);
15957
14417
  setSpanAttributes({
15958
14418
  ...outputMessagesAttribute ? { "gen_ai.output.messages": outputMessagesAttribute } : {},
15959
14419
  ...toGenAiMessagesTraceAttributes(
@@ -16153,24 +14613,12 @@ export {
16153
14613
  getConfigDefaults,
16154
14614
  shouldEmitDevAgentTrace,
16155
14615
  AuthorizationFlowDisabledError,
16156
- RetryableDeliveryError,
16157
- truncateStatusText,
16158
- escapeSlackMrkdwnText,
16159
- formatSlackLink,
16160
- normalizeSlackStatusText,
16161
- splitSlackReplyText,
16162
- buildSlackOutputMessage,
16163
14616
  buildSteeringPiMessage,
16164
- createUserTokenStore,
16165
- postSlackMessage,
16166
- addReactionToMessage,
16167
- removeReactionFromMessage,
16168
- parseOAuthStatePayload,
16169
- formatProviderLabel,
16170
- resolveBaseUrl,
16171
- startOAuthFlow,
16172
14617
  SANDBOX_EGRESS_PROXY_PATH,
16173
14618
  parseSandboxEgressCredentialToken,
14619
+ clearSandboxEgressSignals,
14620
+ consumeSandboxEgressAuthRequiredSignal,
14621
+ consumeSandboxEgressPermissionDeniedSignal,
16174
14622
  normalizeSandboxEgressTracePropagationDomains,
16175
14623
  shouldPropagateSandboxEgressTrace,
16176
14624
  resolveSandboxEgressProviderForHost,
@@ -16182,14 +14630,6 @@ export {
16182
14630
  appendSlackLegacyAttachmentText,
16183
14631
  latestReportedProgress,
16184
14632
  PluginCredentialFailureError,
16185
- getMcpAuthSession,
16186
- deleteMcpAuthSession,
16187
- deleteMcpAuthSessionsForUserProvider,
16188
- getMcpStoredOAuthCredentials,
16189
- deleteMcpStoredOAuthCredentials,
16190
- deleteMcpServerSessionId,
16191
- finalizeMcpAuthorization,
16192
- resolvePluginOAuthAccount,
16193
14633
  executeCredentialedEgressRequest,
16194
14634
  createContextCompactor,
16195
14635
  executeAgentRun,