@sentry/junior 0.42.0 → 0.44.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-QAMTCT2R.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,175 @@ 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(egressId) {
8626
+ const baseUrl = resolveBaseUrl();
8627
+ if (!baseUrl) {
8628
+ return void 0;
8629
+ }
8630
+ const url = new URL(
8631
+ `${SANDBOX_EGRESS_PROXY_PATH}/${encodeURIComponent(egressId)}`,
8632
+ baseUrl
8633
+ );
8634
+ return url.toString();
8635
+ }
8636
+ function buildSandboxEgressNetworkPolicy(egressId) {
8637
+ const entries = providerEntries();
8638
+ if (entries.length === 0) {
8639
+ return void 0;
8640
+ }
8641
+ const forwardURL = proxyUrl(egressId);
8642
+ if (!forwardURL) {
8643
+ throw new Error(
8644
+ "Cannot determine base URL for sandbox credential egress (set JUNIOR_BASE_URL or deploy to Vercel)"
8645
+ );
8646
+ }
8647
+ const allow = {
8648
+ "*": []
8649
+ };
8650
+ for (const entry of entries) {
8651
+ for (const domain of entry.domains) {
8652
+ allow[domain] = [{ forwardURL }];
8653
+ }
8654
+ }
8655
+ return { allow };
8656
+ }
8657
+ async function resolveSandboxCommandEnvironment() {
8658
+ const env = {};
8659
+ for (const plugin of getPluginProviders().sort(
8660
+ (left, right) => left.manifest.name.localeCompare(right.manifest.name)
8661
+ )) {
8662
+ Object.assign(env, resolvePluginCommandEnv(plugin.manifest));
8663
+ const credentials = plugin.manifest.credentials;
8664
+ if (credentials) {
8665
+ env[credentials.authTokenEnv] = resolveAuthTokenPlaceholder(credentials);
8666
+ }
8667
+ }
8668
+ return env;
8669
+ }
8670
+
8671
+ // src/chat/sandbox/egress-session.ts
8672
+ import { randomUUID as randomUUID3 } from "crypto";
8673
+ var SANDBOX_EGRESS_SESSION_PREFIX = "sandbox-egress-session";
8674
+ var SANDBOX_EGRESS_LEASE_PREFIX = "sandbox-egress-lease";
8675
+ var DEFAULT_SESSION_TTL_MS = 30 * 60 * 1e3;
8676
+ function sessionKey2(egressId) {
8677
+ return `${SANDBOX_EGRESS_SESSION_PREFIX}:${egressId}`;
8678
+ }
8679
+ function leaseKey(egressId, provider, session) {
8680
+ return `${SANDBOX_EGRESS_LEASE_PREFIX}:${egressId}:${provider}:${session.requesterId}:${session.activationId}`;
8681
+ }
8682
+ function parseSession(value) {
8683
+ if (!value || typeof value !== "object") {
8684
+ return void 0;
8685
+ }
8686
+ const record = value;
8687
+ if (typeof record.requesterId !== "string" || typeof record.expiresAtMs !== "number" || !Number.isFinite(record.expiresAtMs) || typeof record.activationId !== "string" || !record.activationId) {
8688
+ return void 0;
8689
+ }
8690
+ if (record.expiresAtMs <= Date.now()) {
8691
+ return void 0;
8692
+ }
8693
+ return {
8694
+ requesterId: record.requesterId,
8695
+ expiresAtMs: record.expiresAtMs,
8696
+ activationId: record.activationId
8697
+ };
8698
+ }
8699
+ function parseLease(value) {
8700
+ if (!value || typeof value !== "object") {
8701
+ return void 0;
8702
+ }
8703
+ const record = value;
8704
+ if (typeof record.provider !== "string" || typeof record.expiresAt !== "string" || !Array.isArray(record.headerTransforms)) {
8705
+ return void 0;
8706
+ }
8707
+ const expiresAtMs = Date.parse(record.expiresAt);
8708
+ if (!Number.isFinite(expiresAtMs) || expiresAtMs <= Date.now()) {
8709
+ return void 0;
8710
+ }
8711
+ const headerTransforms = record.headerTransforms.filter(
8712
+ (transform) => Boolean(
8713
+ transform && typeof transform.domain === "string" && transform.headers && typeof transform.headers === "object"
8714
+ )
8715
+ );
8716
+ if (headerTransforms.length === 0) {
8717
+ return void 0;
8718
+ }
8719
+ return {
8720
+ provider: record.provider,
8721
+ expiresAt: record.expiresAt,
8722
+ headerTransforms
8723
+ };
8724
+ }
8725
+ async function upsertSandboxEgressSession(input) {
8726
+ const state = getStateAdapter();
8727
+ await state.connect();
8728
+ const ttlMs = Math.max(1, input.ttlMs ?? DEFAULT_SESSION_TTL_MS);
8729
+ const now = Date.now();
8730
+ const session = {
8731
+ requesterId: input.requesterId,
8732
+ expiresAtMs: now + ttlMs,
8733
+ activationId: randomUUID3()
8734
+ };
8735
+ await state.set(sessionKey2(input.egressId), session, ttlMs);
8736
+ }
8737
+ async function clearSandboxEgressSession(egressId) {
8738
+ const state = getStateAdapter();
8739
+ await state.connect();
8740
+ await state.delete(sessionKey2(egressId));
8741
+ }
8742
+ async function getSandboxEgressSession(egressId) {
8743
+ const state = getStateAdapter();
8744
+ await state.connect();
8745
+ return parseSession(await state.get(sessionKey2(egressId)));
8746
+ }
8747
+ async function setSandboxEgressCredentialLease(egressId, session, lease) {
8748
+ const leaseExpiresAtMs = Date.parse(lease.expiresAt);
8749
+ if (!Number.isFinite(leaseExpiresAtMs) || leaseExpiresAtMs <= Date.now()) {
8750
+ return;
8751
+ }
8752
+ const ttlMs = Math.max(
8753
+ 1,
8754
+ Math.min(leaseExpiresAtMs, session.expiresAtMs) - Date.now()
8755
+ );
8756
+ const state = getStateAdapter();
8757
+ await state.connect();
8758
+ await state.set(leaseKey(egressId, lease.provider, session), lease, ttlMs);
8759
+ }
8760
+ async function getSandboxEgressCredentialLease(egressId, provider, session) {
8761
+ const state = getStateAdapter();
8762
+ await state.connect();
8763
+ return parseLease(await state.get(leaseKey(egressId, provider, session)));
8764
+ }
8765
+ async function clearSandboxEgressCredentialLease(egressId, provider, session) {
8766
+ const state = getStateAdapter();
8767
+ await state.connect();
8768
+ await state.delete(leaseKey(egressId, provider, session));
8769
+ }
8770
+
8466
8771
  // src/chat/sandbox/http-error-details.ts
