@sentry/junior 0.42.0 → 0.43.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/app.js CHANGED
@@ -3,7 +3,7 @@ import {
3
3
  findSkillByName,
4
4
  loadSkillsByName,
5
5
  parseSkillInvocation
6
- } from "./chunk-7QYONRLH.js";
6
+ } from "./chunk-ZUUJTQ2H.js";
7
7
  import {
8
8
  GEN_AI_PROVIDER_NAME,
9
9
  MISSING_GATEWAY_CREDENTIALS_ERROR,
@@ -14,6 +14,7 @@ import {
14
14
  buildNonInteractiveShellScript,
15
15
  completeObject,
16
16
  completeText,
17
+ createSandboxInstance,
17
18
  getGatewayApiKey,
18
19
  getPiGatewayApiKeyOverride,
19
20
  getRuntimeDependencyProfileHash,
@@ -30,7 +31,7 @@ import {
30
31
  runNonInteractiveCommand,
31
32
  sandboxSkillDir,
32
33
  sandboxSkillFile
33
- } from "./chunk-Y3UO7NR6.js";
34
+ } from "./chunk-YSXHRIWR.js";
34
35
  import {
35
36
  CredentialUnavailableError,
36
37
  buildOAuthTokenRequest,
@@ -58,6 +59,7 @@ import {
58
59
  mergeHeaderTransforms,
59
60
  parseOAuthTokenResponse,
60
61
  resolveAuthTokenPlaceholder,
62
+ resolvePluginCommandEnv,
61
63
  serializeGenAiAttribute,
62
64
  setSpanAttributes,
63
65
  setSpanStatus,
@@ -66,7 +68,7 @@ import {
66
68
  toOptionalString,
67
69
  withContext,
68
70
  withSpan
69
- } from "./chunk-SCE5C645.js";
71
+ } from "./chunk-BCG3I2T2.js";
70
72
  import {
71
73
  sentry_exports
72
74
  } from "./chunk-Z3YD6NHK.js";
@@ -3219,140 +3221,6 @@ var ProviderCredentialRouter = class {
3219
3221
  }
3220
3222
  };
3221
3223
 
3222
- // src/chat/capabilities/runtime.ts
3223
- function toHeaderTransforms(lease) {
3224
- if (!Array.isArray(lease.headerTransforms) || lease.headerTransforms.length === 0) {
3225
- return [];
3226
- }
3227
- return lease.headerTransforms.filter(
3228
- (transform) => Boolean(transform?.domain?.trim()) && transform.headers && typeof transform.headers === "object" && Object.keys(transform.headers).length > 0
3229
- ).map((transform) => ({
3230
- domain: transform.domain.trim(),
3231
- headers: transform.headers
3232
- }));
3233
- }
3234
- var SkillCapabilityRuntime = class {
3235
- router;
3236
- requesterId;
3237
- enabledByProvider = /* @__PURE__ */ new Map();
3238
- constructor(params) {
3239
- if (params.router) {
3240
- this.router = params.router;
3241
- } else if (params.broker) {
3242
- this.router = {
3243
- issue: async (input) => await params.broker.issue(input)
3244
- };
3245
- } else {
3246
- throw new Error(
3247
- "SkillCapabilityRuntime requires either router or broker"
3248
- );
3249
- }
3250
- this.requesterId = params.requesterId;
3251
- }
3252
- async enableCredentialsForTurn(input) {
3253
- const provider = input.activeSkill?.pluginProvider;
3254
- if (!provider) {
3255
- return void 0;
3256
- }
3257
- if (!this.requesterId) {
3258
- throw new Error("Credential enablement requires requester context");
3259
- }
3260
- const plugin = getPluginDefinition(provider);
3261
- if (!plugin?.manifest.credentials && !plugin?.manifest.apiHeaders) {
3262
- return void 0;
3263
- }
3264
- const existing = this.enabledByProvider.get(provider);
3265
- const now = Date.now();
3266
- if (existing && existing.expiresAtMs - now > 1e4) {
3267
- return {
3268
- reused: true,
3269
- expiresAt: new Date(existing.expiresAtMs).toISOString()
3270
- };
3271
- }
3272
- logInfo(
3273
- "credential_issue_request",
3274
- {},
3275
- {
3276
- "app.skill.name": input.activeSkill?.name,
3277
- "app.credential.provider": provider
3278
- },
3279
- "Issuing provider credential for current turn"
3280
- );
3281
- try {
3282
- const lease = await this.router.issue({
3283
- provider,
3284
- reason: input.reason,
3285
- requesterId: this.requesterId
3286
- });
3287
- const transforms = toHeaderTransforms(lease);
3288
- if (transforms.length === 0) {
3289
- throw new Error(
3290
- `Credential lease for ${provider} did not include header transforms`
3291
- );
3292
- }
3293
- const expiresAtMs = Date.parse(lease.expiresAt);
3294
- if (!Number.isFinite(expiresAtMs)) {
3295
- throw new Error(
3296
- `Credential lease for ${provider} returned invalid expiresAt`
3297
- );
3298
- }
3299
- this.enabledByProvider.set(provider, {
3300
- expiresAtMs,
3301
- transforms,
3302
- env: lease.env
3303
- });
3304
- logInfo(
3305
- "credential_issue_success",
3306
- {},
3307
- {
3308
- "app.skill.name": input.activeSkill?.name,
3309
- "app.credential.provider": lease.provider,
3310
- "app.credential.expires_at": lease.expiresAt,
3311
- "app.credential.delivery": "header_transform"
3312
- },
3313
- "Issued provider credential lease"
3314
- );
3315
- return { reused: false, expiresAt: lease.expiresAt };
3316
- } catch (error) {
3317
- logWarn(
3318
- "credential_issue_failed",
3319
- {},
3320
- {
3321
- "app.skill.name": input.activeSkill?.name,
3322
- "app.credential.provider": provider,
3323
- "exception.message": error instanceof Error ? error.message : String(error)
3324
- },
3325
- "Provider credential resolution failed"
3326
- );
3327
- throw error;
3328
- }
3329
- }
3330
- getTurnHeaderTransforms() {
3331
- const now = Date.now();
3332
- const headerTransforms = [];
3333
- for (const [provider, entry] of this.enabledByProvider.entries()) {
3334
- if (!Number.isFinite(entry.expiresAtMs) || entry.expiresAtMs <= now) {
3335
- this.enabledByProvider.delete(provider);
3336
- continue;
3337
- }
3338
- headerTransforms.push(...entry.transforms);
3339
- }
3340
- return headerTransforms.length > 0 ? headerTransforms : void 0;
3341
- }
3342
- getTurnEnv() {
3343
- const now = Date.now();
3344
- const env = {};
3345
- for (const [provider, entry] of this.enabledByProvider.entries()) {
3346
- if (!Number.isFinite(entry.expiresAtMs) || entry.expiresAtMs <= now) {
3347
- this.enabledByProvider.delete(provider);
3348
- continue;
3349
- }
3350
- Object.assign(env, entry.env);
3351
- }
3352
- return Object.keys(env).length > 0 ? env : void 0;
3353
- }
3354
- };
3355
-
3356
3224
  // src/chat/credentials/state-adapter-token-store.ts
3357
3225
  var KEY_PREFIX = "oauth-token";
3358
3226
  var BUFFER_MS = 24 * 60 * 60 * 1e3;
@@ -3419,12 +3287,13 @@ var TestCredentialBroker = class {
3419
3287
 
3420
3288
  // src/chat/capabilities/factory.ts
3421
3289
  var ENV_PLACEHOLDER_RE = /\$\{([A-Z_][A-Z0-9_]*)\}/g;
3290
+ var sandboxEgressRouters = /* @__PURE__ */ new WeakMap();
3422
3291
  function createUserTokenStore() {
3423
3292
  return new StateAdapterTokenStore(getStateAdapter());
3424
3293
  }
3425
3294
  function resolveTestApiHeaderTransforms(manifest) {
3426
- const { apiDomains, apiHeaders } = manifest;
3427
- if (!apiDomains || !apiHeaders) {
3295
+ const { domains, apiHeaders } = manifest;
3296
+ if (!domains || !apiHeaders) {
3428
3297
  return [];
3429
3298
  }
3430
3299
  const headers = Object.fromEntries(
@@ -3435,44 +3304,51 @@ function resolveTestApiHeaderTransforms(manifest) {
3435
3304
  })
3436
3305
  ])
3437
3306
  );
3438
- return apiDomains.map((domain) => ({ domain, headers }));
3307
+ return domains.map((domain) => ({ domain, headers }));
3308
+ }
3309
+ function createTestBroker(plugin) {
3310
+ const { apiHeaders, credentials, name } = plugin.manifest;
3311
+ const commandEnv = resolvePluginCommandEnv(plugin.manifest);
3312
+ return new TestCredentialBroker({
3313
+ provider: name,
3314
+ ...credentials ? {
3315
+ domains: credentials.domains,
3316
+ ...credentials.apiHeaders ? { apiHeaders: credentials.apiHeaders } : {},
3317
+ envKey: credentials.authTokenEnv,
3318
+ placeholder: resolveAuthTokenPlaceholder(credentials)
3319
+ } : {},
3320
+ ...apiHeaders ? {
3321
+ headerTransforms: () => resolveTestApiHeaderTransforms(plugin.manifest)
3322
+ } : {},
3323
+ ...Object.keys(commandEnv).length > 0 ? { env: commandEnv } : {}
3324
+ });
3439
3325
  }
3440
- function createSkillCapabilityRuntime(options = {}) {
3326
+ function createProviderCredentialRouter(userTokenStore) {
3441
3327
  logCapabilityCatalogLoadedOnce();
3442
3328
  const useTestBroker = process.env.EVAL_ENABLE_TEST_CREDENTIALS === "1";
3443
- const userTokenStore = createUserTokenStore();
3444
3329
  const brokersByProvider = {};
3445
3330
  for (const plugin of getPluginProviders()) {
3446
- const { apiHeaders, credentials, name } = plugin.manifest;
3447
- if (!credentials && !apiHeaders) {
3331
+ const { name } = plugin.manifest;
3332
+ if (!plugin.manifest.credentials && !plugin.manifest.apiHeaders) {
3448
3333
  continue;
3449
3334
  }
3450
- if (!credentials) {
3451
- brokersByProvider[name] = useTestBroker ? new TestCredentialBroker({
3452
- provider: name,
3453
- headerTransforms: () => resolveTestApiHeaderTransforms(plugin.manifest),
3454
- ...plugin.manifest.commandEnv ? { env: plugin.manifest.commandEnv } : {}
3455
- }) : createPluginBroker(name, { userTokenStore });
3456
- continue;
3457
- }
3458
- const placeholder = resolveAuthTokenPlaceholder(credentials);
3459
- brokersByProvider[name] = useTestBroker ? new TestCredentialBroker({
3460
- provider: name,
3461
- domains: credentials.apiDomains,
3462
- ...credentials.apiHeaders ? { apiHeaders: credentials.apiHeaders } : {},
3463
- ...apiHeaders ? {
3464
- headerTransforms: () => resolveTestApiHeaderTransforms(plugin.manifest)
3465
- } : {},
3466
- ...plugin.manifest.commandEnv ? { env: plugin.manifest.commandEnv } : {},
3467
- envKey: credentials.authTokenEnv,
3468
- placeholder
3469
- }) : createPluginBroker(name, { userTokenStore });
3335
+ brokersByProvider[name] = useTestBroker ? createTestBroker(plugin) : createPluginBroker(name, { userTokenStore });
3470
3336
  }
3471
- const router = new ProviderCredentialRouter({ brokersByProvider });
3472
- return new SkillCapabilityRuntime({
3473
- router,
3474
- requesterId: options.requesterId
3475
- });
3337
+ return new ProviderCredentialRouter({ brokersByProvider });
3338
+ }
3339
+ function getSandboxEgressRouter() {
3340
+ const stateAdapter = getStateAdapter();
3341
+ let router = sandboxEgressRouters.get(stateAdapter);
3342
+ if (!router) {
3343
+ router = createProviderCredentialRouter(
3344
+ new StateAdapterTokenStore(stateAdapter)
3345
+ );
3346
+ sandboxEgressRouters.set(stateAdapter, router);
3347
+ }
3348
+ return router;
3349
+ }
3350
+ async function issueProviderCredentialLease(input) {
3351
+ return await getSandboxEgressRouter().issue(input);
3476
3352
  }
3477
3353
 
3478
3354
  // src/chat/capabilities/jr-rpc-command.ts
@@ -7354,13 +7230,272 @@ function createSlackThreadReadTool(context) {
7354
7230
  });
7355
7231
  }
7356
7232
 
7357
- // src/chat/tools/system-time.ts
7233
+ // src/chat/tools/slack/user-lookup.ts
7358
7234
  import { Type as Type19 } from "@sinclair/typebox";
7235
+
7236
+ // src/chat/slack/users.ts
7237
+ function normalizeUser(raw) {
7238
+ const rawFields = raw.profile?.fields;
7239
+ const profileFields = [];
7240
+ if (rawFields && typeof rawFields === "object") {
7241
+ for (const [id, field] of Object.entries(rawFields)) {
7242
+ if (!field) continue;
7243
+ profileFields.push({
7244
+ id,
7245
+ label: field.label || void 0,
7246
+ value: field.value || void 0,
7247
+ alt: field.alt || void 0
7248
+ });
7249
+ }
7250
+ }
7251
+ return {
7252
+ id: raw.id ?? "",
7253
+ team_id: raw.team_id || void 0,
7254
+ name: raw.name || void 0,
7255
+ real_name: raw.real_name || raw.profile?.real_name || void 0,
7256
+ display_name: raw.profile?.display_name || void 0,
7257
+ title: raw.profile?.title || void 0,
7258
+ email: raw.profile?.email || void 0,
7259
+ status_text: raw.profile?.status_text || void 0,
7260
+ status_emoji: raw.profile?.status_emoji || void 0,
7261
+ is_bot: raw.is_bot ?? false,
7262
+ is_deleted: raw.deleted ?? false,
7263
+ timezone: raw.tz || void 0,
7264
+ ...profileFields.length > 0 ? { profile_fields: profileFields } : {}
7265
+ };
7266
+ }
7267
+ async function lookupSlackUserProfile(userId) {
7268
+ const client2 = getSlackClient();
7269
+ const result = await withSlackRetries(
7270
+ () => client2.users.info({ user: userId }),
7271
+ 3,
7272
+ { action: "users.info" }
7273
+ );
7274
+ const user = result.user;
7275
+ if (!user) {
7276
+ throw new Error(`Slack users.info returned no user for ${userId}`);
7277
+ }
7278
+ return normalizeUser(user);
7279
+ }
7280
+ async function lookupSlackUserByEmail(email) {
7281
+ const client2 = getSlackClient();
7282
+ let result;
7283
+ try {
7284
+ result = await withSlackRetries(
7285
+ () => client2.users.lookupByEmail({ email }),
7286
+ 3,
7287
+ { action: "users.lookupByEmail" }
7288
+ );
7289
+ } catch (error) {
7290
+ const apiError = error.apiError;
7291
+ if (apiError === "users_not_found") {
7292
+ return null;
7293
+ }
7294
+ throw error;
7295
+ }
7296
+ const user = result.user;
7297
+ if (!user) {
7298
+ return null;
7299
+ }
7300
+ return normalizeUser(user);
7301
+ }
7302
+ function scoreMatch(user, queryLower) {
7303
+ const name = (user.name ?? "").toLowerCase();
7304
+ const realName = (user.real_name ?? user.profile?.real_name ?? "").toLowerCase();
7305
+ const displayName = (user.profile?.display_name ?? "").toLowerCase();
7306
+ if (name === queryLower || displayName === queryLower) return 100;
7307
+ if (realName === queryLower) return 90;
7308
+ if (name.startsWith(queryLower) || displayName.startsWith(queryLower))
7309
+ return 70;
7310
+ if (realName.startsWith(queryLower)) return 60;
7311
+ const realNameWords = realName.split(/\s+/);
7312
+ if (realNameWords.some((w) => w === queryLower)) return 55;
7313
+ if (realNameWords.some((w) => w.startsWith(queryLower))) return 50;
7314
+ if (name.includes(queryLower) || displayName.includes(queryLower)) return 30;
7315
+ if (realName.includes(queryLower)) return 20;
7316
+ return 0;
7317
+ }
7318
+ async function searchSlackUsers(options) {
7319
+ const {
7320
+ query,
7321
+ limit = 10,
7322
+ maxPages = 3,
7323
+ includeDeleted = false,
7324
+ includeBots = false
7325
+ } = options;
7326
+ const queryLower = query.toLowerCase().trim();
7327
+ const client2 = getSlackClient();
7328
+ const matches = [];
7329
+ let cursor;
7330
+ let pages = 0;
7331
+ let totalScanned = 0;
7332
+ let truncated = false;
7333
+ while (pages < maxPages) {
7334
+ pages++;
7335
+ const result = await withSlackRetries(
7336
+ () => client2.users.list({
7337
+ limit: 200,
7338
+ ...cursor ? { cursor } : {}
7339
+ }),
7340
+ 3,
7341
+ { action: "users.list" }
7342
+ );
7343
+ const members = result.members ?? [];
7344
+ totalScanned += members.length;
7345
+ for (const member of members) {
7346
+ if (!includeDeleted && member.deleted) continue;
7347
+ if (!includeBots && member.is_bot) continue;
7348
+ if (member.id === "USLACKBOT") continue;
7349
+ const score = scoreMatch(member, queryLower);
7350
+ if (score > 0) {
7351
+ matches.push({ user: member, score });
7352
+ }
7353
+ }
7354
+ const nextCursor = result.response_metadata?.next_cursor;
7355
+ if (!nextCursor) {
7356
+ break;
7357
+ }
7358
+ cursor = nextCursor;
7359
+ }
7360
+ if (pages >= maxPages && cursor) {
7361
+ truncated = true;
7362
+ }
7363
+ matches.sort((a, b) => {
7364
+ if (b.score !== a.score) return b.score - a.score;
7365
+ return (a.user.name ?? "").localeCompare(b.user.name ?? "");
7366
+ });
7367
+ return {
7368
+ users: matches.slice(0, limit).map((m) => normalizeUser(m.user)),
7369
+ searched_pages: pages,
7370
+ searched_user_count: totalScanned,
7371
+ truncated
7372
+ };
7373
+ }
7374
+
7375
+ // src/chat/tools/slack/user-lookup.ts
7376
+ function createSlackUserLookupTool() {
7377
+ return tool({
7378
+ 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.",
7379
+ annotations: { readOnlyHint: true, destructiveHint: false },
7380
+ inputSchema: Type19.Object({
7381
+ user_id: Type19.Optional(
7382
+ Type19.String({
7383
+ minLength: 1,
7384
+ description: "Slack user ID to look up (e.g. U039RR91S). Mutually exclusive with email and query."
7385
+ })
7386
+ ),
7387
+ email: Type19.Optional(
7388
+ Type19.String({
7389
+ minLength: 3,
7390
+ description: "Email address to look up. Mutually exclusive with user_id and query."
7391
+ })
7392
+ ),
7393
+ query: Type19.Optional(
7394
+ Type19.String({
7395
+ minLength: 2,
7396
+ description: "Name to search for (matches against username, display name, real name). Mutually exclusive with user_id and email."
7397
+ })
7398
+ ),
7399
+ limit: Type19.Optional(
7400
+ Type19.Integer({
7401
+ minimum: 1,
7402
+ maximum: 20,
7403
+ description: "Maximum number of results to return for name search. Defaults to 10."
7404
+ })
7405
+ ),
7406
+ max_pages: Type19.Optional(
7407
+ Type19.Integer({
7408
+ minimum: 1,
7409
+ maximum: 5,
7410
+ description: "Maximum number of Slack API pages to scan for name search. Defaults to 3."
7411
+ })
7412
+ ),
7413
+ include_bots: Type19.Optional(
7414
+ Type19.Boolean({
7415
+ description: "Include bot accounts in name search results. Defaults to false."
7416
+ })
7417
+ )
7418
+ }),
7419
+ execute: async ({
7420
+ user_id,
7421
+ email,
7422
+ query,
7423
+ limit,
7424
+ max_pages,
7425
+ include_bots
7426
+ }) => {
7427
+ const modes = [user_id, email, query].filter(Boolean);
7428
+ if (modes.length === 0) {
7429
+ return {
7430
+ ok: false,
7431
+ error: "Provide exactly one of user_id, email, or query to look up a Slack user."
7432
+ };
7433
+ }
7434
+ if (modes.length > 1) {
7435
+ return {
7436
+ ok: false,
7437
+ error: "Only one of user_id, email, or query can be provided."
7438
+ };
7439
+ }
7440
+ try {
7441
+ if (user_id) {
7442
+ return {
7443
+ ok: true,
7444
+ mode: "user_id",
7445
+ user: await lookupSlackUserProfile(user_id)
7446
+ };
7447
+ }
7448
+ if (email) {
7449
+ const profile = await lookupSlackUserByEmail(email);
7450
+ if (!profile) {
7451
+ return {
7452
+ ok: false,
7453
+ mode: "email",
7454
+ email,
7455
+ error: "No Slack user found with that email address."
7456
+ };
7457
+ }
7458
+ return { ok: true, mode: "email", user: profile };
7459
+ }
7460
+ const result = await searchSlackUsers({
7461
+ query,
7462
+ limit: limit ?? 10,
7463
+ maxPages: max_pages ?? 3,
7464
+ includeBots: include_bots ?? false
7465
+ });
7466
+ return {
7467
+ ok: true,
7468
+ mode: "query",
7469
+ query,
7470
+ count: result.users.length,
7471
+ searched_pages: result.searched_pages,
7472
+ searched_user_count: result.searched_user_count,
7473
+ truncated: result.truncated,
7474
+ users: result.users
7475
+ };
7476
+ } catch (error) {
7477
+ if (error instanceof SlackActionError) {
7478
+ return {
7479
+ ok: false,
7480
+ error: error.message,
7481
+ slack_error: error.apiError,
7482
+ code: error.code,
7483
+ ...error.needed ? { needed_scope: error.needed } : {}
7484
+ };
7485
+ }
7486
+ throw error;
7487
+ }
7488
+ }
7489
+ });
7490
+ }
7491
+
7492
+ // src/chat/tools/system-time.ts
7493
+ import { Type as Type20 } from "@sinclair/typebox";
7359
7494
  function createSystemTimeTool() {
7360
7495
  return tool({
7361
7496
  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.",
7362
7497
  annotations: { readOnlyHint: true, destructiveHint: false },
7363
- inputSchema: Type19.Object({}),
7498
+ inputSchema: Type20.Object({}),
7364
7499
  execute: async () => {
7365
7500
  const now = /* @__PURE__ */ new Date();
7366
7501
  return {
@@ -7378,7 +7513,7 @@ function createSystemTimeTool() {
7378
7513
  import {
7379
7514
  Agent
7380
7515
  } from "@mariozechner/pi-agent-core";
7381
- import { Type as Type20 } from "@sinclair/typebox";
7516
+ import { Type as Type21 } from "@sinclair/typebox";
7382
7517
 
7383
7518
  // src/chat/respond-helpers.ts
7384
7519
  var MAX_INLINE_ATTACHMENT_BASE64_CHARS = 12e4;
@@ -7666,12 +7801,12 @@ function createAdvisorTool(context) {
7666
7801
  const spanContext = context.logContext ?? {};
7667
7802
  return tool({
7668
7803
  description: ADVISOR_TOOL_DESCRIPTION,
7669
- inputSchema: Type20.Object({
7670
- question: Type20.String({
7804
+ inputSchema: Type21.Object({
7805
+ question: Type21.String({
7671
7806
  minLength: 1,
7672
7807
  description: "Focused advisor question or decision point."
7673
7808
  }),
7674
- context: Type20.String({
7809
+ context: Type21.String({
7675
7810
  minLength: 1,
7676
7811
  description: "Curated evidence packet: relevant requirements, constraints, current plan, alternatives, code snippets, diffs, command output, and open questions."
7677
7812
  })
@@ -7783,7 +7918,7 @@ function createAdvisorTool(context) {
7783
7918
  }
7784
7919
 
7785
7920
  // src/chat/tools/web/fetch-tool.ts
7786
- import { Type as Type21 } from "@sinclair/typebox";
7921
+ import { Type as Type22 } from "@sinclair/typebox";
7787
7922
 
7788
7923
  // src/chat/tools/web/constants.ts
7789
7924
  var USER_AGENT = "junior-bot/0.1";
@@ -8136,13 +8271,13 @@ function createWebFetchTool(hooks) {
8136
8271
  destructiveHint: false,
8137
8272
  openWorldHint: true
8138
8273
  },
8139
- inputSchema: Type21.Object({
8140
- url: Type21.String({
8274
+ inputSchema: Type22.Object({
8275
+ url: Type22.String({
8141
8276
  minLength: 1,
8142
8277
  description: "HTTP(S) URL to fetch."
8143
8278
  }),
8144
- max_chars: Type21.Optional(
8145
- Type21.Integer({
8279
+ max_chars: Type22.Optional(
8280
+ Type22.Integer({
8146
8281
  minimum: 500,
8147
8282
  maximum: MAX_FETCH_CHARS,
8148
8283
  description: "Optional maximum number of extracted characters to return."
@@ -8202,7 +8337,7 @@ function createWebFetchTool(hooks) {
8202
8337
  // src/chat/tools/web/search.ts
8203
8338
  import { generateText } from "ai";
8204
8339
  import { createGatewayProvider } from "@ai-sdk/gateway";
8205
- import { Type as Type22 } from "@sinclair/typebox";
8340
+ import { Type as Type23 } from "@sinclair/typebox";
8206
8341
  var SEARCH_TIMEOUT_MS = 6e4;
8207
8342
  var MAX_RESULTS2 = 5;
8208
8343
  var DEFAULT_SEARCH_MODEL = "xai/grok-4-fast-reasoning";
@@ -8250,14 +8385,14 @@ function createWebSearchTool() {
8250
8385
  destructiveHint: false,
8251
8386
  openWorldHint: true
8252
8387
  },
8253
- inputSchema: Type22.Object({
8254
- query: Type22.String({
8388
+ inputSchema: Type23.Object({
8389
+ query: Type23.String({
8255
8390
  minLength: 1,
8256
8391
  maxLength: 500,
8257
8392
  description: "Search query."
8258
8393
  }),
8259
- max_results: Type22.Optional(
8260
- Type22.Integer({
8394
+ max_results: Type23.Optional(
8395
+ Type23.Integer({
8261
8396
  minimum: 1,
8262
8397
  maximum: MAX_RESULTS2,
8263
8398
  description: "Max results to return."
@@ -8326,20 +8461,20 @@ function createWebSearchTool() {
8326
8461
  }
8327
8462
 
8328
8463
  // src/chat/tools/sandbox/write-file.ts
8329
- import { Type as Type23 } from "@sinclair/typebox";
8464
+ import { Type as Type24 } from "@sinclair/typebox";
8330
8465
  function createWriteFileTool() {
8331
8466
  return tool({
8332
8467
  description: "Write UTF-8 content to a file in the sandbox workspace. Use for intentional file creation or replacement after validation. Do not use for exploratory analysis-only turns.",
8333
8468
  promptSnippet: "new file or deliberate full-file replacement",
8334
8469
  promptGuidelines: ["targeted existing-file changes: editFile"],
8335
8470
  executionMode: "sequential",
8336
- inputSchema: Type23.Object(
8471
+ inputSchema: Type24.Object(
8337
8472
  {
8338
- path: Type23.String({
8473
+ path: Type24.String({
8339
8474
  minLength: 1,
8340
8475
  description: "Path to write in the sandbox workspace."
8341
8476
  }),
8342
- content: Type23.String({
8477
+ content: Type24.String({
8343
8478
  description: "UTF-8 file content to write."
8344
8479
  })
8345
8480
  },
@@ -8413,6 +8548,7 @@ function createTools(availableSkills, hooks = {}, context) {
8413
8548
  slackCanvasRead: createSlackCanvasReadTool(),
8414
8549
  slackCanvasUpdate: createSlackCanvasUpdateTool(state, context),
8415
8550
  slackThreadRead: createSlackThreadReadTool(context),
8551
+ slackUserLookup: createSlackUserLookupTool(),
8416
8552
  slackListCreate: createSlackListCreateTool(state),
8417
8553
  slackListAddItems: createSlackListAddItemsTool(state),
8418
8554
  slackListGetItems: createSlackListGetItemsTool(state),
@@ -8463,6 +8599,173 @@ function resolveChannelCapabilities(channelId) {
8463
8599
  // src/chat/sandbox/sandbox.ts
8464
8600
  import fs4 from "fs/promises";
8465
8601
 
8602
+ // src/chat/sandbox/egress-policy.ts
8603
+ var SANDBOX_EGRESS_PROXY_PATH = "/api/internal/sandbox-egress";
8604
+ function matchesSandboxEgressDomain(host, domain) {
8605
+ return host.toLowerCase() === domain.toLowerCase();
8606
+ }
8607
+ function manifestDomains(manifest) {
8608
+ const domains = /* @__PURE__ */ new Set([
8609
+ ...manifest.credentials?.domains ?? [],
8610
+ ...manifest.domains ?? []
8611
+ ]);
8612
+ return [...domains].sort((left, right) => left.localeCompare(right));
8613
+ }
8614
+ function providerEntries() {
8615
+ return getPluginProviders().map((plugin) => ({
8616
+ provider: plugin.manifest.name,
8617
+ domains: manifestDomains(plugin.manifest)
8618
+ })).filter((entry) => entry.domains.length > 0).sort((left, right) => left.provider.localeCompare(right.provider));
8619
+ }
8620
+ function resolveSandboxEgressProviderForHost(host) {
8621
+ return providerEntries().find(
8622
+ (entry) => entry.domains.some((domain) => matchesSandboxEgressDomain(host, domain))
8623
+ )?.provider;
8624
+ }
8625
+ function proxyUrl(sandboxId) {
8626
+ const baseUrl = resolveBaseUrl();
8627
+ if (!baseUrl) {
8628
+ return void 0;
8629
+ }
8630
+ const url = new URL(
8631
+ `${SANDBOX_EGRESS_PROXY_PATH}/${encodeURIComponent(sandboxId)}`,
8632
+ baseUrl
8633
+ );
8634
+ return url.toString();
8635
+ }
8636
+ function buildSandboxEgressNetworkPolicy(sandboxId) {
8637
+ const forwardURL = proxyUrl(sandboxId);
8638
+ if (!forwardURL) {
8639
+ return void 0;
8640
+ }
8641
+ const entries = providerEntries();
8642
+ if (entries.length === 0) {
8643
+ return void 0;
8644
+ }
8645
+ const allow = {
8646
+ "*": []
8647
+ };
8648
+ for (const entry of entries) {
8649
+ for (const domain of entry.domains) {
8650
+ allow[domain] = [{ forwardURL }];
8651
+ }
8652
+ }
8653
+ return { allow };
8654
+ }
8655
+ async function resolveSandboxCommandEnvironment() {
8656
+ const env = {};
8657
+ for (const plugin of getPluginProviders().sort(
8658
+ (left, right) => left.manifest.name.localeCompare(right.manifest.name)
8659
+ )) {
8660
+ Object.assign(env, resolvePluginCommandEnv(plugin.manifest));
8661
+ const credentials = plugin.manifest.credentials;
8662
+ if (credentials) {
8663
+ env[credentials.authTokenEnv] = resolveAuthTokenPlaceholder(credentials);
8664
+ }
8665
+ }
8666
+ return env;
8667
+ }
8668
+
8669
+ // src/chat/sandbox/egress-session.ts
8670
+ import { randomUUID as randomUUID3 } from "crypto";
8671
+ var SANDBOX_EGRESS_SESSION_PREFIX = "sandbox-egress-session";
8672
+ var SANDBOX_EGRESS_LEASE_PREFIX = "sandbox-egress-lease";
8673
+ var DEFAULT_SESSION_TTL_MS = 30 * 60 * 1e3;
8674
+ function sessionKey2(sandboxId) {
8675
+ return `${SANDBOX_EGRESS_SESSION_PREFIX}:${sandboxId}`;
8676
+ }
8677
+ function leaseKey(sandboxId, provider, session) {
8678
+ return `${SANDBOX_EGRESS_LEASE_PREFIX}:${sandboxId}:${provider}:${session.requesterId}:${session.activationId}`;
8679
+ }
8680
+ function parseSession(value) {
8681
+ if (!value || typeof value !== "object") {
8682
+ return void 0;
8683
+ }
8684
+ const record = value;
8685
+ if (typeof record.requesterId !== "string" || typeof record.expiresAtMs !== "number" || !Number.isFinite(record.expiresAtMs) || typeof record.activationId !== "string" || !record.activationId) {
8686
+ return void 0;
8687
+ }
8688
+ if (record.expiresAtMs <= Date.now()) {
8689
+ return void 0;
8690
+ }
8691
+ return {
8692
+ requesterId: record.requesterId,
8693
+ expiresAtMs: record.expiresAtMs,
8694
+ activationId: record.activationId
8695
+ };
8696
+ }
8697
+ function parseLease(value) {
8698
+ if (!value || typeof value !== "object") {
8699
+ return void 0;
8700
+ }
8701
+ const record = value;
8702
+ if (typeof record.provider !== "string" || typeof record.expiresAt !== "string" || !Array.isArray(record.headerTransforms)) {
8703
+ return void 0;
8704
+ }
8705
+ const expiresAtMs = Date.parse(record.expiresAt);
8706
+ if (!Number.isFinite(expiresAtMs) || expiresAtMs <= Date.now()) {
8707
+ return void 0;
8708
+ }
8709
+ const headerTransforms = record.headerTransforms.filter(
8710
+ (transform) => Boolean(
8711
+ transform && typeof transform.domain === "string" && transform.headers && typeof transform.headers === "object"
8712
+ )
8713
+ );
8714
+ if (headerTransforms.length === 0) {
8715
+ return void 0;
8716
+ }
8717
+ return {
8718
+ provider: record.provider,
8719
+ expiresAt: record.expiresAt,
8720
+ headerTransforms
8721
+ };
8722
+ }
8723
+ async function upsertSandboxEgressSession(input) {
8724
+ const state = getStateAdapter();
8725
+ await state.connect();
8726
+ const ttlMs = Math.max(1, input.ttlMs ?? DEFAULT_SESSION_TTL_MS);
8727
+ const now = Date.now();
8728
+ const session = {
8729
+ requesterId: input.requesterId,
8730
+ expiresAtMs: now + ttlMs,
8731
+ activationId: randomUUID3()
8732
+ };
8733
+ await state.set(sessionKey2(input.sandboxId), session, ttlMs);
8734
+ }
8735
+ async function clearSandboxEgressSession(sandboxId) {
8736
+ const state = getStateAdapter();
8737
+ await state.connect();
8738
+ await state.delete(sessionKey2(sandboxId));
8739
+ }
8740
+ async function getSandboxEgressSession(sandboxId) {
8741
+ const state = getStateAdapter();
8742
+ await state.connect();
8743
+ return parseSession(await state.get(sessionKey2(sandboxId)));
8744
+ }
8745
+ async function setSandboxEgressCredentialLease(sandboxId, session, lease) {
8746
+ const leaseExpiresAtMs = Date.parse(lease.expiresAt);
8747
+ if (!Number.isFinite(leaseExpiresAtMs) || leaseExpiresAtMs <= Date.now()) {
8748
+ return;
8749
+ }
8750
+ const ttlMs = Math.max(
8751
+ 1,
8752
+ Math.min(leaseExpiresAtMs, session.expiresAtMs) - Date.now()
8753
+ );
8754
+ const state = getStateAdapter();
8755
+ await state.connect();
8756
+ await state.set(leaseKey(sandboxId, lease.provider, session), lease, ttlMs);
8757
+ }
8758
+ async function getSandboxEgressCredentialLease(sandboxId, provider, session) {
8759
+ const state = getStateAdapter();
8760
+ await state.connect();
8761
+ return parseLease(await state.get(leaseKey(sandboxId, provider, session)));
8762
+ }
8763
+ async function clearSandboxEgressCredentialLease(sandboxId, provider, session) {
8764
+ const state = getStateAdapter();
8765
+ await state.connect();
8766
+ await state.delete(leaseKey(sandboxId, provider, session));
8767
+ }
8768
+
8466
8769
  // src/chat/sandbox/http-error-details.ts
8467
8770
  var DEFAULT_PREVIEW_LIMIT = 512;
8468
8771
  function toTrimmedString(value, maxChars) {
@@ -8665,6 +8968,7 @@ function throwSandboxOperationError(action, error, includeMissingPath = false) {
8665
8968
  }
8666
8969
 
8667
8970
  // src/chat/sandbox/session.ts
8971
+ import { randomUUID as randomUUID4 } from "crypto";
8668
8972
  import { Sandbox } from "@vercel/sandbox";
8669
8973
  import { createBashTool as createBashTool2 } from "bash-tool";
8670
8974
 
@@ -9238,27 +9542,39 @@ var SANDBOX_RUNTIME = "node22";
9238
9542
  var SANDBOX_RUNTIME_BIN_DIR = `${SANDBOX_WORKSPACE_ROOT}/.junior/bin`;
9239
9543
  var SNAPSHOT_BOOT_RETRY_COUNT = 3;
9240
9544
  var SNAPSHOT_BOOT_RETRY_DELAY_MS = 1e3;
9241
- function mergeNetworkPolicyWithHeaderTransforms(networkPolicy, headerTransforms) {
9242
- const basePolicy = networkPolicy && typeof networkPolicy === "object" && !Array.isArray(networkPolicy) ? { ...networkPolicy } : {};
9243
- const existingAllowRaw = basePolicy.allow;
9244
- const existingAllow = existingAllowRaw && typeof existingAllowRaw === "object" && !Array.isArray(existingAllowRaw) ? Object.fromEntries(
9245
- Object.entries(existingAllowRaw).map(
9246
- ([domain, rules]) => [
9247
- domain,
9248
- Array.isArray(rules) ? [...rules] : []
9249
- ]
9250
- )
9251
- ) : { "*": [] };
9252
- for (const transform of headerTransforms) {
9253
- const currentRules = existingAllow[transform.domain] ?? [];
9254
- existingAllow[transform.domain] = [
9255
- ...currentRules,
9256
- { transform: [{ headers: transform.headers }] }
9257
- ];
9258
- }
9545
+ var SANDBOX_NAME_PREFIX = "junior-";
9546
+ function createBashToolSandboxAdapter(sandbox) {
9259
9547
  return {
9260
- ...basePolicy,
9261
- allow: existingAllow
9548
+ async executeCommand(command) {
9549
+ const result = await sandbox.runCommand({
9550
+ cmd: "bash",
9551
+ args: ["-c", command]
9552
+ });
9553
+ const [stdout, stderr] = await Promise.all([
9554
+ result.stdout(),
9555
+ result.stderr()
9556
+ ]);
9557
+ return {
9558
+ stdout,
9559
+ stderr,
9560
+ exitCode: result.exitCode
9561
+ };
9562
+ },
9563
+ async readFile(filePath) {
9564
+ const content = await sandbox.readFileToBuffer({ path: filePath });
9565
+ if (content == null) {
9566
+ throw new Error(`File not found: ${filePath}`);
9567
+ }
9568
+ return content.toString("utf8");
9569
+ },
9570
+ async writeFiles(files) {
9571
+ await sandbox.writeFiles(
9572
+ files.map((file) => ({
9573
+ path: file.path,
9574
+ content: file.content
9575
+ }))
9576
+ );
9577
+ }
9262
9578
  };
9263
9579
  }
9264
9580
  function truncateOutput(output, maxLength) {
@@ -9291,23 +9607,36 @@ function createSandboxSessionManager(options) {
9291
9607
  let availableSkills = [];
9292
9608
  let availableReferenceFiles = [];
9293
9609
  let toolExecutors;
9610
+ let appliedNetworkPolicyKey;
9294
9611
  const timeoutMs = options?.timeoutMs ?? 1e3 * 60 * 30;
9295
9612
  const traceContext = options?.traceContext ?? {};
9296
9613
  const dependencyProfileHash = getRuntimeDependencyProfileHash(SANDBOX_RUNTIME);
9614
+ const resolveCommandEnv = options?.commandEnv ?? (async () => ({}));
9297
9615
  const withSandboxSpan = (name, op, attributes, callback) => withSpan(name, op, traceContext, callback, attributes);
9298
9616
  const clearSession = () => {
9299
9617
  sandbox = null;
9300
9618
  sandboxIdHint = void 0;
9301
9619
  toolExecutors = void 0;
9620
+ appliedNetworkPolicyKey = void 0;
9302
9621
  };
9303
- const rememberSandbox = async (nextSandbox) => {
9622
+ const createSandboxName = () => `${SANDBOX_NAME_PREFIX}${randomUUID4()}`;
9623
+ const rememberNetworkPolicy = (networkPolicy) => {
9624
+ appliedNetworkPolicyKey = networkPolicy ? JSON.stringify(networkPolicy) : void 0;
9625
+ };
9626
+ const rememberSandbox = async (nextSandbox, rememberOptions) => {
9304
9627
  sandbox = nextSandbox;
9305
9628
  sandboxIdHint = nextSandbox.sandboxId;
9306
9629
  toolExecutors = void 0;
9307
- await options?.onSandboxAcquired?.({
9308
- sandboxId: nextSandbox.sandboxId,
9630
+ const acquired = {
9631
+ sandboxId: sandboxIdHint,
9309
9632
  ...dependencyProfileHash ? { sandboxDependencyProfileHash: dependencyProfileHash } : {}
9310
- });
9633
+ };
9634
+ await options?.onSandboxAcquired?.(acquired);
9635
+ if (rememberOptions?.recordNetworkPolicy) {
9636
+ rememberNetworkPolicy(
9637
+ options?.createNetworkPolicy?.(nextSandbox.sandboxId)
9638
+ );
9639
+ }
9311
9640
  return nextSandbox;
9312
9641
  };
9313
9642
  const failSetup = (error) => {
@@ -9322,6 +9651,30 @@ function createSandboxSessionManager(options) {
9322
9651
  runtimeBinDir: SANDBOX_RUNTIME_BIN_DIR
9323
9652
  });
9324
9653
  };
9654
+ const refreshNetworkPolicy = async (targetSandbox) => {
9655
+ const networkPolicy = options?.createNetworkPolicy?.(
9656
+ targetSandbox.sandboxId
9657
+ );
9658
+ if (!networkPolicy) {
9659
+ return;
9660
+ }
9661
+ const networkPolicyKey = JSON.stringify(networkPolicy);
9662
+ if (appliedNetworkPolicyKey === networkPolicyKey) {
9663
+ return;
9664
+ }
9665
+ await withSandboxSpan(
9666
+ "sandbox.network_policy.update",
9667
+ "sandbox.update",
9668
+ {
9669
+ "app.sandbox.reused": true,
9670
+ "app.sandbox.source": "id_hint"
9671
+ },
9672
+ async () => {
9673
+ await targetSandbox.update({ networkPolicy });
9674
+ }
9675
+ );
9676
+ appliedNetworkPolicyKey = networkPolicyKey;
9677
+ };
9325
9678
  const ensureSandboxReachable = async (targetSandbox, source) => {
9326
9679
  await withSandboxSpan(
9327
9680
  "sandbox.reuse_probe",
@@ -9341,23 +9694,6 @@ function createSandboxSessionManager(options) {
9341
9694
  }
9342
9695
  );
9343
9696
  };
9344
- const invalidateSandboxInstance = async (targetSandbox, reason) => {
9345
- if (sandbox === targetSandbox) {
9346
- clearSession();
9347
- }
9348
- logWarn(
9349
- "sandbox_network_policy_restore_failed",
9350
- traceContext,
9351
- {
9352
- "exception.message": reason instanceof Error ? reason.message : String(reason)
9353
- },
9354
- "Sandbox network policy restore failed; discarding sandbox instance"
9355
- );
9356
- try {
9357
- await targetSandbox.stop({ blocking: true });
9358
- } catch {
9359
- }
9360
- };
9361
9697
  const recreateUnavailableSandbox = async (source) => {
9362
9698
  setSpanAttributes({
9363
9699
  "app.sandbox.recovery.attempted": true,
@@ -9370,17 +9706,22 @@ function createSandboxSessionManager(options) {
9370
9706
  });
9371
9707
  return replacement;
9372
9708
  };
9373
- const createSandboxFromSnapshot = async (snapshotId, sandboxCredentials) => {
9709
+ const createSandboxFromSnapshot = async (snapshotId, sandboxCredentials, initialSandboxName) => {
9374
9710
  for (let attempt = 0; attempt < SNAPSHOT_BOOT_RETRY_COUNT; attempt += 1) {
9711
+ const sandboxName = attempt === 0 ? initialSandboxName : createSandboxName();
9712
+ const networkPolicy = options?.createNetworkPolicy?.(sandboxName);
9375
9713
  try {
9376
- return await Sandbox.create({
9377
- timeout: timeoutMs,
9378
- source: {
9379
- type: "snapshot",
9380
- snapshotId
9381
- },
9382
- ...sandboxCredentials ?? {}
9383
- });
9714
+ return createSandboxInstance(
9715
+ await Sandbox.create({
9716
+ timeout: timeoutMs,
9717
+ ...networkPolicy ? { name: sandboxName, persistent: false, networkPolicy } : {},
9718
+ source: {
9719
+ type: "snapshot",
9720
+ snapshotId
9721
+ },
9722
+ ...sandboxCredentials ?? {}
9723
+ })
9724
+ );
9384
9725
  } catch (error) {
9385
9726
  if (!isSnapshottingError(error) || attempt === SNAPSHOT_BOOT_RETRY_COUNT - 1) {
9386
9727
  throw error;
@@ -9405,18 +9746,23 @@ function createSandboxSessionManager(options) {
9405
9746
  });
9406
9747
  };
9407
9748
  const createSandboxFromResolvedSnapshot = async (params) => {
9408
- const { runtime, snapshot, sandboxCredentials } = params;
9749
+ const { runtime, snapshot, sandboxCredentials, sandboxName } = params;
9409
9750
  if (!snapshot.snapshotId) {
9410
- return await Sandbox.create({
9411
- timeout: timeoutMs,
9412
- runtime,
9413
- ...sandboxCredentials ?? {}
9414
- });
9751
+ const networkPolicy = options?.createNetworkPolicy?.(sandboxName);
9752
+ return createSandboxInstance(
9753
+ await Sandbox.create({
9754
+ timeout: timeoutMs,
9755
+ runtime,
9756
+ ...networkPolicy ? { name: sandboxName, persistent: false, networkPolicy } : {},
9757
+ ...sandboxCredentials ?? {}
9758
+ })
9759
+ );
9415
9760
  }
9416
9761
  try {
9417
9762
  return await createSandboxFromSnapshot(
9418
9763
  snapshot.snapshotId,
9419
- sandboxCredentials
9764
+ sandboxCredentials,
9765
+ sandboxName
9420
9766
  );
9421
9767
  } catch (error) {
9422
9768
  if (!isSnapshotMissingError(error)) {
@@ -9436,13 +9782,15 @@ function createSandboxSessionManager(options) {
9436
9782
  }
9437
9783
  return await createSandboxFromSnapshot(
9438
9784
  rebuiltSnapshot.snapshotId,
9439
- sandboxCredentials
9785
+ sandboxCredentials,
9786
+ sandboxName
9440
9787
  );
9441
9788
  }
9442
9789
  };
9443
9790
  const createFreshSandbox = async () => {
9444
9791
  const runtime = SANDBOX_RUNTIME;
9445
9792
  const sandboxCredentials = getVercelSandboxCredentials();
9793
+ const sandboxName = createSandboxName();
9446
9794
  let createdSandbox;
9447
9795
  try {
9448
9796
  createdSandbox = await withSandboxSpan(
@@ -9462,7 +9810,8 @@ function createSandboxSessionManager(options) {
9462
9810
  return await createSandboxFromResolvedSnapshot({
9463
9811
  runtime,
9464
9812
  snapshot,
9465
- sandboxCredentials
9813
+ sandboxCredentials,
9814
+ sandboxName
9466
9815
  });
9467
9816
  }
9468
9817
  );
@@ -9474,7 +9823,7 @@ function createSandboxSessionManager(options) {
9474
9823
  } catch (error) {
9475
9824
  return failSetup(error);
9476
9825
  }
9477
- return await rememberSandbox(createdSandbox);
9826
+ return await rememberSandbox(createdSandbox, { recordNetworkPolicy: true });
9478
9827
  };
9479
9828
  const discardHintIfProfileChanged = () => {
9480
9829
  if (sandbox || !sandboxIdHint || dependencyProfileHash === options?.sandboxDependencyProfileHash) {
@@ -9497,6 +9846,7 @@ function createSandboxSessionManager(options) {
9497
9846
  }
9498
9847
  try {
9499
9848
  await ensureSandboxReachable(cachedSandbox, "memory");
9849
+ await refreshNetworkPolicy(cachedSandbox);
9500
9850
  return cachedSandbox;
9501
9851
  } catch (error) {
9502
9852
  if (isSandboxUnavailableError(error)) {
@@ -9519,15 +9869,19 @@ function createSandboxSessionManager(options) {
9519
9869
  "app.sandbox.reused": true,
9520
9870
  "app.sandbox.source": "id_hint"
9521
9871
  },
9522
- async () => await Sandbox.get({
9523
- sandboxId: sandboxIdHint,
9524
- ...sandboxCredentials ?? {}
9525
- })
9872
+ async () => createSandboxInstance(
9873
+ await Sandbox.get({
9874
+ name: sandboxIdHint,
9875
+ resume: true,
9876
+ ...sandboxCredentials ?? {}
9877
+ })
9878
+ )
9526
9879
  );
9527
9880
  } catch {
9528
9881
  return null;
9529
9882
  }
9530
9883
  try {
9884
+ await refreshNetworkPolicy(hintedSandbox);
9531
9885
  await syncSkills(hintedSandbox);
9532
9886
  return await rememberSandbox(hintedSandbox);
9533
9887
  } catch (error) {
@@ -9582,37 +9936,6 @@ function createSandboxSessionManager(options) {
9582
9936
  stderrTruncated: stderr.truncated
9583
9937
  };
9584
9938
  };
9585
- const withTemporaryHeaderTransforms = async (sandboxInstance, headerTransforms, callback) => {
9586
- if (!headerTransforms || headerTransforms.length === 0) {
9587
- return await callback();
9588
- }
9589
- const restoreNetworkPolicy = sandboxInstance.networkPolicy ?? "allow-all";
9590
- const policy = mergeNetworkPolicyWithHeaderTransforms(
9591
- restoreNetworkPolicy,
9592
- headerTransforms
9593
- );
9594
- await sandboxInstance.updateNetworkPolicy(policy);
9595
- let callbackError;
9596
- let restoreError;
9597
- let result;
9598
- try {
9599
- result = await callback();
9600
- } catch (error) {
9601
- callbackError = error;
9602
- throw error;
9603
- } finally {
9604
- try {
9605
- await sandboxInstance.updateNetworkPolicy(restoreNetworkPolicy);
9606
- } catch (error) {
9607
- restoreError = error;
9608
- await invalidateSandboxInstance(sandboxInstance, error);
9609
- }
9610
- }
9611
- if (restoreError && !callbackError) {
9612
- throw restoreError;
9613
- }
9614
- return result;
9615
- };
9616
9939
  const extendKeepAlive = async (activeSandbox) => {
9617
9940
  const keepAliveMs = parseKeepAliveMs();
9618
9941
  if (keepAliveMs === 0) {
@@ -9633,6 +9956,7 @@ function createSandboxSessionManager(options) {
9633
9956
  }
9634
9957
  };
9635
9958
  const buildToolExecutors = async (sandboxInstance) => {
9959
+ const activeSandboxId = sandboxInstance.sandboxId;
9636
9960
  const toolkit = await withSandboxSpan(
9637
9961
  "sandbox.bash_tool.init",
9638
9962
  "sandbox.tool.init",
@@ -9641,7 +9965,7 @@ function createSandboxSessionManager(options) {
9641
9965
  "app.sandbox.destination": SANDBOX_WORKSPACE_ROOT
9642
9966
  },
9643
9967
  async () => await createBashTool2({
9644
- sandbox: sandboxInstance,
9968
+ sandbox: createBashToolSandboxAdapter(sandboxInstance),
9645
9969
  destination: SANDBOX_WORKSPACE_ROOT
9646
9970
  })
9647
9971
  );
@@ -9652,47 +9976,69 @@ function createSandboxSessionManager(options) {
9652
9976
  }
9653
9977
  return {
9654
9978
  bash: async (input) => {
9655
- const script = buildNonInteractiveShellScript(input.command, {
9656
- env: input.env,
9657
- pathPrefix: `${SANDBOX_RUNTIME_BIN_DIR}:$PATH`
9658
- });
9659
- const controller = input.timeoutMs && input.timeoutMs > 0 ? new AbortController() : void 0;
9979
+ await options?.beforeCommand?.(activeSandboxId);
9660
9980
  let timedOut = false;
9661
- const timeoutId = controller ? setTimeout(() => {
9662
- timedOut = true;
9663
- controller.abort();
9664
- }, input.timeoutMs) : void 0;
9665
- return await withTemporaryHeaderTransforms(
9666
- sandboxInstance,
9667
- input.headerTransforms,
9668
- async () => {
9669
- try {
9670
- const commandResult2 = await sandboxInstance.runCommand({
9671
- cmd: "bash",
9672
- args: ["-c", script],
9673
- cwd: SANDBOX_WORKSPACE_ROOT,
9674
- ...controller ? { signal: controller.signal } : {}
9675
- });
9676
- return await readCommandOutput(commandResult2);
9677
- } catch (error) {
9678
- if (timedOut) {
9679
- return {
9680
- stdout: "",
9681
- stderr: `Command timed out after ${input.timeoutMs}ms`,
9682
- exitCode: 124,
9683
- stdoutTruncated: false,
9684
- stderrTruncated: false,
9685
- timedOut: true
9686
- };
9687
- }
9688
- throw error;
9689
- } finally {
9690
- if (timeoutId) {
9691
- clearTimeout(timeoutId);
9692
- }
9693
- }
9981
+ let timeoutId;
9982
+ let commandFinished = false;
9983
+ const finishCommand = async () => {
9984
+ if (commandFinished) {
9985
+ return;
9694
9986
  }
9695
- );
9987
+ commandFinished = true;
9988
+ await options?.afterCommand?.(activeSandboxId);
9989
+ };
9990
+ const finishCommandBestEffort = async () => {
9991
+ try {
9992
+ await finishCommand();
9993
+ } catch (error) {
9994
+ logWarn(
9995
+ "sandbox_command_cleanup_failed",
9996
+ traceContext,
9997
+ {
9998
+ "app.sandbox.id": activeSandboxId,
9999
+ "error.type": error instanceof Error ? error.name : "sandbox_command_cleanup_error"
10000
+ },
10001
+ "Sandbox command cleanup failed"
10002
+ );
10003
+ }
10004
+ };
10005
+ try {
10006
+ const sandboxCommandEnv = await resolveCommandEnv();
10007
+ const script = buildNonInteractiveShellScript(input.command, {
10008
+ env: { ...sandboxCommandEnv, ...input.env ?? {} },
10009
+ pathPrefix: `${SANDBOX_RUNTIME_BIN_DIR}:$PATH`
10010
+ });
10011
+ const controller = input.timeoutMs && input.timeoutMs > 0 ? new AbortController() : void 0;
10012
+ timeoutId = controller ? setTimeout(() => {
10013
+ timedOut = true;
10014
+ controller.abort();
10015
+ }, input.timeoutMs) : void 0;
10016
+ const commandResult2 = await sandboxInstance.runCommand({
10017
+ cmd: "bash",
10018
+ args: ["-c", script],
10019
+ cwd: SANDBOX_WORKSPACE_ROOT,
10020
+ ...controller ? { signal: controller.signal } : {}
10021
+ });
10022
+ await finishCommandBestEffort();
10023
+ return await readCommandOutput(commandResult2);
10024
+ } catch (error) {
10025
+ if (timedOut) {
10026
+ return {
10027
+ stdout: "",
10028
+ stderr: `Command timed out after ${input.timeoutMs}ms`,
10029
+ exitCode: 124,
10030
+ stdoutTruncated: false,
10031
+ stderrTruncated: false,
10032
+ timedOut: true
10033
+ };
10034
+ }
10035
+ throw error;
10036
+ } finally {
10037
+ if (timeoutId) {
10038
+ clearTimeout(timeoutId);
10039
+ }
10040
+ await finishCommandBestEffort();
10041
+ }
9696
10042
  },
9697
10043
  readFile: async (input) => await executeReadFile(input, {
9698
10044
  toolCallId: "sandbox-read-file",
@@ -9725,7 +10071,7 @@ function createSandboxSessionManager(options) {
9725
10071
  availableReferenceFiles = [...files];
9726
10072
  },
9727
10073
  getSandboxId() {
9728
- return sandbox?.sandboxId ?? sandboxIdHint;
10074
+ return sandbox ? sandbox.sandboxId : sandboxIdHint;
9729
10075
  },
9730
10076
  getDependencyProfileHash() {
9731
10077
  return dependencyProfileHash;
@@ -9748,7 +10094,7 @@ function createSandboxSessionManager(options) {
9748
10094
  "app.sandbox.stop.blocking": true
9749
10095
  },
9750
10096
  async () => {
9751
- await activeSandbox.stop({ blocking: true });
10097
+ await activeSandbox.stop();
9752
10098
  }
9753
10099
  );
9754
10100
  sandbox = null;
@@ -9767,21 +10113,6 @@ var SANDBOX_TOOL_NAMES = /* @__PURE__ */ new Set([
9767
10113
  "listDir",
9768
10114
  "writeFile"
9769
10115
  ]);
9770
- function parseHeaderTransforms(raw) {
9771
- if (!Array.isArray(raw)) {
9772
- return void 0;
9773
- }
9774
- return raw.filter(
9775
- (value) => Boolean(value && typeof value === "object")
9776
- ).map((transform) => ({
9777
- domain: String(transform.domain ?? "").trim(),
9778
- headers: transform.headers && typeof transform.headers === "object" && !Array.isArray(transform.headers) ? Object.fromEntries(
9779
- Object.entries(transform.headers).filter(([, value]) => typeof value === "string").map(([key, value]) => [key, value])
9780
- ) : {}
9781
- })).filter(
9782
- (transform) => transform.domain.length > 0 && Object.keys(transform.headers).length > 0
9783
- );
9784
- }
9785
10116
  function parseEnv(raw) {
9786
10117
  if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
9787
10118
  return void 0;
@@ -9790,27 +10121,33 @@ function parseEnv(raw) {
9790
10121
  Object.entries(raw).filter(([, value]) => typeof value === "string").map(([key, value]) => [key, value])
9791
10122
  );
9792
10123
  }
9793
- function createSandboxWorkspace(sandbox) {
9794
- return {
9795
- sandboxId: sandbox.sandboxId,
9796
- readFileToBuffer(input) {
9797
- return sandbox.readFileToBuffer(input);
9798
- },
9799
- runCommand(input) {
9800
- return sandbox.runCommand(input);
9801
- }
9802
- };
9803
- }
9804
10124
  function createSandboxExecutor(options) {
9805
10125
  let availableSkills = [];
9806
10126
  let referenceFiles = [];
9807
10127
  const traceContext = options?.traceContext ?? {};
10128
+ const credentialEgress = options?.credentialEgress;
10129
+ const syncSandboxEgressSession = credentialEgress ? async (sandboxId) => {
10130
+ await upsertSandboxEgressSession({
10131
+ sandboxId,
10132
+ requesterId: credentialEgress.requesterId,
10133
+ ttlMs: options?.timeoutMs
10134
+ });
10135
+ } : void 0;
10136
+ const clearSandboxEgressSessionForCommand = credentialEgress ? async (sandboxId) => {
10137
+ await clearSandboxEgressSession(sandboxId);
10138
+ } : void 0;
9808
10139
  const sessionManager = createSandboxSessionManager({
9809
10140
  sandboxId: options?.sandboxId,
9810
10141
  sandboxDependencyProfileHash: options?.sandboxDependencyProfileHash,
9811
10142
  timeoutMs: options?.timeoutMs,
9812
10143
  traceContext,
9813
- onSandboxAcquired: options?.onSandboxAcquired
10144
+ commandEnv: credentialEgress ? async () => await resolveSandboxCommandEnvironment() : void 0,
10145
+ createNetworkPolicy: credentialEgress ? buildSandboxEgressNetworkPolicy : void 0,
10146
+ beforeCommand: syncSandboxEgressSession,
10147
+ afterCommand: clearSandboxEgressSessionForCommand,
10148
+ onSandboxAcquired: async (sandbox) => {
10149
+ await options?.onSandboxAcquired?.(sandbox);
10150
+ }
9814
10151
  });
9815
10152
  const withSandboxSpan = (name, op, attributes, callback) => withSpan(name, op, traceContext, callback, attributes);
9816
10153
  const logSandboxBootRequest = (trigger, details = {}) => {
@@ -9828,7 +10165,6 @@ function createSandboxExecutor(options) {
9828
10165
  );
9829
10166
  };
9830
10167
  const executeBashTool = async (rawInput, command) => {
9831
- const headerTransforms = parseHeaderTransforms(rawInput.headerTransforms);
9832
10168
  const env = parseEnv(rawInput.env);
9833
10169
  const timeoutMs = positiveInteger(rawInput.timeoutMs);
9834
10170
  logSandboxBootRequest("tool.bash", {
@@ -9845,7 +10181,6 @@ function createSandboxExecutor(options) {
9845
10181
  try {
9846
10182
  const response = await executeBash({
9847
10183
  command,
9848
- ...headerTransforms ? { headerTransforms } : {},
9849
10184
  ...env ? { env } : {},
9850
10185
  ...timeoutMs ? { timeoutMs } : {}
9851
10186
  });
@@ -10149,7 +10484,7 @@ function createSandboxExecutor(options) {
10149
10484
  return SANDBOX_TOOL_NAMES.has(toolName);
10150
10485
  },
10151
10486
  async createSandbox() {
10152
- return createSandboxWorkspace(await sessionManager.createSandbox());
10487
+ return await sessionManager.createSandbox();
10153
10488
  },
10154
10489
  execute,
10155
10490
  async dispose() {
@@ -10250,34 +10585,6 @@ function buildSandboxInput(toolName, params) {
10250
10585
  return params;
10251
10586
  }
10252
10587
 
10253
- // src/chat/tools/execution/inject-credentials.ts
10254
- function resolveCredentialInjection(toolName, command, capabilityRuntime, sandbox) {
10255
- if (toolName !== "bash" || !capabilityRuntime) {
10256
- return {};
10257
- }
10258
- const headerTransforms = capabilityRuntime.getTurnHeaderTransforms();
10259
- const env = capabilityRuntime.getTurnEnv();
10260
- const isCustomCommand = /^jr-rpc(?:\s|$)/.test(command.trim());
10261
- const shouldLog = !isCustomCommand && Boolean(headerTransforms && headerTransforms.length > 0);
10262
- if (shouldLog) {
10263
- const headerDomains = (headerTransforms ?? []).map(
10264
- (transform) => transform.domain
10265
- );
10266
- const skillName = sandbox.getActiveSkill()?.name;
10267
- logInfo(
10268
- "credential_inject_start",
10269
- {},
10270
- {
10271
- "app.skill.name": skillName,
10272
- "app.credential.delivery": "header_transform",
10273
- "app.credential.header_domains": headerDomains
10274
- },
10275
- `Injecting scoped credential headers for sandbox command (${skillName ?? "unknown skill"} \u2192 ${headerDomains.join(", ")})`
10276
- );
10277
- }
10278
- return { headerTransforms, env };
10279
- }
10280
-
10281
10588
  // src/chat/tools/execution/normalize-result.ts
10282
10589
  function isStructuredToolExecutionResult(value) {
10283
10590
  const content = value?.content;
@@ -10367,7 +10674,7 @@ function handleToolExecutionError(error, toolName, toolCallId, shouldTrace, trac
10367
10674
  }
10368
10675
 
10369
10676
  // src/chat/tools/agent-tools.ts
10370
- function createAgentTools(tools, sandbox, spanContext, onStatus, sandboxExecutor, capabilityRuntime, pluginAuthOrchestration, onToolCall) {
10677
+ function createAgentTools(tools, sandbox, spanContext, onStatus, sandboxExecutor, pluginAuthOrchestration, onToolCall) {
10371
10678
  const shouldTrace = shouldEmitDevAgentTrace();
10372
10679
  return Object.entries(tools).map(([toolName, toolDef]) => ({
10373
10680
  name: toolName,
@@ -10407,21 +10714,11 @@ function createAgentTools(tools, sandbox, spanContext, onStatus, sandboxExecutor
10407
10714
  };
10408
10715
  }
10409
10716
  const bashCommand = toolName === "bash" && typeof parsed.command === "string" ? parsed.command.trim() : "";
10410
- const injection = resolveCredentialInjection(
10411
- toolName,
10412
- bashCommand,
10413
- capabilityRuntime,
10414
- sandbox
10415
- );
10416
10717
  const sandboxInput = buildSandboxInput(toolName, parsed);
10417
10718
  const isSandbox = Boolean(sandboxExecutor?.canExecute(toolName));
10418
10719
  const result = isSandbox ? await sandboxExecutor.execute({
10419
10720
  toolName,
10420
- input: toolName === "bash" && (injection.headerTransforms || injection.env) ? {
10421
- ...sandboxInput,
10422
- ...injection.headerTransforms ? { headerTransforms: injection.headerTransforms } : {},
10423
- ...injection.env ? { env: injection.env } : {}
10424
- } : sandboxInput
10721
+ input: sandboxInput
10425
10722
  }) : await toolDef.execute(parsed, {
10426
10723
  experimental_context: sandbox
10427
10724
  });
@@ -11284,6 +11581,7 @@ ${typeof result.stderr === "string" ? result.stderr : ""}`.toLowerCase();
11284
11581
  return false;
11285
11582
  }
11286
11583
  return [
11584
+ /\bjunior-auth-required\b/,
11287
11585
  /\b401\b/,
11288
11586
  /\bunauthorized\b/,
11289
11587
  /\bbad credentials\b/,
@@ -11296,6 +11594,33 @@ ${typeof result.stderr === "string" ? result.stderr : ""}`.toLowerCase();
11296
11594
  /\breauthoriz/
11297
11595
  ].some((pattern) => pattern.test(text));
11298
11596
  }
11597
+ function commandText(details) {
11598
+ if (!details || typeof details !== "object") {
11599
+ return "";
11600
+ }
11601
+ const result = details;
11602
+ return `${typeof result.stdout === "string" ? result.stdout : ""}
11603
+ ${typeof result.stderr === "string" ? result.stderr : ""}`;
11604
+ }
11605
+ function explicitAuthRequiredProvider(details) {
11606
+ const match = /\bjunior-auth-required\s+provider=([a-z0-9-]+)\b/.exec(
11607
+ commandText(details).toLowerCase()
11608
+ );
11609
+ return match?.[1];
11610
+ }
11611
+ function registeredProviderNames() {
11612
+ const providers = /* @__PURE__ */ new Set();
11613
+ for (const plugin of getPluginProviders()) {
11614
+ const domains = [
11615
+ ...plugin.manifest.credentials?.domains ?? [],
11616
+ ...plugin.manifest.domains ?? []
11617
+ ];
11618
+ if (domains.length > 0) {
11619
+ providers.add(plugin.manifest.name);
11620
+ }
11621
+ }
11622
+ return [...providers].sort((left, right) => left.localeCompare(right));
11623
+ }
11299
11624
  function commandTargetsProvider(provider, command, details) {
11300
11625
  const normalizedCommand = command.trim().toLowerCase();
11301
11626
  if (!normalizedCommand) {
@@ -11310,16 +11635,15 @@ function commandTargetsProvider(provider, command, details) {
11310
11635
  const credentials = manifest?.credentials;
11311
11636
  if (credentials) {
11312
11637
  candidates.add(credentials.authTokenEnv.toLowerCase());
11313
- for (const domain of credentials.apiDomains) {
11638
+ for (const domain of credentials.domains) {
11314
11639
  candidates.add(domain.toLowerCase());
11315
11640
  }
11316
11641
  }
11317
- for (const domain of manifest?.apiDomains ?? []) {
11642
+ for (const domain of manifest?.domains ?? []) {
11318
11643
  candidates.add(domain.toLowerCase());
11319
11644
  }
11320
11645
  const combinedText = `${normalizedCommand}
11321
- ${details.stdout?.toLowerCase() ?? ""}
11322
- ${details.stderr?.toLowerCase() ?? ""}`;
11646
+ ${commandText(details).toLowerCase()}`;
11323
11647
  return [...candidates].some((candidate) => combinedText.includes(candidate));
11324
11648
  }
11325
11649
  function createPluginAuthOrchestration(deps, abortAgent) {
@@ -11377,23 +11701,22 @@ function createPluginAuthOrchestration(deps, abortAgent) {
11377
11701
  abortAgent();
11378
11702
  throw pendingPause;
11379
11703
  };
11380
- const handleCredentialUnavailable = async (input) => {
11381
- if (pendingPause) {
11382
- throw pendingPause;
11383
- }
11384
- if (!deps.requesterId || !getPluginOAuthConfig(input.error.provider)) {
11385
- throw input.error;
11386
- }
11387
- return await startAuthorizationPause(
11388
- input.error.provider,
11389
- input.activeSkill
11390
- );
11391
- };
11392
11704
  return {
11393
- handleCredentialUnavailable,
11394
11705
  handleCommandFailure: async (input) => {
11395
- const provider = input.activeSkill?.pluginProvider;
11396
- if (!provider || !deps.requesterId || !deps.userTokenStore || !getPluginOAuthConfig(provider) || !isCommandAuthFailure(input.details) || !commandTargetsProvider(provider, input.command, input.details)) {
11706
+ const providers = registeredProviderNames();
11707
+ const authFailure = isCommandAuthFailure(input.details);
11708
+ if (!authFailure) {
11709
+ return;
11710
+ }
11711
+ const explicitProvider = explicitAuthRequiredProvider(input.details);
11712
+ const provider = explicitProvider && providers.includes(explicitProvider) ? explicitProvider : providers.find(
11713
+ (availableProvider) => commandTargetsProvider(
11714
+ availableProvider,
11715
+ input.command,
11716
+ input.details
11717
+ )
11718
+ );
11719
+ if (!provider || !deps.requesterId || !deps.userTokenStore || !getPluginOAuthConfig(provider)) {
11397
11720
  return;
11398
11721
  }
11399
11722
  await startAuthorizationPause(provider, input.activeSkill, {
@@ -11659,14 +11982,15 @@ async function generateAssistantReply(messageText, context = {}) {
11659
11982
  ...context.configuration ?? {},
11660
11983
  ...persistedConfigurationValues
11661
11984
  };
11662
- const capabilityRuntime = createSkillCapabilityRuntime({
11663
- requesterId: context.requester?.userId
11664
- });
11985
+ const requesterId = context.requester?.userId;
11665
11986
  const userTokenStore = createUserTokenStore();
11666
11987
  sandboxExecutor = createSandboxExecutor({
11667
11988
  sandboxId: context.sandbox?.sandboxId,
11668
11989
  sandboxDependencyProfileHash: context.sandbox?.sandboxDependencyProfileHash,
11669
11990
  traceContext: spanContext,
11991
+ credentialEgress: requesterId ? {
11992
+ requesterId
11993
+ } : void 0,
11670
11994
  onSandboxAcquired: async (sandbox2) => {
11671
11995
  lastKnownSandboxId = sandbox2.sandboxId;
11672
11996
  lastKnownSandboxDependencyProfileHash = sandbox2.sandboxDependencyProfileHash;
@@ -11830,25 +12154,6 @@ async function generateAssistantReply(messageText, context = {}) {
11830
12154
  const syncResumeState = () => {
11831
12155
  loadedSkillNamesForResume = activeSkills.map((skill) => skill.name);
11832
12156
  };
11833
- const enableSkillCredentials = async (skill, reason) => {
11834
- if (!skill?.pluginProvider) {
11835
- return;
11836
- }
11837
- try {
11838
- await capabilityRuntime.enableCredentialsForTurn({
11839
- activeSkill: skill,
11840
- reason
11841
- });
11842
- } catch (error) {
11843
- if (error instanceof CredentialUnavailableError && context.requester?.userId) {
11844
- await pluginAuth.handleCredentialUnavailable({
11845
- activeSkill: skill,
11846
- error
11847
- });
11848
- }
11849
- throw error;
11850
- }
11851
- };
11852
12157
  setTags({
11853
12158
  conversationId: spanContext.conversationId,
11854
12159
  slackThreadId: context.correlation?.threadId,
@@ -11890,10 +12195,6 @@ async function generateAssistantReply(messageText, context = {}) {
11890
12195
  if (mcpAuth.getPendingPause()) {
11891
12196
  return void 0;
11892
12197
  }
11893
- await enableSkillCredentials(
11894
- effective,
11895
- `skill:${effective.name}:turn:load`
11896
- );
11897
12198
  if (!effective.pluginProvider) {
11898
12199
  return void 0;
11899
12200
  }
@@ -11946,7 +12247,6 @@ async function generateAssistantReply(messageText, context = {}) {
11946
12247
  timeoutResumeMessages = existingCheckpoint?.piMessages ?? [];
11947
12248
  throw mcpAuth.getPendingPause();
11948
12249
  }
11949
- await enableSkillCredentials(skill, `skill:${skill.name}:turn:resume`);
11950
12250
  }
11951
12251
  syncResumeState();
11952
12252
  const activeMcpCatalogs = toActiveMcpCatalogSummaries(
@@ -12007,7 +12307,6 @@ async function generateAssistantReply(messageText, context = {}) {
12007
12307
  spanContext,
12008
12308
  context.onStatus,
12009
12309
  sandboxExecutor,
12010
- capabilityRuntime,
12011
12310
  pluginAuth,
12012
12311
  onToolCall
12013
12312
  );
@@ -12017,7 +12316,6 @@ async function generateAssistantReply(messageText, context = {}) {
12017
12316
  spanContext,
12018
12317
  context.onStatus,
12019
12318
  sandboxExecutor,
12020
- capabilityRuntime,
12021
12319
  pluginAuth,
12022
12320
  onToolCall
12023
12321
  );
@@ -14368,6 +14666,337 @@ async function GET5(request, provider, waitUntil) {
14368
14666
  });
14369
14667
  }
14370
14668
 
14669
+ // src/chat/sandbox/egress-oidc.ts
14670
+ import {
14671
+ createRemoteJWKSet,
14672
+ decodeJwt,
14673
+ jwtVerify
14674
+ } from "jose";
14675
+ var OIDC_DISCOVERY_CACHE_TTL_MS = 60 * 60 * 1e3;
14676
+ var OIDC_DISCOVERY_CACHE_MAX_ENTRIES = 8;
14677
+ var jwksByIssuer = /* @__PURE__ */ new Map();
14678
+ function buildDiscoveryUrl(issuer) {
14679
+ const url = new URL(issuer);
14680
+ if (url.protocol !== "https:" || url.hostname !== "oidc.vercel.com") {
14681
+ throw new Error("Unexpected Vercel OIDC issuer");
14682
+ }
14683
+ url.pathname = `${url.pathname.replace(/\/$/, "")}/.well-known/openid-configuration`;
14684
+ url.search = "";
14685
+ url.hash = "";
14686
+ return url;
14687
+ }
14688
+ function buildJwksUrl(value) {
14689
+ const url = new URL(value);
14690
+ if (url.protocol !== "https:") {
14691
+ throw new Error("Vercel OIDC discovery jwks_uri must use HTTPS");
14692
+ }
14693
+ return url;
14694
+ }
14695
+ async function getJwks(issuer) {
14696
+ const now = Date.now();
14697
+ const cached = jwksByIssuer.get(issuer);
14698
+ if (cached && cached.expiresAtMs > now) {
14699
+ return cached.jwks;
14700
+ }
14701
+ if (cached) {
14702
+ jwksByIssuer.delete(issuer);
14703
+ }
14704
+ const response = await fetch(buildDiscoveryUrl(issuer), {
14705
+ redirect: "error"
14706
+ });
14707
+ if (!response.ok) {
14708
+ throw new Error("Unable to load Vercel OIDC discovery metadata");
14709
+ }
14710
+ const config = await response.json();
14711
+ if (!config.jwks_uri) {
14712
+ throw new Error("Vercel OIDC discovery metadata did not include jwks_uri");
14713
+ }
14714
+ const jwks = createRemoteJWKSet(buildJwksUrl(config.jwks_uri));
14715
+ if (!jwksByIssuer.has(issuer) && jwksByIssuer.size >= OIDC_DISCOVERY_CACHE_MAX_ENTRIES) {
14716
+ const oldestIssuer = jwksByIssuer.keys().next().value;
14717
+ if (oldestIssuer) {
14718
+ jwksByIssuer.delete(oldestIssuer);
14719
+ }
14720
+ }
14721
+ jwksByIssuer.set(issuer, {
14722
+ jwks,
14723
+ expiresAtMs: now + OIDC_DISCOVERY_CACHE_TTL_MS
14724
+ });
14725
+ return jwks;
14726
+ }
14727
+ function expectedVercelOidcAudience() {
14728
+ const audience = process.env.VERCEL_OIDC_AUDIENCE?.trim();
14729
+ if (!audience) {
14730
+ throw new Error("VERCEL_OIDC_AUDIENCE is required for sandbox egress OIDC");
14731
+ }
14732
+ return audience;
14733
+ }
14734
+ function validateVercelSandboxOidcClaims(payload, sandboxId) {
14735
+ const expectedTeamId = process.env.VERCEL_TEAM_ID?.trim();
14736
+ const expectedProjectId = process.env.VERCEL_PROJECT_ID?.trim();
14737
+ if (!expectedProjectId) {
14738
+ throw new Error("VERCEL_PROJECT_ID is required for sandbox egress OIDC");
14739
+ }
14740
+ if (expectedTeamId && (typeof payload.owner_id !== "string" || payload.owner_id !== expectedTeamId)) {
14741
+ throw new Error("Vercel OIDC token belongs to a different team");
14742
+ }
14743
+ if (typeof payload.project_id !== "string" || payload.project_id !== expectedProjectId) {
14744
+ throw new Error("Vercel OIDC token belongs to a different project");
14745
+ }
14746
+ if (payload.sandbox_id !== sandboxId) {
14747
+ throw new Error("Vercel OIDC token belongs to a different sandbox");
14748
+ }
14749
+ }
14750
+ async function verifyVercelSandboxOidcToken(token, sandboxId) {
14751
+ const unverified = decodeJwt(token);
14752
+ if (typeof unverified.iss !== "string") {
14753
+ throw new Error("Vercel OIDC token did not include an issuer");
14754
+ }
14755
+ const audience = expectedVercelOidcAudience();
14756
+ const jwks = await getJwks(unverified.iss);
14757
+ const verified = await jwtVerify(token, jwks, {
14758
+ issuer: unverified.iss,
14759
+ audience
14760
+ });
14761
+ validateVercelSandboxOidcClaims(verified.payload, sandboxId);
14762
+ return verified.payload;
14763
+ }
14764
+
14765
+ // src/chat/sandbox/egress-proxy.ts
14766
+ var OIDC_TOKEN_HEADER = "vercel-sandbox-oidc-token";
14767
+ var FORWARDED_HOST_HEADER = "vercel-forwarded-host";
14768
+ var FORWARDED_SCHEME_HEADER = "vercel-forwarded-scheme";
14769
+ var FORWARDED_PORT_HEADER = "vercel-forwarded-port";
14770
+ var ROUTE_PREFIX = "/api/internal/sandbox-egress";
14771
+ var HOP_BY_HOP_HEADERS = /* @__PURE__ */ new Set([
14772
+ "connection",
14773
+ "host",
14774
+ "keep-alive",
14775
+ "proxy-authenticate",
14776
+ "proxy-authorization",
14777
+ "te",
14778
+ "trailer",
14779
+ "transfer-encoding",
14780
+ "upgrade"
14781
+ ]);
14782
+ var PROXY_ONLY_HEADERS = /* @__PURE__ */ new Set([
14783
+ OIDC_TOKEN_HEADER,
14784
+ FORWARDED_HOST_HEADER,
14785
+ FORWARDED_SCHEME_HEADER,
14786
+ FORWARDED_PORT_HEADER
14787
+ ]);
14788
+ var AUTH_REJECTION_STATUS = /* @__PURE__ */ new Set([401, 403]);
14789
+ function jsonError(message, status) {
14790
+ return Response.json({ error: message }, { status });
14791
+ }
14792
+ function normalizeHost(value) {
14793
+ const trimmed = value.trim().toLowerCase();
14794
+ if (!trimmed || trimmed.includes("/") || trimmed.includes("\\") || trimmed.includes(":")) {
14795
+ return void 0;
14796
+ }
14797
+ return trimmed.replace(/\.$/, "");
14798
+ }
14799
+ function normalizeScheme(value) {
14800
+ return value.trim().toLowerCase() === "https" ? "https" : void 0;
14801
+ }
14802
+ function normalizePort(value) {
14803
+ if (!value) {
14804
+ return void 0;
14805
+ }
14806
+ const trimmed = value.trim();
14807
+ if (!/^\d{1,5}$/.test(trimmed)) {
14808
+ return void 0;
14809
+ }
14810
+ const port = Number.parseInt(trimmed, 10);
14811
+ return port >= 1 && port <= 65535 ? trimmed : void 0;
14812
+ }
14813
+ function upstreamPath(request, sandboxId) {
14814
+ const url = new URL(request.url);
14815
+ const prefix = `${ROUTE_PREFIX}/${encodeURIComponent(sandboxId)}`;
14816
+ if (url.pathname === prefix) {
14817
+ return `/${url.search}`;
14818
+ }
14819
+ if (url.pathname.startsWith(`${prefix}/`)) {
14820
+ return `${url.pathname.slice(prefix.length)}${url.search}`;
14821
+ }
14822
+ return void 0;
14823
+ }
14824
+ function buildUpstreamUrl(request, sandboxId) {
14825
+ const forwardedHost = request.headers.get(FORWARDED_HOST_HEADER);
14826
+ if (!forwardedHost?.trim()) {
14827
+ return { ok: false, error: "Missing forwarded host" };
14828
+ }
14829
+ const host = normalizeHost(forwardedHost);
14830
+ if (!host) {
14831
+ return { ok: false, error: "Invalid forwarded host" };
14832
+ }
14833
+ const forwardedScheme = request.headers.get(FORWARDED_SCHEME_HEADER);
14834
+ if (!forwardedScheme?.trim()) {
14835
+ return { ok: false, error: "Missing forwarded scheme" };
14836
+ }
14837
+ const scheme = normalizeScheme(forwardedScheme);
14838
+ if (!scheme) {
14839
+ return { ok: false, error: "Forwarded scheme must be https" };
14840
+ }
14841
+ const forwardedPort = request.headers.get(FORWARDED_PORT_HEADER);
14842
+ const port = normalizePort(forwardedPort);
14843
+ if (forwardedPort && !port) {
14844
+ return { ok: false, error: "Invalid forwarded port" };
14845
+ }
14846
+ const path11 = upstreamPath(request, sandboxId);
14847
+ if (!path11) {
14848
+ return { ok: false, error: "Invalid egress route" };
14849
+ }
14850
+ try {
14851
+ const url = new URL(`${scheme}://${host}${port ? `:${port}` : ""}${path11}`);
14852
+ return { ok: true, url };
14853
+ } catch {
14854
+ return { ok: false, error: "Invalid forwarded URL" };
14855
+ }
14856
+ }
14857
+ async function requestBodyBytes(request) {
14858
+ if (request.method === "GET" || request.method === "HEAD" || request.body === null) {
14859
+ return void 0;
14860
+ }
14861
+ return await request.arrayBuffer();
14862
+ }
14863
+ function requestHeaders(request, lease, upstreamHost) {
14864
+ const headers = new Headers();
14865
+ request.headers.forEach((value, key) => {
14866
+ const normalized = key.toLowerCase();
14867
+ if (HOP_BY_HOP_HEADERS.has(normalized) || PROXY_ONLY_HEADERS.has(normalized)) {
14868
+ return;
14869
+ }
14870
+ headers.append(key, value);
14871
+ });
14872
+ for (const transform of lease.headerTransforms) {
14873
+ if (!matchesSandboxEgressDomain(upstreamHost, transform.domain)) {
14874
+ continue;
14875
+ }
14876
+ for (const [key, value] of Object.entries(transform.headers)) {
14877
+ headers.set(key, value);
14878
+ }
14879
+ }
14880
+ return headers;
14881
+ }
14882
+ function responseHeaders(upstream) {
14883
+ const headers = new Headers();
14884
+ upstream.headers.forEach((value, key) => {
14885
+ const normalized = key.toLowerCase();
14886
+ if (!HOP_BY_HOP_HEADERS.has(normalized)) {
14887
+ headers.append(key, value);
14888
+ }
14889
+ });
14890
+ return headers;
14891
+ }
14892
+ async function credentialLease(sandboxId, provider, session) {
14893
+ const cached = await getSandboxEgressCredentialLease(
14894
+ sandboxId,
14895
+ provider,
14896
+ session
14897
+ );
14898
+ if (cached) {
14899
+ return cached;
14900
+ }
14901
+ const lease = await issueProviderCredentialLease({
14902
+ provider,
14903
+ requesterId: session.requesterId,
14904
+ reason: `sandbox-egress:${provider}`
14905
+ });
14906
+ const headerTransforms = lease.headerTransforms ?? [];
14907
+ if (headerTransforms.length === 0) {
14908
+ throw new Error(
14909
+ `Credential lease for ${provider} did not include header transforms`
14910
+ );
14911
+ }
14912
+ const cachedLease = {
14913
+ provider,
14914
+ expiresAt: lease.expiresAt,
14915
+ headerTransforms
14916
+ };
14917
+ await setSandboxEgressCredentialLease(sandboxId, session, cachedLease);
14918
+ return cachedLease;
14919
+ }
14920
+ function hasTransformForHost(lease, host) {
14921
+ return lease.headerTransforms.some(
14922
+ (transform) => matchesSandboxEgressDomain(host, transform.domain)
14923
+ );
14924
+ }
14925
+ async function proxySandboxEgressRequest(request, sandboxId, deps = {}) {
14926
+ const oidcToken = request.headers.get(OIDC_TOKEN_HEADER)?.trim();
14927
+ if (!oidcToken) {
14928
+ return jsonError("Missing Vercel Sandbox OIDC token", 401);
14929
+ }
14930
+ try {
14931
+ await (deps.verifyOidc ?? verifyVercelSandboxOidcToken)(
14932
+ oidcToken,
14933
+ sandboxId
14934
+ );
14935
+ } catch (error) {
14936
+ logWarn(
14937
+ "sandbox_egress_oidc_verification_failed",
14938
+ {},
14939
+ {
14940
+ "app.sandbox.oidc_error": error instanceof Error ? error.message : String(error)
14941
+ },
14942
+ "Sandbox egress OIDC verification failed"
14943
+ );
14944
+ return jsonError("Invalid Vercel Sandbox OIDC token", 401);
14945
+ }
14946
+ const upstreamResult = buildUpstreamUrl(request, sandboxId);
14947
+ if (!upstreamResult.ok) {
14948
+ return jsonError(upstreamResult.error, 400);
14949
+ }
14950
+ const upstreamUrl = upstreamResult.url;
14951
+ const provider = resolveSandboxEgressProviderForHost(upstreamUrl.hostname);
14952
+ if (!provider) {
14953
+ return jsonError("No provider owns forwarded host", 403);
14954
+ }
14955
+ const session = await getSandboxEgressSession(sandboxId);
14956
+ if (!session) {
14957
+ return jsonError("Sandbox egress session is not authorized", 403);
14958
+ }
14959
+ let lease;
14960
+ try {
14961
+ lease = await credentialLease(sandboxId, provider, session);
14962
+ } catch (error) {
14963
+ if (error instanceof CredentialUnavailableError) {
14964
+ return new Response(
14965
+ `junior-auth-required provider=${error.provider} 401 unauthorized
14966
+ ${error.message}`,
14967
+ {
14968
+ status: 401,
14969
+ headers: { "content-type": "text/plain; charset=utf-8" }
14970
+ }
14971
+ );
14972
+ }
14973
+ throw error;
14974
+ }
14975
+ if (!hasTransformForHost(lease, upstreamUrl.hostname)) {
14976
+ return jsonError("Credential lease does not cover forwarded host", 403);
14977
+ }
14978
+ const body = await requestBodyBytes(request);
14979
+ const upstream = await (deps.fetch ?? fetch)(upstreamUrl, {
14980
+ method: request.method,
14981
+ headers: requestHeaders(request, lease, upstreamUrl.hostname),
14982
+ ...body ? { body } : {},
14983
+ redirect: "manual"
14984
+ });
14985
+ if (AUTH_REJECTION_STATUS.has(upstream.status)) {
14986
+ await clearSandboxEgressCredentialLease(sandboxId, provider, session);
14987
+ }
14988
+ return new Response(upstream.body, {
14989
+ status: upstream.status,
14990
+ statusText: upstream.statusText,
14991
+ headers: responseHeaders(upstream)
14992
+ });
14993
+ }
14994
+
14995
+ // src/handlers/sandbox-egress-proxy.ts
14996
+ async function ALL(request, sandboxId) {
14997
+ return await proxySandboxEgressRequest(request, sandboxId);
14998
+ }
14999
+
14371
15000
  // src/chat/slack/context.ts
14372
15001
  function toTrimmedSlackString(value) {
14373
15002
  const normalized = toOptionalString(value);
@@ -17744,6 +18373,12 @@ async function createApp(options) {
17744
18373
  app.post("/api/internal/turn-resume", (c) => {
17745
18374
  return POST(c.req.raw, waitUntil);
17746
18375
  });
18376
+ app.all("/api/internal/sandbox-egress/:sandboxId", (c) => {
18377
+ return ALL(c.req.raw, c.req.param("sandboxId"));
18378
+ });
18379
+ app.all("/api/internal/sandbox-egress/:sandboxId/*", (c) => {
18380
+ return ALL(c.req.raw, c.req.param("sandboxId"));
18381
+ });
17747
18382
  app.post("/api/webhooks/:platform", (c) => {
17748
18383
  return POST2(c.req.raw, c.req.param("platform"), waitUntil);
17749
18384
  });