8467
8772
  var DEFAULT_PREVIEW_LIMIT = 512;
8468
8773
  function toTrimmedString(value, maxChars) {
@@ -8665,6 +8970,7 @@ function throwSandboxOperationError(action, error, includeMissingPath = false) {
8665
8970
  }
8666
8971
 
8667
8972
  // src/chat/sandbox/session.ts
8973
+ import { randomUUID as randomUUID4 } from "crypto";
8668
8974
  import { Sandbox } from "@vercel/sandbox";
8669
8975
  import { createBashTool as createBashTool2 } from "bash-tool";
8670
8976
 
@@ -9238,27 +9544,39 @@ var SANDBOX_RUNTIME = "node22";
9238
9544
  var SANDBOX_RUNTIME_BIN_DIR = `${SANDBOX_WORKSPACE_ROOT}/.junior/bin`;
9239
9545
  var SNAPSHOT_BOOT_RETRY_COUNT = 3;
9240
9546
  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
- }
9547
+ var SANDBOX_NAME_PREFIX = "junior-";
9548
+ function createBashToolSandboxAdapter(sandbox) {
9259
9549
  return {
9260
- ...basePolicy,
9261
- allow: existingAllow
9550
+ async executeCommand(command) {
9551
+ const result = await sandbox.runCommand({
9552
+ cmd: "bash",
9553
+ args: ["-c", command]
9554
+ });
9555
+ const [stdout, stderr] = await Promise.all([
9556
+ result.stdout(),
9557
+ result.stderr()
9558
+ ]);
9559
+ return {
9560
+ stdout,
9561
+ stderr,
9562
+ exitCode: result.exitCode
9563
+ };
9564
+ },
9565
+ async readFile(filePath) {
9566
+ const content = await sandbox.readFileToBuffer({ path: filePath });
9567
+ if (content == null) {
9568
+ throw new Error(`File not found: ${filePath}`);
9569
+ }
9570
+ return content.toString("utf8");
9571
+ },
9572
+ async writeFiles(files) {
9573
+ await sandbox.writeFiles(
9574
+ files.map((file) => ({
9575
+ path: file.path,
9576
+ content: file.content
9577
+ }))
9578
+ );
9579
+ }
9262
9580
  };
9263
9581
  }
9264
9582
  function truncateOutput(output, maxLength) {
@@ -9291,23 +9609,31 @@ function createSandboxSessionManager(options) {
9291
9609
  let availableSkills = [];
9292
9610
  let availableReferenceFiles = [];
9293
9611
  let toolExecutors;
9612
+ let appliedNetworkPolicyKey;
9294
9613
  const timeoutMs = options?.timeoutMs ?? 1e3 * 60 * 30;
9295
9614
  const traceContext = options?.traceContext ?? {};
9296
9615
  const dependencyProfileHash = getRuntimeDependencyProfileHash(SANDBOX_RUNTIME);
9616
+ const resolveCommandEnv = options?.commandEnv ?? (async () => ({}));
9297
9617
  const withSandboxSpan = (name, op, attributes, callback) => withSpan(name, op, traceContext, callback, attributes);
9298
9618
  const clearSession = () => {
9299
9619
  sandbox = null;
9300
9620
  sandboxIdHint = void 0;
9301
9621
  toolExecutors = void 0;
9622
+ appliedNetworkPolicyKey = void 0;
9623
+ };
9624
+ const createSandboxName = () => `${SANDBOX_NAME_PREFIX}${randomUUID4()}`;
9625
+ const preflightNetworkPolicy = (sandboxName) => {
9626
+ return options?.createNetworkPolicy?.(sandboxName);
9302
9627
  };
9303
9628
  const rememberSandbox = async (nextSandbox) => {
9304
9629
  sandbox = nextSandbox;
9305
9630
  sandboxIdHint = nextSandbox.sandboxId;
9306
9631
  toolExecutors = void 0;
9307
- await options?.onSandboxAcquired?.({
9308
- sandboxId: nextSandbox.sandboxId,
9632
+ const acquired = {
9633
+ sandboxId: sandboxIdHint,
9309
9634
  ...dependencyProfileHash ? { sandboxDependencyProfileHash: dependencyProfileHash } : {}
9310
- });
9635
+ };
9636
+ await options?.onSandboxAcquired?.(acquired);
9311
9637
  return nextSandbox;
9312
9638
  };
9313
9639
  const failSetup = (error) => {
@@ -9322,6 +9648,30 @@ function createSandboxSessionManager(options) {
9322
9648
  runtimeBinDir: SANDBOX_RUNTIME_BIN_DIR
9323
9649
  });
9324
9650
  };
9651
+ const refreshNetworkPolicy = async (targetSandbox) => {
9652
+ const networkPolicy = options?.createNetworkPolicy?.(
9653
+ targetSandbox.sandboxEgressId
9654
+ );
9655
+ if (!networkPolicy) {
9656
+ return;
9657
+ }
9658
+ const networkPolicyKey = JSON.stringify(networkPolicy);
9659
+ if (appliedNetworkPolicyKey === networkPolicyKey) {
9660
+ return;
9661
+ }
9662
+ await withSandboxSpan(
9663
+ "sandbox.network_policy.update",
9664
+ "sandbox.update",
9665
+ {
9666
+ "app.sandbox.reused": true,
9667
+ "app.sandbox.source": "id_hint"
9668
+ },
9669
+ async () => {
9670
+ await targetSandbox.update({ networkPolicy });
9671
+ }
9672
+ );
9673
+ appliedNetworkPolicyKey = networkPolicyKey;
9674
+ };
9325
9675
  const ensureSandboxReachable = async (targetSandbox, source) => {
9326
9676
  await withSandboxSpan(
9327
9677
  "sandbox.reuse_probe",
@@ -9341,23 +9691,6 @@ function createSandboxSessionManager(options) {
9341
9691
  }
9342
9692
  );
9343
9693
  };
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
9694
  const recreateUnavailableSandbox = async (source) => {
9362
9695
  setSpanAttributes({
9363
9696
  "app.sandbox.recovery.attempted": true,
@@ -9370,17 +9703,22 @@ function createSandboxSessionManager(options) {
9370
9703
  });
9371
9704
  return replacement;
9372
9705
  };
9373
- const createSandboxFromSnapshot = async (snapshotId, sandboxCredentials) => {
9706
+ const createSandboxFromSnapshot = async (snapshotId, sandboxCredentials, initialSandboxName) => {
9374
9707
  for (let attempt = 0; attempt < SNAPSHOT_BOOT_RETRY_COUNT; attempt += 1) {
9708
+ const sandboxName = attempt === 0 ? initialSandboxName : createSandboxName();
9709
+ const networkPolicy = preflightNetworkPolicy(sandboxName);
9375
9710
  try {
9376
- return await Sandbox.create({
9377
- timeout: timeoutMs,
9378
- source: {
9379
- type: "snapshot",
9380
- snapshotId
9381
- },
9382
- ...sandboxCredentials ?? {}
9383
- });
9711
+ return createSandboxInstance(
9712
+ await Sandbox.create({
9713
+ timeout: timeoutMs,
9714
+ ...networkPolicy ? { name: sandboxName, persistent: false, networkPolicy } : {},
9715
+ source: {
9716
+ type: "snapshot",
9717
+ snapshotId
9718
+ },
9719
+ ...sandboxCredentials ?? {}
9720
+ })
9721
+ );
9384
9722
  } catch (error) {
9385
9723
  if (!isSnapshottingError(error) || attempt === SNAPSHOT_BOOT_RETRY_COUNT - 1) {
9386
9724
  throw error;
@@ -9405,18 +9743,23 @@ function createSandboxSessionManager(options) {
9405
9743
  });
9406
9744
  };
9407
9745
  const createSandboxFromResolvedSnapshot = async (params) => {
9408
- const { runtime, snapshot, sandboxCredentials } = params;
9746
+ const { runtime, snapshot, sandboxCredentials, sandboxName } = params;
9409
9747
  if (!snapshot.snapshotId) {
9410
- return await Sandbox.create({
9411
- timeout: timeoutMs,
9412
- runtime,
9413
- ...sandboxCredentials ?? {}
9414
- });
9748
+ const networkPolicy = preflightNetworkPolicy(sandboxName);
9749
+ return createSandboxInstance(
9750
+ await Sandbox.create({
9751
+ timeout: timeoutMs,
9752
+ runtime,
9753
+ ...networkPolicy ? { name: sandboxName, persistent: false, networkPolicy } : {},
9754
+ ...sandboxCredentials ?? {}
9755
+ })
9756
+ );
9415
9757
  }
9416
9758
  try {
9417
9759
  return await createSandboxFromSnapshot(
9418
9760
  snapshot.snapshotId,
9419
- sandboxCredentials
9761
+ sandboxCredentials,
9762
+ sandboxName
9420
9763
  );
9421
9764
  } catch (error) {
9422
9765
  if (!isSnapshotMissingError(error)) {
@@ -9436,13 +9779,15 @@ function createSandboxSessionManager(options) {
9436
9779
  }
9437
9780
  return await createSandboxFromSnapshot(
9438
9781
  rebuiltSnapshot.snapshotId,
9439
- sandboxCredentials
9782
+ sandboxCredentials,
9783
+ sandboxName
9440
9784
  );
9441
9785
  }
9442
9786
  };
9443
9787
  const createFreshSandbox = async () => {
9444
9788
  const runtime = SANDBOX_RUNTIME;
9445
9789
  const sandboxCredentials = getVercelSandboxCredentials();
9790
+ const sandboxName = createSandboxName();
9446
9791
  let createdSandbox;
9447
9792
  try {
9448
9793
  createdSandbox = await withSandboxSpan(
@@ -9462,7 +9807,8 @@ function createSandboxSessionManager(options) {
9462
9807
  return await createSandboxFromResolvedSnapshot({
9463
9808
  runtime,
9464
9809
  snapshot,
9465
- sandboxCredentials
9810
+ sandboxCredentials,
9811
+ sandboxName
9466
9812
  });
9467
9813
  }
9468
9814
  );
@@ -9470,6 +9816,7 @@ function createSandboxSessionManager(options) {
9470
9816
  return failSetup(error);
9471
9817
  }
9472
9818
  try {
9819
+ await refreshNetworkPolicy(createdSandbox);
9473
9820
  await syncSkills(createdSandbox);
9474
9821
  } catch (error) {
9475
9822
  return failSetup(error);
@@ -9497,6 +9844,7 @@ function createSandboxSessionManager(options) {
9497
9844
  }
9498
9845
  try {
9499
9846
  await ensureSandboxReachable(cachedSandbox, "memory");
9847
+ await refreshNetworkPolicy(cachedSandbox);
9500
9848
  return cachedSandbox;
9501
9849
  } catch (error) {
9502
9850
  if (isSandboxUnavailableError(error)) {
@@ -9519,15 +9867,19 @@ function createSandboxSessionManager(options) {
9519
9867
  "app.sandbox.reused": true,
9520
9868
  "app.sandbox.source": "id_hint"
9521
9869
  },
9522
- async () => await Sandbox.get({
9523
- sandboxId: sandboxIdHint,
9524
- ...sandboxCredentials ?? {}
9525
- })
9870
+ async () => createSandboxInstance(
9871
+ await Sandbox.get({
9872
+ name: sandboxIdHint,
9873
+ resume: true,
9874
+ ...sandboxCredentials ?? {}
9875
+ })
9876
+ )
9526
9877
  );
9527
9878
  } catch {
9528
9879
  return null;
9529
9880
  }
9530
9881
  try {
9882
+ await refreshNetworkPolicy(hintedSandbox);
9531
9883
  await syncSkills(hintedSandbox);
9532
9884
  return await rememberSandbox(hintedSandbox);
9533
9885
  } catch (error) {
@@ -9582,37 +9934,6 @@ function createSandboxSessionManager(options) {
9582
9934
  stderrTruncated: stderr.truncated
9583
9935
  };
9584
9936
  };
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
9937
  const extendKeepAlive = async (activeSandbox) => {
9617
9938
  const keepAliveMs = parseKeepAliveMs();
9618
9939
  if (keepAliveMs === 0) {
@@ -9633,6 +9954,7 @@ function createSandboxSessionManager(options) {
9633
9954
  }
9634
9955
  };
9635
9956
  const buildToolExecutors = async (sandboxInstance) => {
9957
+ const activeSandboxId = sandboxInstance.sandboxId;
9636
9958
  const toolkit = await withSandboxSpan(
9637
9959
  "sandbox.bash_tool.init",
9638
9960
  "sandbox.tool.init",
@@ -9641,7 +9963,7 @@ function createSandboxSessionManager(options) {
9641
9963
  "app.sandbox.destination": SANDBOX_WORKSPACE_ROOT
9642
9964
  },
9643
9965
  async () => await createBashTool2({
9644
- sandbox: sandboxInstance,
9966
+ sandbox: createBashToolSandboxAdapter(sandboxInstance),
9645
9967
  destination: SANDBOX_WORKSPACE_ROOT
9646
9968
  })
9647
9969
  );
@@ -9652,47 +9974,70 @@ function createSandboxSessionManager(options) {
9652
9974
  }
9653
9975
  return {
9654
9976
  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;
9977
+ const commandEgressId = sandboxInstance.sandboxEgressId;
9978
+ await options?.beforeCommand?.(commandEgressId);
9660
9979
  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
- }
9980
+ let timeoutId;
9981
+ let commandFinished = false;
9982
+ const finishCommand = async () => {
9983
+ if (commandFinished) {
9984
+ return;
9694
9985
  }
9695
- );
9986
+ commandFinished = true;
9987
+ await options?.afterCommand?.(commandEgressId);
9988
+ };
9989
+ const finishCommandBestEffort = async () => {
9990
+ try {
9991
+ await finishCommand();
9992
+ } catch (error) {
9993
+ logWarn(
9994
+ "sandbox_command_cleanup_failed",
9995
+ traceContext,
9996
+ {
9997
+ "app.sandbox.id": activeSandboxId,
9998
+ "error.type": error instanceof Error ? error.name : "sandbox_command_cleanup_error"
9999
+ },
10000
+ "Sandbox command cleanup failed"
10001
+ );
10002
+ }
10003
+ };
10004
+ try {
10005
+ const sandboxCommandEnv = await resolveCommandEnv();
10006
+ const script = buildNonInteractiveShellScript(input.command, {
10007
+ env: { ...sandboxCommandEnv, ...input.env ?? {} },
10008
+ pathPrefix: `${SANDBOX_RUNTIME_BIN_DIR}:$PATH`
10009
+ });
10010
+ const controller = input.timeoutMs && input.timeoutMs > 0 ? new AbortController() : void 0;
10011
+ timeoutId = controller ? setTimeout(() => {
10012
+ timedOut = true;
10013
+ controller.abort();
10014
+ }, input.timeoutMs) : void 0;
10015
+ const commandResult2 = await sandboxInstance.runCommand({
10016
+ cmd: "bash",
10017
+ args: ["-c", script],
10018
+ cwd: SANDBOX_WORKSPACE_ROOT,
10019
+ ...controller ? { signal: controller.signal } : {}
10020
+ });
10021
+ await finishCommandBestEffort();
10022
+ return await readCommandOutput(commandResult2);
10023
+ } catch (error) {
10024
+ if (timedOut) {
10025
+ return {
10026
+ stdout: "",
10027
+ stderr: `Command timed out after ${input.timeoutMs}ms`,
10028
+ exitCode: 124,
10029
+ stdoutTruncated: false,
10030
+ stderrTruncated: false,
10031
+ timedOut: true
10032
+ };
10033
+ }
10034
+ throw error;
10035
+ } finally {
10036
+ if (timeoutId) {
10037
+ clearTimeout(timeoutId);
10038
+ }
10039
+ await finishCommandBestEffort();
10040
+ }
9696
10041
  },
9697
10042
  readFile: async (input) => await executeReadFile(input, {
9698
10043
  toolCallId: "sandbox-read-file",
@@ -9725,7 +10070,7 @@ function createSandboxSessionManager(options) {
9725
10070
  availableReferenceFiles = [...files];
9726
10071
  },
9727
10072
  getSandboxId() {
9728
- return sandbox?.sandboxId ?? sandboxIdHint;
10073
+ return sandbox ? sandbox.sandboxId : sandboxIdHint;
9729
10074
  },
9730
10075
  getDependencyProfileHash() {
9731
10076
  return dependencyProfileHash;
@@ -9748,7 +10093,7 @@ function createSandboxSessionManager(options) {
9748
10093
  "app.sandbox.stop.blocking": true
9749
10094
  },
9750
10095
  async () => {
9751
- await activeSandbox.stop({ blocking: true });
10096
+ await activeSandbox.stop();
9752
10097
  }
9753
10098
  );
9754
10099
  sandbox = null;
@@ -9767,21 +10112,6 @@ var SANDBOX_TOOL_NAMES = /* @__PURE__ */ new Set([
9767
10112
  "listDir",
9768
10113
  "writeFile"
9769
10114
  ]);
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
10115
  function parseEnv(raw) {
9786
10116
  if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
9787
10117
  return void 0;
@@ -9790,27 +10120,33 @@ function parseEnv(raw) {
9790
10120
  Object.entries(raw).filter(([, value]) => typeof value === "string").map(([key, value]) => [key, value])
9791
10121
  );
9792
10122
  }
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
10123
  function createSandboxExecutor(options) {
9805
10124
  let availableSkills = [];
9806
10125
  let referenceFiles = [];
9807
10126
  const traceContext = options?.traceContext ?? {};
10127
+ const credentialEgress = options?.credentialEgress;
10128
+ const syncSandboxEgressSession = credentialEgress ? async (egressId) => {
10129
+ await upsertSandboxEgressSession({
10130
+ egressId,
10131
+ requesterId: credentialEgress.requesterId,
10132
+ ttlMs: options?.timeoutMs
10133
+ });
10134
+ } : void 0;
10135
+ const clearSandboxEgressSessionForCommand = credentialEgress ? async (egressId) => {
10136
+ await clearSandboxEgressSession(egressId);
10137
+ } : void 0;
9808
10138
  const sessionManager = createSandboxSessionManager({
9809
10139
  sandboxId: options?.sandboxId,
9810
10140
  sandboxDependencyProfileHash: options?.sandboxDependencyProfileHash,
9811
10141
  timeoutMs: options?.timeoutMs,
9812
10142
  traceContext,
9813
- onSandboxAcquired: options?.onSandboxAcquired
10143
+ commandEnv: credentialEgress ? async () => await resolveSandboxCommandEnvironment() : void 0,
10144
+ createNetworkPolicy: credentialEgress ? buildSandboxEgressNetworkPolicy : void 0,
10145
+ beforeCommand: syncSandboxEgressSession,
10146
+ afterCommand: clearSandboxEgressSessionForCommand,
10147
+ onSandboxAcquired: async (sandbox) => {
10148
+ await options?.onSandboxAcquired?.(sandbox);
10149
+ }
9814
10150
  });
9815
10151
  const withSandboxSpan = (name, op, attributes, callback) => withSpan(name, op, traceContext, callback, attributes);
9816
10152
  const logSandboxBootRequest = (trigger, details = {}) => {
@@ -9828,7 +10164,6 @@ function createSandboxExecutor(options) {
9828
10164
  );
9829
10165
  };
9830
10166
  const executeBashTool = async (rawInput, command) => {
9831
- const headerTransforms = parseHeaderTransforms(rawInput.headerTransforms);
9832
10167
  const env = parseEnv(rawInput.env);
9833
10168
  const timeoutMs = positiveInteger(rawInput.timeoutMs);
9834
10169
  logSandboxBootRequest("tool.bash", {
@@ -9845,7 +10180,6 @@ function createSandboxExecutor(options) {
9845
10180
  try {
9846
10181
  const response = await executeBash({
9847
10182
  command,
9848
- ...headerTransforms ? { headerTransforms } : {},
9849
10183
  ...env ? { env } : {},
9850
10184
  ...timeoutMs ? { timeoutMs } : {}
9851
10185
  });
@@ -10149,7 +10483,7 @@ function createSandboxExecutor(options) {
10149
10483
  return SANDBOX_TOOL_NAMES.has(toolName);
10150
10484
  },
10151
10485
  async createSandbox() {
10152
- return createSandboxWorkspace(await sessionManager.createSandbox());
10486
+ return await sessionManager.createSandbox();
10153
10487
  },
10154
10488
  execute,
10155
10489
  async dispose() {
@@ -10250,34 +10584,6 @@ function buildSandboxInput(toolName, params) {
10250
10584
  return params;
10251
10585
  }
10252
10586
 
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
10587
  // src/chat/tools/execution/normalize-result.ts
10282
10588
  function isStructuredToolExecutionResult(value) {
10283
10589
  const content = value?.content;
@@ -10367,7 +10673,7 @@ function handleToolExecutionError(error, toolName, toolCallId, shouldTrace, trac
10367
10673
  }
10368
10674
 
10369
10675
  // src/chat/tools/agent-tools.ts
10370
- function createAgentTools(tools, sandbox, spanContext, onStatus, sandboxExecutor, capabilityRuntime, pluginAuthOrchestration, onToolCall) {
10676
+ function createAgentTools(tools, sandbox, spanContext, onStatus, sandboxExecutor, pluginAuthOrchestration, onToolCall) {
10371
10677
  const shouldTrace = shouldEmitDevAgentTrace();
10372
10678
  return Object.entries(tools).map(([toolName, toolDef]) => ({
10373
10679
  name: toolName,
@@ -10407,21 +10713,11 @@ function createAgentTools(tools, sandbox, spanContext, onStatus, sandboxExecutor
10407
10713
  };
10408
10714
  }
10409
10715
  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
10716
  const sandboxInput = buildSandboxInput(toolName, parsed);
10417
10717
  const isSandbox = Boolean(sandboxExecutor?.canExecute(toolName));
10418
10718
  const result = isSandbox ? await sandboxExecutor.execute({
10419
10719
  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
10720
+ input: sandboxInput
10425
10721
  }) : await toolDef.execute(parsed, {
10426
10722
  experimental_context: sandbox
10427
10723
  });
@@ -11284,6 +11580,7 @@ ${typeof result.stderr === "string" ? result.stderr : ""}`.toLowerCase();
11284
11580
  return false;
11285
11581
  }
11286
11582
  return [
11583
+ /\bjunior-auth-required\b/,
11287
11584
  /\b401\b/,
11288
11585
  /\bunauthorized\b/,
11289
11586
  /\bbad credentials\b/,
@@ -11296,6 +11593,33 @@ ${typeof result.stderr === "string" ? result.stderr : ""}`.toLowerCase();
11296
11593
  /\breauthoriz/
11297
11594
  ].some((pattern) => pattern.test(text));
11298
11595
  }
11596
+ function commandText(details) {
11597
+ if (!details || typeof details !== "object") {
11598
+ return "";
11599
+ }
11600
+ const result = details;
11601
+ return `${typeof result.stdout === "string" ? result.stdout : ""}
11602
+ ${typeof result.stderr === "string" ? result.stderr : ""}`;
11603
+ }
11604
+ function explicitAuthRequiredProvider(details) {
11605
+ const match = /\bjunior-auth-required\s+provider=([a-z0-9-]+)\b/.exec(
11606
+ commandText(details).toLowerCase()
11607
+ );
11608
+ return match?.[1];
11609
+ }
11610
+ function registeredProviderNames() {
11611
+ const providers = /* @__PURE__ */ new Set();
11612
+ for (const plugin of getPluginProviders()) {
11613
+ const domains = [
11614
+ ...plugin.manifest.credentials?.domains ?? [],
11615
+ ...plugin.manifest.domains ?? []
11616
+ ];
11617
+ if (domains.length > 0) {
11618
+ providers.add(plugin.manifest.name);
11619
+ }
11620
+ }
11621
+ return [...providers].sort((left, right) => left.localeCompare(right));
11622
+ }
11299
11623
  function commandTargetsProvider(provider, command, details) {
11300
11624
  const normalizedCommand = command.trim().toLowerCase();
11301
11625
  if (!normalizedCommand) {
@@ -11310,16 +11634,15 @@ function commandTargetsProvider(provider, command, details) {
11310
11634
  const credentials = manifest?.credentials;
11311
11635
  if (credentials) {
11312
11636
  candidates.add(credentials.authTokenEnv.toLowerCase());
11313
- for (const domain of credentials.apiDomains) {
11637
+ for (const domain of credentials.domains) {
11314
11638
  candidates.add(domain.toLowerCase());
11315
11639
  }
11316
11640
  }
11317
- for (const domain of manifest?.apiDomains ?? []) {
11641
+ for (const domain of manifest?.domains ?? []) {
11318
11642
  candidates.add(domain.toLowerCase());
11319
11643
  }
11320
11644
  const combinedText = `${normalizedCommand}
11321
- ${details.stdout?.toLowerCase() ?? ""}
11322
- ${details.stderr?.toLowerCase() ?? ""}`;
11645
+ ${commandText(details).toLowerCase()}`;
11323
11646
  return [...candidates].some((candidate) => combinedText.includes(candidate));
11324
11647
  }
11325
11648
  function createPluginAuthOrchestration(deps, abortAgent) {
@@ -11377,23 +11700,22 @@ function createPluginAuthOrchestration(deps, abortAgent) {
11377
11700
  abortAgent();
11378
11701
  throw pendingPause;
11379
11702
  };
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
11703
  return {
11393
- handleCredentialUnavailable,
11394
11704
  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)) {
11705
+ const providers = registeredProviderNames();
11706
+ const authFailure = isCommandAuthFailure(input.details);
11707
+ if (!authFailure) {
11708
+ return;
11709
+ }
11710
+ const explicitProvider = explicitAuthRequiredProvider(input.details);
11711
+ const provider = explicitProvider && providers.includes(explicitProvider) ? explicitProvider : providers.find(
11712
+ (availableProvider) => commandTargetsProvider(
11713
+ availableProvider,
11714
+ input.command,
11715
+ input.details
11716
+ )
11717
+ );
11718
+ if (!provider || !deps.requesterId || !deps.userTokenStore || !getPluginOAuthConfig(provider)) {
11397
11719
  return;
11398
11720
  }
11399
11721
  await startAuthorizationPause(provider, input.activeSkill, {
@@ -11659,14 +11981,15 @@ async function generateAssistantReply(messageText, context = {}) {
11659
11981
  ...context.configuration ?? {},
11660
11982
  ...persistedConfigurationValues
11661
11983
  };
11662
- const capabilityRuntime = createSkillCapabilityRuntime({
11663
- requesterId: context.requester?.userId
11664
- });
11984
+ const requesterId = context.requester?.userId;
11665
11985
  const userTokenStore = createUserTokenStore();
11666
11986
  sandboxExecutor = createSandboxExecutor({
11667
11987
  sandboxId: context.sandbox?.sandboxId,
11668
11988
  sandboxDependencyProfileHash: context.sandbox?.sandboxDependencyProfileHash,
11669
11989
  traceContext: spanContext,
11990
+ credentialEgress: requesterId ? {
11991
+ requesterId
11992
+ } : void 0,
11670
11993
  onSandboxAcquired: async (sandbox2) => {
11671
11994
  lastKnownSandboxId = sandbox2.sandboxId;
11672
11995
  lastKnownSandboxDependencyProfileHash = sandbox2.sandboxDependencyProfileHash;
@@ -11830,25 +12153,6 @@ async function generateAssistantReply(messageText, context = {}) {
11830
12153
  const syncResumeState = () => {
11831
12154
  loadedSkillNamesForResume = activeSkills.map((skill) => skill.name);
11832
12155
  };
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
12156
  setTags({
11853
12157
  conversationId: spanContext.conversationId,
11854
12158
  slackThreadId: context.correlation?.threadId,
@@ -11890,10 +12194,6 @@ async function generateAssistantReply(messageText, context = {}) {
11890
12194
  if (mcpAuth.getPendingPause()) {
11891
12195
  return void 0;
11892
12196
  }
11893
- await enableSkillCredentials(
11894
- effective,
11895
- `skill:${effective.name}:turn:load`
11896
- );
11897
12197
  if (!effective.pluginProvider) {
11898
12198
  return void 0;
11899
12199
  }
@@ -11946,7 +12246,6 @@ async function generateAssistantReply(messageText, context = {}) {
11946
12246
  timeoutResumeMessages = existingCheckpoint?.piMessages ?? [];
11947
12247
  throw mcpAuth.getPendingPause();
11948
12248
  }
11949
- await enableSkillCredentials(skill, `skill:${skill.name}:turn:resume`);
11950
12249
  }
11951
12250
  syncResumeState();
11952
12251
  const activeMcpCatalogs = toActiveMcpCatalogSummaries(
@@ -12007,7 +12306,6 @@ async function generateAssistantReply(messageText, context = {}) {
12007
12306
  spanContext,
12008
12307
  context.onStatus,
12009
12308
  sandboxExecutor,
12010
- capabilityRuntime,
12011
12309
  pluginAuth,
12012
12310
  onToolCall
12013
12311
  );
@@ -12017,7 +12315,6 @@ async function generateAssistantReply(messageText, context = {}) {
12017
12315
  spanContext,
12018
12316
  context.onStatus,
12019
12317
  sandboxExecutor,
12020
- capabilityRuntime,
12021
12318
  pluginAuth,
12022
12319
  onToolCall
12023
12320
  );
@@ -14368,6 +14665,328 @@ async function GET5(request, provider, waitUntil) {
14368
14665
  });
14369
14666
  }
14370
14667
 
14668
+ // src/chat/sandbox/egress-oidc.ts
14669
+ import {
14670
+ createRemoteJWKSet,
14671
+ decodeJwt,
14672
+ jwtVerify
14673
+ } from "jose";
14674
+ var OIDC_DISCOVERY_CACHE_TTL_MS = 60 * 60 * 1e3;
14675
+ var OIDC_DISCOVERY_CACHE_MAX_ENTRIES = 8;
14676
+ var jwksByIssuer = /* @__PURE__ */ new Map();
14677
+ function buildDiscoveryUrl(issuer) {
14678
+ const url = new URL(issuer);
14679
+ if (url.protocol !== "https:" || url.hostname !== "oidc.vercel.com") {
14680
+ throw new Error("Unexpected Vercel OIDC issuer");
14681
+ }
14682
+ url.pathname = `${url.pathname.replace(/\/$/, "")}/.well-known/openid-configuration`;
14683
+ url.search = "";
14684
+ url.hash = "";
14685
+ return url;
14686
+ }
14687
+ function buildJwksUrl(value) {
14688
+ const url = new URL(value);
14689
+ if (url.protocol !== "https:") {
14690
+ throw new Error("Vercel OIDC discovery jwks_uri must use HTTPS");
14691
+ }
14692
+ return url;
14693
+ }
14694
+ async function getJwks(issuer) {
14695
+ const now = Date.now();
14696
+ const cached = jwksByIssuer.get(issuer);
14697
+ if (cached && cached.expiresAtMs > now) {
14698
+ return cached.jwks;
14699
+ }
14700
+ if (cached) {
14701
+ jwksByIssuer.delete(issuer);
14702
+ }
14703
+ const response = await fetch(buildDiscoveryUrl(issuer), {
14704
+ redirect: "error"
14705
+ });
14706
+ if (!response.ok) {
14707
+ throw new Error("Unable to load Vercel OIDC discovery metadata");
14708
+ }
14709
+ const config = await response.json();
14710
+ if (!config.jwks_uri) {
14711
+ throw new Error("Vercel OIDC discovery metadata did not include jwks_uri");
14712
+ }
14713
+ const jwks = createRemoteJWKSet(buildJwksUrl(config.jwks_uri));
14714
+ if (!jwksByIssuer.has(issuer) && jwksByIssuer.size >= OIDC_DISCOVERY_CACHE_MAX_ENTRIES) {
14715
+ const oldestIssuer = jwksByIssuer.keys().next().value;
14716
+ if (oldestIssuer) {
14717
+ jwksByIssuer.delete(oldestIssuer);
14718
+ }
14719
+ }
14720
+ jwksByIssuer.set(issuer, {
14721
+ jwks,
14722
+ expiresAtMs: now + OIDC_DISCOVERY_CACHE_TTL_MS
14723
+ });
14724
+ return jwks;
14725
+ }
14726
+ function validateSandboxClaim(payload, egressId) {
14727
+ if (payload.sandbox_id !== egressId) {
14728
+ throw new Error("Vercel OIDC token belongs to a different sandbox");
14729
+ }
14730
+ }
14731
+ async function verifyVercelSandboxOidcToken(token, egressId) {
14732
+ const unverified = decodeJwt(token);
14733
+ if (typeof unverified.iss !== "string") {
14734
+ throw new Error("Vercel OIDC token did not include an issuer");
14735
+ }
14736
+ const jwks = await getJwks(unverified.iss);
14737
+ const verified = await jwtVerify(token, jwks, {
14738
+ issuer: unverified.iss
14739
+ });
14740
+ validateSandboxClaim(verified.payload, egressId);
14741
+ return verified.payload;
14742
+ }
14743
+
14744
+ // src/chat/sandbox/egress-proxy.ts
14745
+ var OIDC_TOKEN_HEADER = "vercel-sandbox-oidc-token";
14746
+ var FORWARDED_HOST_HEADER = "vercel-forwarded-host";
14747
+ var FORWARDED_SCHEME_HEADER = "vercel-forwarded-scheme";
14748
+ var FORWARDED_PORT_HEADER = "vercel-forwarded-port";
14749
+ var ROUTE_PREFIX = "/api/internal/sandbox-egress";
14750
+ var HOP_BY_HOP_HEADERS = /* @__PURE__ */ new Set([
14751
+ "connection",
14752
+ "host",
14753
+ "keep-alive",
14754
+ "proxy-authenticate",
14755
+ "proxy-authorization",
14756
+ "te",
14757
+ "trailer",
14758
+ "transfer-encoding",
14759
+ "upgrade"
14760
+ ]);
14761
+ var PROXY_ONLY_HEADERS = /* @__PURE__ */ new Set([
14762
+ OIDC_TOKEN_HEADER,
14763
+ FORWARDED_HOST_HEADER,
14764
+ FORWARDED_SCHEME_HEADER,
14765
+ FORWARDED_PORT_HEADER
14766
+ ]);
14767
+ var AUTH_REJECTION_STATUS = /* @__PURE__ */ new Set([401, 403]);
14768
+ function jsonError(message, status) {
14769
+ return Response.json({ error: message }, { status });
14770
+ }
14771
+ function normalizeHost(value) {
14772
+ const trimmed = value.trim().toLowerCase();
14773
+ if (!trimmed || trimmed.includes("/") || trimmed.includes("\\") || trimmed.includes(":")) {
14774
+ return void 0;
14775
+ }
14776
+ return trimmed.replace(/\.$/, "");
14777
+ }
14778
+ function normalizeScheme(value) {
14779
+ return value.trim().toLowerCase() === "https" ? "https" : void 0;
14780
+ }
14781
+ function normalizePort(value) {
14782
+ if (!value) {
14783
+ return void 0;
14784
+ }
14785
+ const trimmed = value.trim();
14786
+ if (!/^\d{1,5}$/.test(trimmed)) {
14787
+ return void 0;
14788
+ }
14789
+ const port = Number.parseInt(trimmed, 10);
14790
+ return port >= 1 && port <= 65535 ? trimmed : void 0;
14791
+ }
14792
+ function upstreamPath(request, egressId) {
14793
+ const url = new URL(request.url);
14794
+ const prefix = `${ROUTE_PREFIX}/${encodeURIComponent(egressId)}`;
14795
+ if (url.pathname === prefix) {
14796
+ return `/${url.search}`;
14797
+ }
14798
+ if (url.pathname.startsWith(`${prefix}/`)) {
14799
+ return `${url.pathname.slice(prefix.length)}${url.search}`;
14800
+ }
14801
+ return void 0;
14802
+ }
14803
+ function buildUpstreamUrl(request, egressId) {
14804
+ const forwardedHost = request.headers.get(FORWARDED_HOST_HEADER);
14805
+ if (!forwardedHost?.trim()) {
14806
+ return { ok: false, error: "Missing forwarded host" };
14807
+ }
14808
+ const host = normalizeHost(forwardedHost);
14809
+ if (!host) {
14810
+ return { ok: false, error: "Invalid forwarded host" };
14811
+ }
14812
+ const forwardedScheme = request.headers.get(FORWARDED_SCHEME_HEADER);
14813
+ if (!forwardedScheme?.trim()) {
14814
+ return { ok: false, error: "Missing forwarded scheme" };
14815
+ }
14816
+ const scheme = normalizeScheme(forwardedScheme);
14817
+ if (!scheme) {
14818
+ return { ok: false, error: "Forwarded scheme must be https" };
14819
+ }
14820
+ const forwardedPort = request.headers.get(FORWARDED_PORT_HEADER);
14821
+ const port = normalizePort(forwardedPort);
14822
+ if (forwardedPort && !port) {
14823
+ return { ok: false, error: "Invalid forwarded port" };
14824
+ }
14825
+ const path11 = upstreamPath(request, egressId);
14826
+ if (!path11) {
14827
+ return { ok: false, error: "Invalid egress route" };
14828
+ }
14829
+ try {
14830
+ const url = new URL(`${scheme}://${host}${port ? `:${port}` : ""}${path11}`);
14831
+ return { ok: true, url };
14832
+ } catch {
14833
+ return { ok: false, error: "Invalid forwarded URL" };
14834
+ }
14835
+ }
14836
+ async function requestBodyBytes(request) {
14837
+ if (request.method === "GET" || request.method === "HEAD" || request.body === null) {
14838
+ return void 0;
14839
+ }
14840
+ return await request.arrayBuffer();
14841
+ }
14842
+ function requestHeaders(request, lease, upstreamHost) {
14843
+ const headers = new Headers();
14844
+ request.headers.forEach((value, key) => {
14845
+ const normalized = key.toLowerCase();
14846
+ if (HOP_BY_HOP_HEADERS.has(normalized) || PROXY_ONLY_HEADERS.has(normalized)) {
14847
+ return;
14848
+ }
14849
+ headers.append(key, value);
14850
+ });
14851
+ for (const transform of lease.headerTransforms) {
14852
+ if (!matchesSandboxEgressDomain(upstreamHost, transform.domain)) {
14853
+ continue;
14854
+ }
14855
+ for (const [key, value] of Object.entries(transform.headers)) {
14856
+ headers.set(key, value);
14857
+ }
14858
+ }
14859
+ return headers;
14860
+ }
14861
+ function responseHeaders(upstream) {
14862
+ const headers = new Headers();
14863
+ upstream.headers.forEach((value, key) => {
14864
+ const normalized = key.toLowerCase();
14865
+ if (!HOP_BY_HOP_HEADERS.has(normalized)) {
14866
+ headers.append(key, value);
14867
+ }
14868
+ });
14869
+ return headers;
14870
+ }
14871
+ async function credentialLease(egressId, provider, session) {
14872
+ const cached = await getSandboxEgressCredentialLease(
14873
+ egressId,
14874
+ provider,
14875
+ session
14876
+ );
14877
+ if (cached) {
14878
+ return cached;
14879
+ }
14880
+ const lease = await issueProviderCredentialLease({
14881
+ provider,
14882
+ requesterId: session.requesterId,
14883
+ reason: `sandbox-egress:${provider}`
14884
+ });
14885
+ const headerTransforms = lease.headerTransforms ?? [];
14886
+ if (headerTransforms.length === 0) {
14887
+ throw new Error(
14888
+ `Credential lease for ${provider} did not include header transforms`
14889
+ );
14890
+ }
14891
+ const cachedLease = {
14892
+ provider,
14893
+ expiresAt: lease.expiresAt,
14894
+ headerTransforms
14895
+ };
14896
+ await setSandboxEgressCredentialLease(egressId, session, cachedLease);
14897
+ return cachedLease;
14898
+ }
14899
+ function hasTransformForHost(lease, host) {
14900
+ return lease.headerTransforms.some(
14901
+ (transform) => matchesSandboxEgressDomain(host, transform.domain)
14902
+ );
14903
+ }
14904
+ async function proxySandboxEgressRequest(request, egressId, deps = {}) {
14905
+ const oidcToken = request.headers.get(OIDC_TOKEN_HEADER)?.trim();
14906
+ if (!oidcToken) {
14907
+ return jsonError("Missing Vercel Sandbox OIDC token", 401);
14908
+ }
14909
+ try {
14910
+ await (deps.verifyOidc ?? verifyVercelSandboxOidcToken)(
14911
+ oidcToken,
14912
+ egressId
14913
+ );
14914
+ } catch (error) {
14915
+ logWarn(
14916
+ "sandbox_egress_oidc_verification_failed",
14917
+ {},
14918
+ {
14919
+ "app.sandbox.oidc_error": error instanceof Error ? error.message : String(error)
14920
+ },
14921
+ "Sandbox egress OIDC verification failed"
14922
+ );
14923
+ return jsonError("Invalid Vercel Sandbox OIDC token", 401);
14924
+ }
14925
+ const upstreamResult = buildUpstreamUrl(request, egressId);
14926
+ if (!upstreamResult.ok) {
14927
+ return jsonError(upstreamResult.error, 400);
14928
+ }
14929
+ const upstreamUrl = upstreamResult.url;
14930
+ const provider = resolveSandboxEgressProviderForHost(upstreamUrl.hostname);
14931
+ if (!provider) {
14932
+ return jsonError("No provider owns forwarded host", 403);
14933
+ }
14934
+ const session = await getSandboxEgressSession(egressId);
14935
+ if (!session) {
14936
+ return jsonError("Sandbox egress session is not authorized", 403);
14937
+ }
14938
+ let lease;
14939
+ try {
14940
+ lease = await credentialLease(egressId, provider, session);
14941
+ } catch (error) {
14942
+ if (error instanceof CredentialUnavailableError) {
14943
+ return new Response(
14944
+ `junior-auth-required provider=${error.provider} 401 unauthorized
14945
+ ${error.message}`,
14946
+ {
14947
+ status: 401,
14948
+ headers: { "content-type": "text/plain; charset=utf-8" }
14949
+ }
14950
+ );
14951
+ }
14952
+ throw error;
14953
+ }
14954
+ if (!hasTransformForHost(lease, upstreamUrl.hostname)) {
14955
+ return jsonError("Credential lease does not cover forwarded host", 403);
14956
+ }
14957
+ const body = await requestBodyBytes(request);
14958
+ const upstream = await (deps.fetch ?? fetch)(upstreamUrl, {
14959
+ method: request.method,
14960
+ headers: requestHeaders(request, lease, upstreamUrl.hostname),
14961
+ ...body ? { body } : {},
14962
+ redirect: "manual"
14963
+ });
14964
+ if (AUTH_REJECTION_STATUS.has(upstream.status)) {
14965
+ logWarn(
14966
+ "sandbox_egress_upstream_auth_rejected",
14967
+ {},
14968
+ {
14969
+ "app.credential.provider": provider,
14970
+ "http.request.method": request.method,
14971
+ "http.response.status_code": upstream.status,
14972
+ "server.address": upstreamUrl.hostname
14973
+ },
14974
+ "Sandbox egress upstream auth rejected"
14975
+ );
14976
+ await clearSandboxEgressCredentialLease(egressId, provider, session);
14977
+ }
14978
+ return new Response(upstream.body, {
14979
+ status: upstream.status,
14980
+ statusText: upstream.statusText,
14981
+ headers: responseHeaders(upstream)
14982
+ });
14983
+ }
14984
+
14985
+ // src/handlers/sandbox-egress-proxy.ts
14986
+ async function ALL(request, egressId) {
14987
+ return await proxySandboxEgressRequest(request, egressId);
14988
+ }
14989
+
14371
14990
  // src/chat/slack/context.ts
14372
14991
  function toTrimmedSlackString(value) {
14373
14992
  const normalized = toOptionalString(value);
@@ -17744,6 +18363,12 @@ async function createApp(options) {
17744
18363
  app.post("/api/internal/turn-resume", (c) => {
17745
18364
  return POST(c.req.raw, waitUntil);
17746
18365
  });
18366
+ app.all("/api/internal/sandbox-egress/:egressId", (c) => {
18367
+ return ALL(c.req.raw, c.req.param("egressId"));
18368
+ });
18369
+ app.all("/api/internal/sandbox-egress/:egressId/*", (c) => {
18370
+ return ALL(c.req.raw, c.req.param("egressId"));
18371
+ });
17747
18372
  app.post("/api/webhooks/:platform", (c) => {
17748
18373
  return POST2(c.req.raw, c.req.param("platform"), waitUntil);
17749
18374
  });