@opengeni/api-router 0.12.5 → 0.12.12

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.
@@ -21,13 +21,13 @@ import {
21
21
  import { dbSql, getWorkspace as getWorkspace2 } from "@opengeni/db";
22
22
  import { createObservability } from "@opengeni/observability";
23
23
  import { createObjectStorage } from "@opengeni/storage";
24
- import { WebStandardStreamableHTTPServerTransport as WebStandardStreamableHTTPServerTransport2 } from "@modelcontextprotocol/sdk/server/webStandardStreamableHttp.js";
24
+ import { WebStandardStreamableHTTPServerTransport as WebStandardStreamableHTTPServerTransport3 } from "@modelcontextprotocol/sdk/server/webStandardStreamableHttp.js";
25
25
  import { Hono } from "hono";
26
26
  import { bodyLimit } from "hono/body-limit";
27
27
  import { cors } from "hono/cors";
28
28
  import { HTTPException as HTTPException26 } from "hono/http-exception";
29
29
  import {
30
- hasPermission as hasPermission9,
30
+ hasPermission as hasPermission10,
31
31
  requireAccessGrant as requireAccessGrant18,
32
32
  requirePermission,
33
33
  requireSessionAuthorization as requireSessionAuthorization3,
@@ -291,6 +291,8 @@ import { requireLimit as requireLimit8 } from "@opengeni/core";
291
291
  // src/mcp/server.ts
292
292
  import {
293
293
  CreateScheduledTaskRequest,
294
+ DEFAULT_FIRST_PARTY_MCP_TOOLS,
295
+ FIRST_PARTY_MCP_TOOL_NAMES,
294
296
  defaultRepositoryMountPath,
295
297
  SESSION_EVENT_RAW_DELTA_TYPES,
296
298
  SessionEventLatestClass,
@@ -335,7 +337,6 @@ import {
335
337
  MEMORY_CORRECT_TOOL_DESCRIPTION,
336
338
  MEMORY_SAVE_TOOL_DESCRIPTION,
337
339
  MEMORY_SEARCH_TOOL_DESCRIPTION,
338
- requireFile,
339
340
  requireScheduledTask,
340
341
  requireSession,
341
342
  saveWorkspaceMemory,
@@ -356,7 +357,9 @@ import {
356
357
  GitHubAppConfigurationError,
357
358
  githubAppMissingSettings
358
359
  } from "@opengeni/github";
359
- import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
360
+ import {
361
+ McpServer
362
+ } from "@modelcontextprotocol/sdk/server/mcp.js";
360
363
  import * as z4 from "zod/v4";
361
364
  import {
362
365
  hasPermission as hasPermission2,
@@ -418,6 +421,7 @@ async function listWorkspaceGitHubInstallationBindings(deps, workspaceId) {
418
421
  ),
419
422
  repositoryScope: installation.repositoryScope,
420
423
  repositoryCount: installation.repositoryIds.length,
424
+ configureUrl: null,
421
425
  createdAt: installation.createdAt,
422
426
  updatedAt: installation.updatedAt
423
427
  }));
@@ -2607,17 +2611,124 @@ function slackPostOutcomeMayBeAmbiguous(error) {
2607
2611
  }
2608
2612
 
2609
2613
  // src/mcp/server.ts
2614
+ var FIRST_PARTY_TOOL_AUTHORIZATION = {
2615
+ set_session_title: { sessionRequired: true, allOf: ["sessions:control"] },
2616
+ goal_set: { sessionRequired: true, allOf: ["goals:manage"] },
2617
+ goal_update: { sessionRequired: true, allOf: ["goals:manage"] },
2618
+ goal_complete: { sessionRequired: true, allOf: ["goals:manage"] },
2619
+ goal_pause: { sessionRequired: true, allOf: ["goals:manage"] },
2620
+ memory_search: { sessionRequired: true, allOf: ["documents:search"] },
2621
+ memory_save: { sessionRequired: true, allOf: ["documents:search"] },
2622
+ memory_correct: { sessionRequired: true, allOf: ["documents:search"] },
2623
+ sandboxes_list: { sessionRequired: true, allOf: ["sessions:read"] },
2624
+ sandbox_attach: { sessionRequired: true, allOf: ["sessions:control"] },
2625
+ sandbox_swap: { sessionRequired: true, allOf: ["sessions:control"] },
2626
+ run_on: { sessionRequired: true, allOf: ["sessions:control"] },
2627
+ sandbox_provision: { sessionRequired: true, allOf: ["sessions:control"] },
2628
+ rig_list: { allOf: ["rigs:use"] },
2629
+ rig_get: { allOf: ["rigs:use"] },
2630
+ rig_propose_change: { allOf: ["rigs:use"] },
2631
+ rig_verify: { allOf: ["rigs:use"] },
2632
+ rig_promote: { allOf: ["rigs:manage"] },
2633
+ sessions_list: { allOf: ["sessions:read"] },
2634
+ session_get: { allOf: ["sessions:read"] },
2635
+ session_events: { allOf: ["sessions:read"] },
2636
+ session_create: { allOf: ["sessions:create"] },
2637
+ session_send_message: { allOf: ["sessions:control"] },
2638
+ session_pause: { allOf: ["sessions:control"] },
2639
+ session_resume: { allOf: ["sessions:control"] },
2640
+ session_steer: { sessionRequired: true, allOf: ["sessions:control"] },
2641
+ set_other_session_title: { allOf: ["sessions:control"] },
2642
+ variable_set_list: { allOf: ["variable-sets:use"] },
2643
+ environment_list: { allOf: ["variable-sets:use"] },
2644
+ variable_set_set_variable: { allOf: ["variable-sets:manage"] },
2645
+ environment_set_variable: { allOf: ["variable-sets:manage"] },
2646
+ github_connect_link: { allOf: ["github:use"] },
2647
+ github_token: { sessionRequired: true, allOf: ["github:use"] },
2648
+ github_repositories_list: { allOf: ["github:use"] },
2649
+ social_connections_list: { allOf: ["connections:read"] },
2650
+ social_posts_recent: { allOf: ["connections:read"] },
2651
+ social_daily_analysis_context: { allOf: ["connections:read"] },
2652
+ scheduled_tasks_list: { anyOf: ["scheduled_tasks:manage", "scheduled_tasks:run"] },
2653
+ scheduled_tasks_get: { anyOf: ["scheduled_tasks:manage", "scheduled_tasks:run"] },
2654
+ scheduled_tasks_create: { allOf: ["scheduled_tasks:manage"] },
2655
+ scheduled_tasks_update: { allOf: ["scheduled_tasks:manage"] },
2656
+ scheduled_tasks_pause: { allOf: ["scheduled_tasks:manage"] },
2657
+ scheduled_tasks_resume: { allOf: ["scheduled_tasks:manage"] },
2658
+ scheduled_tasks_trigger: { allOf: ["scheduled_tasks:run"] },
2659
+ scheduled_tasks_delete: { allOf: ["scheduled_tasks:manage"] },
2660
+ scheduled_task_runs_list: { anyOf: ["scheduled_tasks:manage", "scheduled_tasks:run"] },
2661
+ slack_bot_list_channels: { allOf: ["connections:read"] },
2662
+ slack_bot_channel_history: { allOf: ["connections:read"] },
2663
+ slack_bot_list_users: { allOf: ["connections:read"] },
2664
+ slack_bot_post_message: { allOf: ["connections:read"] }
2665
+ };
2666
+ var FIRST_PARTY_MCP_TOOL_NAME_SET = new Set(FIRST_PARTY_MCP_TOOL_NAMES);
2667
+ var PolicyMcpServer = class extends McpServer {
2668
+ constructor(grant, sessionId, selectedTools, allowUncatalogued) {
2669
+ super({ name: "opengeni", version: "1.0.0" });
2670
+ this.grant = grant;
2671
+ this.sessionId = sessionId;
2672
+ this.selectedTools = selectedTools;
2673
+ this.allowUncatalogued = allowUncatalogued;
2674
+ }
2675
+ registeredToolCount = 0;
2676
+ registerTool(name, config, cb) {
2677
+ const catalogued = FIRST_PARTY_MCP_TOOL_NAME_SET.has(name);
2678
+ let admitted = this.allowUncatalogued && !catalogued;
2679
+ if (catalogued) {
2680
+ const toolName = name;
2681
+ const policy = FIRST_PARTY_TOOL_AUTHORIZATION[toolName];
2682
+ const authorized = (!policy.sessionRequired || this.sessionId !== null) && (policy.allOf?.every((permission) => hasPermission2(this.grant.permissions, permission)) ?? true) && (policy.anyOf?.some((permission) => hasPermission2(this.grant.permissions, permission)) ?? true);
2683
+ const selected = this.selectedTools === null || this.selectedTools.has(toolName);
2684
+ admitted = authorized && selected;
2685
+ }
2686
+ if (!admitted) {
2687
+ return {
2688
+ ...config.title ? { title: config.title } : {},
2689
+ ...config.description ? { description: config.description } : {},
2690
+ ...config.annotations ? { annotations: config.annotations } : {},
2691
+ ...config._meta ? { _meta: config._meta } : {},
2692
+ handler: cb,
2693
+ enabled: false,
2694
+ enable() {
2695
+ },
2696
+ disable() {
2697
+ },
2698
+ update() {
2699
+ },
2700
+ remove() {
2701
+ }
2702
+ };
2703
+ }
2704
+ this.registeredToolCount += 1;
2705
+ return super.registerTool(name, config, cb);
2706
+ }
2707
+ ensureToolsListHandler() {
2708
+ if (this.registeredToolCount > 0) return;
2709
+ super.registerTool(
2710
+ "__opengeni_empty_first_party_surface__",
2711
+ {
2712
+ description: "Internal disabled placeholder for an empty first-party surface.",
2713
+ inputSchema: z4.object({})
2714
+ },
2715
+ async () => ({
2716
+ content: [{ type: "text", text: '{"unavailable":true}' }]
2717
+ })
2718
+ ).disable();
2719
+ }
2720
+ };
2610
2721
  function buildOpenGeniMcpServer(deps, grant, options = {}) {
2611
- const server = new McpServer({
2612
- name: "opengeni",
2613
- version: "1.0.0"
2614
- });
2615
2722
  const json = (value) => ({
2616
2723
  content: [{ type: "text", text: JSON.stringify(value, null, 2) }]
2617
2724
  });
2618
2725
  const can = (permission) => hasPermission2(grant.permissions, permission);
2619
2726
  const toolspaceMode = options.toolspace != null;
2620
2727
  const sessionId = typeof grant.metadata?.["sessionId"] === "string" ? grant.metadata["sessionId"] : null;
2728
+ const selectedTools = sessionId !== null && !toolspaceMode ? new Set(
2729
+ grant.metadata?.["firstPartyMcpTools"] ?? DEFAULT_FIRST_PARTY_MCP_TOOLS
2730
+ ) : null;
2731
+ const server = new PolicyMcpServer(grant, sessionId, selectedTools, toolspaceMode);
2621
2732
  if (sessionId !== null && (!toolspaceMode || can("sessions:control"))) {
2622
2733
  server.registerTool(
2623
2734
  "set_session_title",
@@ -2657,44 +2768,6 @@ function buildOpenGeniMcpServer(deps, grant, options = {}) {
2657
2768
  registerGitHubTokenTool(server, deps, grant, sessionId, json);
2658
2769
  }
2659
2770
  }
2660
- if (!toolspaceMode || can("files:read")) {
2661
- server.registerTool(
2662
- "files_get_download_url",
2663
- {
2664
- description: "Create a short-lived download URL for a ready file asset.",
2665
- inputSchema: { fileId: z4.string().uuid() }
2666
- },
2667
- async ({ fileId }) => {
2668
- if (!deps.objectStorage) {
2669
- throw new Error("object storage is not configured");
2670
- }
2671
- const file = await requireFile(deps.db, grant.workspaceId, fileId);
2672
- if (file.status !== "ready") {
2673
- throw new Error(`file is ${file.status}`);
2674
- }
2675
- const signed = await deps.objectStorage.createGetUrl({
2676
- key: file.objectKey
2677
- });
2678
- return json({
2679
- file: {
2680
- id: file.id,
2681
- filename: file.filename,
2682
- safeFilename: file.safeFilename,
2683
- contentType: file.contentType,
2684
- sizeBytes: file.sizeBytes,
2685
- sha256: file.sha256,
2686
- status: file.status,
2687
- createdAt: file.createdAt,
2688
- updatedAt: file.updatedAt
2689
- },
2690
- downloadUrl: {
2691
- url: signed.url,
2692
- expiresAt: signed.expiresAt.toISOString()
2693
- }
2694
- });
2695
- }
2696
- );
2697
- }
2698
2771
  if (!toolspaceMode || can("github:use")) {
2699
2772
  server.registerTool(
2700
2773
  "github_repositories_list",
@@ -2985,7 +3058,8 @@ function buildOpenGeniMcpServer(deps, grant, options = {}) {
2985
3058
  await deps.workflowClient.triggerScheduledTask({
2986
3059
  task,
2987
3060
  agentRunUsageIdempotencyKey,
2988
- triggerWorkflowId
3061
+ triggerWorkflowId,
3062
+ initiator: { kind: "subject", subjectId: grant.subjectId }
2989
3063
  });
2990
3064
  await recordWorkspaceUsage(deps, {
2991
3065
  accountId: grant.accountId,
@@ -3031,6 +3105,7 @@ function buildOpenGeniMcpServer(deps, grant, options = {}) {
3031
3105
  );
3032
3106
  }
3033
3107
  registerToolspaceProxyTools(server, options.toolspace ?? null);
3108
+ server.ensureToolsListHandler();
3034
3109
  return server;
3035
3110
  }
3036
3111
  function registerSlackBotTools(server, deps, grant, sessionId, json) {
@@ -3658,7 +3733,7 @@ function registerRigTools(server, deps, grant, can, sessionId, json) {
3658
3733
  );
3659
3734
  }
3660
3735
  }
3661
- function exactAgentCommandContext(grant, callerSessionId) {
3736
+ function exactAgentCommandContext(grant, callerSessionId, authorizationSurface) {
3662
3737
  const turnId = grant.metadata?.["turnId"];
3663
3738
  const attemptId = grant.metadata?.["attemptId"];
3664
3739
  const executionGeneration = grant.metadata?.["executionGeneration"];
@@ -3672,7 +3747,8 @@ function exactAgentCommandContext(grant, callerSessionId) {
3672
3747
  callerSessionId,
3673
3748
  callerTurnId: turnId,
3674
3749
  callerAttemptId: attemptId,
3675
- callerExecutionGeneration: executionGeneration
3750
+ callerExecutionGeneration: executionGeneration,
3751
+ ...authorizationSurface ? { authorizationSurface } : {}
3676
3752
  };
3677
3753
  }
3678
3754
  function registerWorkspaceOrchestrationTools(server, deps, grant, can, callerSessionId, toolspaceMode, json) {
@@ -3896,6 +3972,9 @@ function registerWorkspaceOrchestrationTools(server, deps, grant, can, callerSes
3896
3972
  firstPartyMcpPermissions: z4.array(z4.string()).optional().describe(
3897
3973
  "Optional first-party capability set for the child. Omit to inherit this session's effective permissions. An explicit set may only narrow capabilities held by this session. A goal-bearing child requires goals:manage in the resulting set; creation fails rather than adding it implicitly."
3898
3974
  ),
3975
+ firstPartyMcpTools: z4.array(z4.enum(FIRST_PARTY_MCP_TOOL_NAMES)).optional().describe(
3976
+ "Exact model-visible first-party tool selection for the child. Omit to inherit this session's effective selection. This does not grant permissions."
3977
+ ),
3899
3978
  // Shared-sandbox placement (addendum 05 §D). OMIT (default) to SHARE the
3900
3979
  // creator's box — one filesystem/repo/desktop, N independent conversations;
3901
3980
  // this is the SAFE DEFAULT. Pass "new" for a fresh isolated box (a different
@@ -3949,7 +4028,7 @@ function registerWorkspaceOrchestrationTools(server, deps, grant, can, callerSes
3949
4028
  server.registerTool(
3950
4029
  "session_send_message",
3951
4030
  {
3952
- description: "Send information to another session. From an OpenGeni worker this becomes a coalescible internal update, never a visible prompt-queue row; pending updates are delivered together on the target's next inference. A sessionless operator call appends one visible prompt.",
4031
+ description: "Send information to another session. From an OpenGeni worker this becomes a canonical coalescible machine input: it appears in the target's compact incoming queue group, is durably added to model history when claimed, and remains visible in the timeline. A sessionless operator call appends one human/API prompt.",
3953
4032
  inputSchema: {
3954
4033
  sessionId: z4.string().uuid(),
3955
4034
  text: z4.string().min(1),
@@ -3987,7 +4066,6 @@ function registerWorkspaceOrchestrationTools(server, deps, grant, can, callerSes
3987
4066
  targetSessionId,
3988
4067
  {
3989
4068
  text,
3990
- toolsProvided: false,
3991
4069
  delivery: "send",
3992
4070
  origin: "operator",
3993
4071
  clientEventId: idempotencyKey,
@@ -4010,16 +4088,10 @@ function registerWorkspaceOrchestrationTools(server, deps, grant, can, callerSes
4010
4088
  }
4011
4089
  },
4012
4090
  async ({ sessionId, idempotencyKey, reason }) => {
4013
- const authorization = await authorizeFirstPartySession(
4014
- deps,
4015
- grant,
4016
- sessionId,
4017
- "session.control"
4018
- );
4019
4091
  if (callerSessionId !== null) {
4020
4092
  const controlled2 = await controlAgentSessionWorkstream(
4021
4093
  deps,
4022
- exactAgentCommandContext(grant, callerSessionId),
4094
+ exactAgentCommandContext(grant, callerSessionId, "first_party_mcp"),
4023
4095
  {
4024
4096
  targetSessionId: sessionId,
4025
4097
  action: "pause",
@@ -4032,7 +4104,7 @@ function registerWorkspaceOrchestrationTools(server, deps, grant, can, callerSes
4032
4104
  effectiveControl: projectEffectiveControlForRelatedAccess(
4033
4105
  serializeEffectiveSessionControl(controlled2.control),
4034
4106
  sessionId,
4035
- authorization?.relatedSessionAccess ?? "root"
4107
+ controlled2.authorization?.relatedSessionAccess ?? "root"
4036
4108
  ),
4037
4109
  interruptionCount: controlled2.interruptionCount,
4038
4110
  replay: controlled2.replay
@@ -4044,7 +4116,8 @@ function registerWorkspaceOrchestrationTools(server, deps, grant, can, callerSes
4044
4116
  accountId: grant.accountId,
4045
4117
  workspaceId: grant.workspaceId,
4046
4118
  sessionId,
4047
- subjectId: grant.subjectId
4119
+ subjectId: grant.subjectId,
4120
+ authorizationSurface: "first_party_mcp"
4048
4121
  },
4049
4122
  {
4050
4123
  action: "pause",
@@ -4052,14 +4125,7 @@ function registerWorkspaceOrchestrationTools(server, deps, grant, can, callerSes
4052
4125
  ...reason ? { reason } : {}
4053
4126
  }
4054
4127
  );
4055
- return json({
4056
- ...controlled,
4057
- effectiveControl: projectEffectiveControlForRelatedAccess(
4058
- controlled.effectiveControl,
4059
- sessionId,
4060
- authorization?.relatedSessionAccess ?? "root"
4061
- )
4062
- });
4128
+ return json(controlled);
4063
4129
  }
4064
4130
  );
4065
4131
  server.registerTool(
@@ -4073,16 +4139,10 @@ function registerWorkspaceOrchestrationTools(server, deps, grant, can, callerSes
4073
4139
  }
4074
4140
  },
4075
4141
  async ({ sessionId, idempotencyKey, reason }) => {
4076
- const authorization = await authorizeFirstPartySession(
4077
- deps,
4078
- grant,
4079
- sessionId,
4080
- "session.control"
4081
- );
4082
4142
  if (callerSessionId !== null) {
4083
4143
  const controlled2 = await controlAgentSessionWorkstream(
4084
4144
  deps,
4085
- exactAgentCommandContext(grant, callerSessionId),
4145
+ exactAgentCommandContext(grant, callerSessionId, "first_party_mcp"),
4086
4146
  {
4087
4147
  targetSessionId: sessionId,
4088
4148
  action: "resume",
@@ -4095,7 +4155,7 @@ function registerWorkspaceOrchestrationTools(server, deps, grant, can, callerSes
4095
4155
  effectiveControl: projectEffectiveControlForRelatedAccess(
4096
4156
  serializeEffectiveSessionControl(controlled2.control),
4097
4157
  sessionId,
4098
- authorization?.relatedSessionAccess ?? "root"
4158
+ controlled2.authorization?.relatedSessionAccess ?? "root"
4099
4159
  ),
4100
4160
  interruptionCount: controlled2.interruptionCount,
4101
4161
  replay: controlled2.replay
@@ -4107,7 +4167,8 @@ function registerWorkspaceOrchestrationTools(server, deps, grant, can, callerSes
4107
4167
  accountId: grant.accountId,
4108
4168
  workspaceId: grant.workspaceId,
4109
4169
  sessionId,
4110
- subjectId: grant.subjectId
4170
+ subjectId: grant.subjectId,
4171
+ authorizationSurface: "first_party_mcp"
4111
4172
  },
4112
4173
  {
4113
4174
  action: "resume",
@@ -4115,21 +4176,14 @@ function registerWorkspaceOrchestrationTools(server, deps, grant, can, callerSes
4115
4176
  ...reason ? { reason } : {}
4116
4177
  }
4117
4178
  );
4118
- return json({
4119
- ...controlled,
4120
- effectiveControl: projectEffectiveControlForRelatedAccess(
4121
- controlled.effectiveControl,
4122
- sessionId,
4123
- authorization?.relatedSessionAccess ?? "root"
4124
- )
4125
- });
4179
+ return json(controlled);
4126
4180
  }
4127
4181
  );
4128
4182
  if (callerSessionId !== null) {
4129
4183
  server.registerTool(
4130
4184
  "session_steer",
4131
4185
  {
4132
- description: "Atomically replace another session's current direction and resume it. The instruction is an internal update, never a human queue row.",
4186
+ description: "Atomically replace another session's current direction and resume it. The Agent Steer is a typed machine input shown in the target queue/timeline and durably retained in model history; it never impersonates a human prompt.",
4133
4187
  inputSchema: {
4134
4188
  sessionId: z4.string().uuid(),
4135
4189
  instruction: z4.string().min(1),
@@ -4137,10 +4191,9 @@ function registerWorkspaceOrchestrationTools(server, deps, grant, can, callerSes
4137
4191
  }
4138
4192
  },
4139
4193
  async ({ sessionId, instruction, idempotencyKey }) => {
4140
- await authorizeFirstPartySession(deps, grant, sessionId, "session.steer");
4141
4194
  const result = await steerAgentSession(
4142
4195
  deps,
4143
- exactAgentCommandContext(grant, callerSessionId),
4196
+ exactAgentCommandContext(grant, callerSessionId, "first_party_mcp"),
4144
4197
  { targetSessionId: sessionId, instruction, idempotencyKey }
4145
4198
  );
4146
4199
  return json({
@@ -4314,14 +4367,16 @@ function registerGitHubConnectTool(server, deps, grant, options, json) {
4314
4367
  const { settings } = deps;
4315
4368
  const missing = githubAppMissingSettings(settings);
4316
4369
  const slug = settings.githubAppSlug?.trim() || null;
4370
+ const setupMode = settings.productAccessMode === "managed" ? "platform" : "operator";
4317
4371
  if (missing.length > 0 || !slug) {
4318
4372
  return json({
4319
4373
  configured: false,
4320
4374
  status: "disabled",
4321
- appSlug: slug,
4375
+ setupMode,
4376
+ appSlug: setupMode === "operator" ? slug : null,
4322
4377
  installUrl: null,
4323
4378
  linkUrl: null,
4324
- missing
4379
+ missing: setupMode === "operator" ? missing : []
4325
4380
  });
4326
4381
  }
4327
4382
  const installations = await listWorkspaceGitHubInstallationBindings(deps, grant.workspaceId);
@@ -4334,13 +4389,18 @@ function registerGitHubConnectTool(server, deps, grant, options, json) {
4334
4389
  ...githubBrowserGrantClaims(settings, grant)
4335
4390
  }) : null;
4336
4391
  const connectUrl = state ? `${baseUrl}/v1/workspaces/${grant.workspaceId}/github/connect?state=${encodeURIComponent(state)}` : null;
4392
+ const installationViews = installations.map((installation) => ({
4393
+ ...installation,
4394
+ configureUrl: state && baseUrl ? `${baseUrl}/v1/workspaces/${grant.workspaceId}/github/installations/${installation.installationId}/configure?state=${encodeURIComponent(state)}` : null
4395
+ }));
4337
4396
  return json({
4338
4397
  configured: true,
4339
4398
  status,
4340
- appSlug: slug,
4399
+ setupMode,
4400
+ appSlug: setupMode === "operator" ? slug : null,
4341
4401
  installUrl: connectUrl,
4342
4402
  linkUrl: connectUrl,
4343
- installations,
4403
+ installations: installationViews,
4344
4404
  missing: []
4345
4405
  });
4346
4406
  }
@@ -5881,8 +5941,8 @@ function registerCatalogAssetRoutes(app, deps) {
5881
5941
  if (!contentType) {
5882
5942
  throw new HTTPException5(404, { message: "asset not found" });
5883
5943
  }
5884
- const object4 = await objectStorage.getObjectBytes(key);
5885
- if (!object4) {
5944
+ const object5 = await objectStorage.getObjectBytes(key);
5945
+ if (!object5) {
5886
5946
  throw new HTTPException5(404, { message: "asset not found" });
5887
5947
  }
5888
5948
  const etag = etagForKey(key);
@@ -5895,7 +5955,7 @@ function registerCatalogAssetRoutes(app, deps) {
5895
5955
  if (ifNoneMatchSatisfied(c.req.header("if-none-match"), etag)) {
5896
5956
  return c.body(null, 304, headers);
5897
5957
  }
5898
- return c.body(new Uint8Array(object4.bytes), 200, { ...headers, "Content-Type": contentType });
5958
+ return c.body(new Uint8Array(object5.bytes), 200, { ...headers, "Content-Type": contentType });
5899
5959
  });
5900
5960
  }
5901
5961
  function catalogAssetKeyFromPath(pathname) {
@@ -8931,7 +8991,7 @@ import {
8931
8991
  queueDocumentForReindex,
8932
8992
  searchDocuments as searchDocuments2
8933
8993
  } from "@opengeni/documents";
8934
- import { WebStandardStreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/webStandardStreamableHttp.js";
8994
+ import { WebStandardStreamableHTTPServerTransport as WebStandardStreamableHTTPServerTransport2 } from "@modelcontextprotocol/sdk/server/webStandardStreamableHttp.js";
8935
8995
  import { HTTPException as HTTPException11 } from "hono/http-exception";
8936
8996
  import { requireAccessGrant as requireAccessGrant5 } from "@opengeni/core";
8937
8997
  import { recordWorkspaceUsage as recordWorkspaceUsage3, requireLimit as requireLimit3 } from "@opengeni/core";
@@ -9162,8 +9222,90 @@ import {
9162
9222
  import { HTTPException as HTTPException10 } from "hono/http-exception";
9163
9223
  import { requireAccessGrant as requireAccessGrant4 } from "@opengeni/core";
9164
9224
  import { recordWorkspaceUsage as recordWorkspaceUsage2, requireLimit as requireLimit2 } from "@opengeni/core";
9225
+ import { WebStandardStreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/webStandardStreamableHttp.js";
9226
+
9227
+ // src/mcp/files.ts
9228
+ import { requireFile } from "@opengeni/db";
9229
+ import { hasPermission as hasPermission7 } from "@opengeni/core";
9230
+ import { McpServer as McpServer3 } from "@modelcontextprotocol/sdk/server/mcp.js";
9231
+ import * as z3 from "zod/v4";
9232
+ function buildFilesMcpServer(deps, grant) {
9233
+ const server = new McpServer3({
9234
+ name: "opengeni-files",
9235
+ version: "1.0.0"
9236
+ });
9237
+ if (!hasPermission7(grant.permissions, "files:read")) {
9238
+ server.registerTool(
9239
+ "__opengeni_empty_files_surface__",
9240
+ {
9241
+ description: "Internal disabled placeholder for an unauthorized files surface.",
9242
+ inputSchema: z3.object({})
9243
+ },
9244
+ async () => ({
9245
+ content: [{ type: "text", text: '{"unavailable":true}' }]
9246
+ })
9247
+ ).disable();
9248
+ return server;
9249
+ }
9250
+ server.registerTool(
9251
+ "files_get_download_url",
9252
+ {
9253
+ description: "Create a short-lived download URL for a ready file asset.",
9254
+ inputSchema: { fileId: z3.string().uuid() }
9255
+ },
9256
+ async ({ fileId }) => {
9257
+ if (!deps.objectStorage) {
9258
+ throw new Error("object storage is not configured");
9259
+ }
9260
+ const file = await requireFile(deps.db, grant.workspaceId, fileId);
9261
+ if (file.status !== "ready") {
9262
+ throw new Error(`file is ${file.status}`);
9263
+ }
9264
+ const signed = await deps.objectStorage.createGetUrl({ key: file.objectKey });
9265
+ return {
9266
+ content: [
9267
+ {
9268
+ type: "text",
9269
+ text: JSON.stringify(
9270
+ {
9271
+ file: {
9272
+ id: file.id,
9273
+ filename: file.filename,
9274
+ safeFilename: file.safeFilename,
9275
+ contentType: file.contentType,
9276
+ sizeBytes: file.sizeBytes,
9277
+ sha256: file.sha256,
9278
+ status: file.status,
9279
+ createdAt: file.createdAt,
9280
+ updatedAt: file.updatedAt
9281
+ },
9282
+ downloadUrl: {
9283
+ url: signed.url,
9284
+ expiresAt: signed.expiresAt.toISOString()
9285
+ }
9286
+ },
9287
+ null,
9288
+ 2
9289
+ )
9290
+ }
9291
+ ]
9292
+ };
9293
+ }
9294
+ );
9295
+ return server;
9296
+ }
9297
+
9298
+ // src/routes/files.ts
9165
9299
  function registerFileRoutes(app, deps) {
9166
9300
  const { db, objectStorage } = deps;
9301
+ app.all("/v1/workspaces/:workspaceId/mcp/files", async (c) => {
9302
+ const workspaceId = c.req.param("workspaceId");
9303
+ const grant = await requireAccessGrant4(c, deps, workspaceId, "files:read");
9304
+ const transport = new WebStandardStreamableHTTPServerTransport({ enableJsonResponse: true });
9305
+ const server = buildFilesMcpServer(deps, grant);
9306
+ await server.connect(transport);
9307
+ return await transport.handleRequest(c.req.raw);
9308
+ });
9167
9309
  app.post("/v1/workspaces/:workspaceId/files/uploads", async (c) => {
9168
9310
  const workspaceId = c.req.param("workspaceId");
9169
9311
  const grant = await requireAccessGrant4(c, deps, workspaceId, "files:upload");
@@ -9972,7 +10114,7 @@ function registerDocumentRoutes(app, deps) {
9972
10114
  const workspaceId = c.req.param("workspaceId");
9973
10115
  const grant = await requireAccessGrant5(c, deps, workspaceId, "documents:search");
9974
10116
  const sessionId = typeof grant.metadata?.sessionId === "string" ? grant.metadata.sessionId : void 0;
9975
- const transport = new WebStandardStreamableHTTPServerTransport({ enableJsonResponse: true });
10117
+ const transport = new WebStandardStreamableHTTPServerTransport2({ enableJsonResponse: true });
9976
10118
  const server = buildDocumentsMcpServer(
9977
10119
  db,
9978
10120
  grant.accountId,
@@ -11653,6 +11795,7 @@ import {
11653
11795
  buildGitHubAppManifest,
11654
11796
  convertGitHubAppManifest,
11655
11797
  createSignedState as createSignedState5,
11798
+ discoverGitHubInstallationBindingCandidates,
11656
11799
  envLinesFromGitHubManifestConversion,
11657
11800
  GitHubAppApiError,
11658
11801
  GitHubAppConfigurationError as GitHubAppConfigurationError2,
@@ -11667,7 +11810,7 @@ import {
11667
11810
  } from "@opengeni/github";
11668
11811
  import { deleteCookie, setCookie } from "hono/cookie";
11669
11812
  import { HTTPException as HTTPException17 } from "hono/http-exception";
11670
- import { hasPermission as hasPermission7, requireAccessGrant as requireAccessGrant10 } from "@opengeni/core";
11813
+ import { hasPermission as hasPermission8, requireAccessGrant as requireAccessGrant10 } from "@opengeni/core";
11671
11814
  var githubStateCookie = "opengeni_github_state";
11672
11815
  var githubBindingStateMaxAgeSeconds = 10 * 60;
11673
11816
  var legacyInstallationChooserDisabledMessage = "The legacy repository-admin GitHub installation chooser is disabled; use the GitHub owner-consent connect flow";
@@ -11680,7 +11823,8 @@ function registerGitHubRoutes(app, deps) {
11680
11823
  const slug = settings.githubAppSlug?.trim() || null;
11681
11824
  const installations = missing.length === 0 ? await listWorkspaceGitHubInstallationBindings(deps, grant.workspaceId) : [];
11682
11825
  const status = githubBindingStatus(missing.length === 0, installations);
11683
- const canManage = hasPermission7(grant.permissions, "github:manage");
11826
+ const setupMode = settings.productAccessMode === "managed" ? "platform" : "operator";
11827
+ const canManage = hasPermission8(grant.permissions, "github:manage");
11684
11828
  const connectState = missing.length === 0 && slug && canManage ? createSignedState5(githubStateSecret, {
11685
11829
  accountId: grant.accountId,
11686
11830
  workspaceId: grant.workspaceId,
@@ -11688,16 +11832,21 @@ function registerGitHubRoutes(app, deps) {
11688
11832
  ...githubBrowserGrantClaims(settings, grant)
11689
11833
  }) : null;
11690
11834
  const connectUrl = connectState ? `${openGeniBaseUrl(settings, c)}/v1/workspaces/${grant.workspaceId}/github/connect?state=${encodeURIComponent(connectState)}` : null;
11835
+ const installationViews = installations.map((installation) => ({
11836
+ ...installation,
11837
+ configureUrl: connectState ? `${openGeniBaseUrl(settings, c)}/v1/workspaces/${grant.workspaceId}/github/installations/${installation.installationId}/configure?state=${encodeURIComponent(connectState)}` : null
11838
+ }));
11691
11839
  return c.json({
11692
11840
  configured: missing.length === 0,
11693
11841
  status,
11694
- appId: settings.githubAppId ?? null,
11695
- clientId: settings.githubClientId ?? null,
11696
- appSlug: slug,
11842
+ setupMode,
11843
+ appId: setupMode === "operator" ? settings.githubAppId ?? null : null,
11844
+ clientId: setupMode === "operator" ? settings.githubClientId ?? null : null,
11845
+ appSlug: setupMode === "operator" ? slug : null,
11697
11846
  installUrl: connectUrl,
11698
11847
  linkUrl: connectUrl,
11699
- installations,
11700
- missing
11848
+ installations: installationViews,
11849
+ missing: setupMode === "operator" ? missing : []
11701
11850
  });
11702
11851
  });
11703
11852
  app.get("/v1/workspaces/:workspaceId/github/connect", async (c) => {
@@ -11710,8 +11859,8 @@ function registerGitHubRoutes(app, deps) {
11710
11859
  if (!statePayload || statePayload.intent !== "installation_authority" || statePayload.workspaceId !== workspaceId || typeof statePayload.accountId !== "string" || !isFreshGitHubBindingState(statePayload)) {
11711
11860
  throw new HTTPException17(400, { message: "invalid or expired GitHub installation state" });
11712
11861
  }
11713
- const slug = settings.githubAppSlug?.trim();
11714
- if (!slug || githubAppMissingSettings2(settings).length > 0) {
11862
+ const clientId = settings.githubClientId?.trim();
11863
+ if (!clientId || githubAppMissingSettings2(settings).length > 0) {
11715
11864
  throw new HTTPException17(409, {
11716
11865
  message: JSON.stringify({
11717
11866
  message: "GitHub App is not configured",
@@ -11719,9 +11868,19 @@ function registerGitHubRoutes(app, deps) {
11719
11868
  })
11720
11869
  });
11721
11870
  }
11722
- setGitHubStateCookie(c, deps, state);
11871
+ const discoveryState = createSignedState5(githubStateSecret, {
11872
+ accountId: statePayload.accountId,
11873
+ workspaceId,
11874
+ intent: "installation_authority_discovery",
11875
+ ...continuedGitHubBrowserGrantClaims(statePayload)
11876
+ });
11877
+ setGitHubStateCookie(c, deps, discoveryState);
11723
11878
  return c.redirect(
11724
- `https://github.com/apps/${slug}/installations/new?state=${encodeURIComponent(state)}`
11879
+ githubOAuthAuthorizeUrl({
11880
+ clientId,
11881
+ state: discoveryState,
11882
+ redirectUri: `${openGeniBaseUrl(settings, c)}/v1/github/oauth/callback`
11883
+ })
11725
11884
  );
11726
11885
  });
11727
11886
  app.get("/v1/workspaces/:workspaceId/github/repositories", async (c) => {
@@ -11773,7 +11932,46 @@ function registerGitHubRoutes(app, deps) {
11773
11932
  }
11774
11933
  return c.body(null, 204);
11775
11934
  });
11935
+ app.get(
11936
+ "/v1/workspaces/:workspaceId/github/installations/:installationId/configure",
11937
+ async (c) => {
11938
+ const workspaceId = c.req.param("workspaceId");
11939
+ const installationId = parsePositiveInteger(c.req.param("installationId"));
11940
+ const state = c.req.query("state");
11941
+ if (installationId === null || !state) {
11942
+ throw new HTTPException17(400, { message: "invalid GitHub installation configuration" });
11943
+ }
11944
+ const statePayload = readSignedState4(state, githubStateSecret);
11945
+ if (!statePayload || statePayload.intent !== "installation_authority" || statePayload.workspaceId !== workspaceId || typeof statePayload.accountId !== "string" || !isFreshGitHubBindingState(statePayload)) {
11946
+ throw new HTTPException17(400, {
11947
+ message: "invalid or expired GitHub installation configuration state"
11948
+ });
11949
+ }
11950
+ const grant = await requireGitHubManageGrant(c, deps, workspaceId, statePayload);
11951
+ if (grant.accountId !== statePayload.accountId) {
11952
+ throw new HTTPException17(403, {
11953
+ message: "GitHub installation state does not match this workspace"
11954
+ });
11955
+ }
11956
+ const installation = (await listWorkspaceGitHubInstallationBindings(deps, grant.workspaceId)).find((candidate) => candidate.installationId === installationId);
11957
+ if (!installation) {
11958
+ throw new HTTPException17(404, { message: "GitHub installation binding not found" });
11959
+ }
11960
+ const configureState = createSignedState5(githubStateSecret, {
11961
+ accountId: grant.accountId,
11962
+ workspaceId: grant.workspaceId,
11963
+ expectedInstallationId: installationId,
11964
+ intent: "installation_authority_install",
11965
+ ...continuedGitHubBrowserGrantClaims(statePayload)
11966
+ });
11967
+ setGitHubStateCookie(c, deps, configureState);
11968
+ const configureUrl = githubInstallationSettingsUrl(installation);
11969
+ configureUrl.searchParams.set("state", configureState);
11970
+ return c.redirect(configureUrl.toString());
11971
+ }
11972
+ );
11776
11973
  app.post("/v1/workspaces/:workspaceId/github/app-manifest", async (c) => {
11974
+ assertOperatorGitHubAppSetup(settings);
11777
11975
  const workspaceId = c.req.param("workspaceId");
11778
11976
  const grant = await requireAccessGrant10(c, deps, workspaceId, "github:manage");
11779
11977
  const payload = GitHubAppManifestCreate.parse(await c.req.json());
@@ -11802,6 +12000,7 @@ function registerGitHubRoutes(app, deps) {
11802
12000
  });
11803
12001
  });
11804
12002
  app.get("/v1/github/app-manifest/callback", async (c) => {
12003
+ assertOperatorGitHubAppSetup(settings);
11805
12004
  const code = c.req.query("code");
11806
12005
  const state = c.req.query("state");
11807
12006
  if (!code) {
@@ -11821,12 +12020,15 @@ function registerGitHubRoutes(app, deps) {
11821
12020
  }
11822
12021
  });
11823
12022
  const handleGitHubInstallCallback = async (c) => {
11824
- const state = c.req.query("state");
12023
+ const state = c.req.query("state") ?? allCookieValues(c, githubStateCookie).find((candidate) => {
12024
+ const payload = readSignedState4(candidate, githubStateSecret);
12025
+ return payload?.intent === "installation_authority_install" && isFreshGitHubBindingState(payload);
12026
+ });
11825
12027
  if (!state) {
11826
12028
  throw new HTTPException17(400, { message: "missing GitHub installation state" });
11827
12029
  }
11828
12030
  const statePayload = readSignedState4(state, githubStateSecret);
11829
- if (!statePayload || statePayload.intent !== "installation_authority" || typeof statePayload.accountId !== "string" || typeof statePayload.workspaceId !== "string" || !isFreshGitHubBindingState(statePayload)) {
12031
+ if (!statePayload || statePayload.intent !== "installation_authority_install" || typeof statePayload.accountId !== "string" || typeof statePayload.workspaceId !== "string" || !isFreshGitHubBindingState(statePayload)) {
11830
12032
  throw new HTTPException17(400, { message: "invalid or expired GitHub installation state" });
11831
12033
  }
11832
12034
  requireGitHubStateCookie(c, state);
@@ -11847,6 +12049,11 @@ function registerGitHubRoutes(app, deps) {
11847
12049
  if (installationId === null) {
11848
12050
  throw new HTTPException17(400, { message: "missing or invalid GitHub installation_id" });
11849
12051
  }
12052
+ if (statePayload.expectedInstallationId !== void 0 && statePayload.expectedInstallationId !== installationId) {
12053
+ throw new HTTPException17(409, {
12054
+ message: "GitHub returned a different installation than the one being configured"
12055
+ });
12056
+ }
11850
12057
  const clientId = settings.githubClientId?.trim();
11851
12058
  if (!clientId) {
11852
12059
  throw new HTTPException17(409, {
@@ -11884,7 +12091,62 @@ function registerGitHubRoutes(app, deps) {
11884
12091
  throw new HTTPException17(400, { message: "missing GitHub OAuth state" });
11885
12092
  }
11886
12093
  const statePayload = readSignedState4(state, githubStateSecret);
11887
- if (!statePayload || statePayload.intent !== "installation_authority_oauth" || typeof statePayload.accountId !== "string" || typeof statePayload.workspaceId !== "string" || !isFreshGitHubBindingState(statePayload)) {
12094
+ if (!statePayload || typeof statePayload.accountId !== "string" || typeof statePayload.workspaceId !== "string" || !isFreshGitHubBindingState(statePayload)) {
12095
+ throw new HTTPException17(400, { message: "invalid or expired GitHub OAuth state" });
12096
+ }
12097
+ if (statePayload.intent === "installation_authority_discovery") {
12098
+ requireGitHubStateCookie(c, state);
12099
+ const grant2 = await requireGitHubManageGrant(c, deps, statePayload.workspaceId, statePayload);
12100
+ if (grant2.accountId !== statePayload.accountId) {
12101
+ throw new HTTPException17(403, {
12102
+ message: "GitHub OAuth state does not match this workspace"
12103
+ });
12104
+ }
12105
+ let candidates;
12106
+ try {
12107
+ candidates = deps.githubAppApi?.discoverInstallationBindingCandidates ? await deps.githubAppApi.discoverInstallationBindingCandidates({ code }) : deps.githubAppApi ? null : await discoverGitHubInstallationBindingCandidates(settings, { code });
12108
+ } catch (error) {
12109
+ throw githubAuthorityHttpError(error);
12110
+ }
12111
+ if (!candidates) {
12112
+ throw new HTTPException17(409, {
12113
+ message: "The configured GitHub provider cannot discover owner-authorized installations"
12114
+ });
12115
+ }
12116
+ if (!isConsistentGitHubBindingCandidates(candidates)) {
12117
+ throw new HTTPException17(409, {
12118
+ message: "GitHub installation discovery proof is stale or invalid"
12119
+ });
12120
+ }
12121
+ const selectionState = createSignedState5(githubStateSecret, {
12122
+ accountId: grant2.accountId,
12123
+ workspaceId: grant2.workspaceId,
12124
+ intent: "installation_authority_selection",
12125
+ allowedInstallationIds: candidates.map(({ installation }) => installation.installationId),
12126
+ ...continuedGitHubBrowserGrantClaims(statePayload)
12127
+ });
12128
+ if (candidates.length === 0) {
12129
+ return redirectToGitHubInstallation(c, deps, selectionState);
12130
+ }
12131
+ if (candidates.length === 1) {
12132
+ return redirectToExactGitHubAuthorization(
12133
+ c,
12134
+ deps,
12135
+ selectionState,
12136
+ candidates[0].installation.installationId
12137
+ );
12138
+ }
12139
+ setGitHubStateCookie(c, deps, selectionState);
12140
+ return c.html(
12141
+ githubInstallationChooserHtml(
12142
+ candidates,
12143
+ selectionState,
12144
+ grant2.workspaceId,
12145
+ openGeniBaseUrl(settings, c)
12146
+ )
12147
+ );
12148
+ }
12149
+ if (statePayload.intent !== "installation_authority_oauth") {
11888
12150
  throw new HTTPException17(400, { message: "invalid or expired GitHub OAuth state" });
11889
12151
  }
11890
12152
  const installationId = parsePositiveInteger(String(statePayload.installationId ?? ""));
@@ -11955,6 +12217,41 @@ function registerGitHubRoutes(app, deps) {
11955
12217
  )
11956
12218
  );
11957
12219
  });
12220
+ app.post("/v1/workspaces/:workspaceId/github/installations/select", async (c) => {
12221
+ const workspaceId = c.req.param("workspaceId");
12222
+ const form = new URLSearchParams(await c.req.text());
12223
+ const state = form.get("state");
12224
+ if (!state) {
12225
+ throw new HTTPException17(400, { message: "missing GitHub installation selection state" });
12226
+ }
12227
+ const statePayload = readSignedState4(state, githubStateSecret);
12228
+ if (!statePayload || statePayload.intent !== "installation_authority_selection" || typeof statePayload.accountId !== "string" || statePayload.accountId.length === 0 || statePayload.workspaceId !== workspaceId || !isFreshGitHubBindingState(statePayload)) {
12229
+ throw new HTTPException17(400, {
12230
+ message: "invalid or expired GitHub installation selection state"
12231
+ });
12232
+ }
12233
+ requireGitHubStateCookie(c, state);
12234
+ const grant = await requireGitHubManageGrant(c, deps, workspaceId, statePayload);
12235
+ if (grant.accountId !== statePayload.accountId) {
12236
+ throw new HTTPException17(403, {
12237
+ message: "GitHub installation state does not match this workspace"
12238
+ });
12239
+ }
12240
+ const selected = form.get("installation_id");
12241
+ if (selected === "new") {
12242
+ return redirectToGitHubInstallation(c, deps, state);
12243
+ }
12244
+ const installationId = parsePositiveInteger(selected);
12245
+ if (installationId === null) {
12246
+ throw new HTTPException17(400, { message: "invalid GitHub installation selection" });
12247
+ }
12248
+ if (!Array.isArray(statePayload.allowedInstallationIds) || !statePayload.allowedInstallationIds.includes(installationId)) {
12249
+ throw new HTTPException17(403, {
12250
+ message: "GitHub installation was not in the owner-authorized selection"
12251
+ });
12252
+ }
12253
+ return redirectToExactGitHubAuthorization(c, deps, state, installationId);
12254
+ });
11958
12255
  app.post("/v1/workspaces/:workspaceId/github/installations", async (c) => {
11959
12256
  const workspaceId = c.req.param("workspaceId");
11960
12257
  const form = new URLSearchParams(await c.req.text());
@@ -11966,11 +12263,62 @@ function registerGitHubRoutes(app, deps) {
11966
12263
  if (!statePayload || typeof statePayload.accountId !== "string" || statePayload.accountId.length === 0 || statePayload.workspaceId !== workspaceId) {
11967
12264
  throw new HTTPException17(400, { message: "invalid or expired GitHub OAuth state" });
11968
12265
  }
11969
- throw legacyInstallationChooserDisabled();
12266
+ throw new HTTPException17(410, { message: legacyInstallationChooserDisabledMessage });
12267
+ });
12268
+ }
12269
+ function assertOperatorGitHubAppSetup(settings) {
12270
+ if (settings.productAccessMode === "managed") {
12271
+ throw new HTTPException17(404, {
12272
+ message: "GitHub App creation is unavailable in platform-managed deployments"
12273
+ });
12274
+ }
12275
+ }
12276
+ function redirectToGitHubInstallation(c, deps, sourceState) {
12277
+ const payload = readSignedState4(sourceState, deps.githubStateSecret);
12278
+ const slug = deps.settings.githubAppSlug?.trim();
12279
+ if (!payload || typeof payload.accountId !== "string" || typeof payload.workspaceId !== "string" || !slug) {
12280
+ throw new HTTPException17(409, { message: "GitHub App installation is unavailable" });
12281
+ }
12282
+ const installState = createSignedState5(deps.githubStateSecret, {
12283
+ accountId: payload.accountId,
12284
+ workspaceId: payload.workspaceId,
12285
+ intent: "installation_authority_install",
12286
+ ...continuedGitHubBrowserGrantClaims(payload)
11970
12287
  });
12288
+ setGitHubStateCookie(c, deps, installState);
12289
+ return c.redirect(
12290
+ `https://github.com/apps/${encodeURIComponent(slug)}/installations/new?state=${encodeURIComponent(installState)}`
12291
+ );
12292
+ }
12293
+ function githubInstallationSettingsUrl(installation) {
12294
+ if (installation.accountType === "Organization" && installation.accountLogin) {
12295
+ return new URL(
12296
+ `https://github.com/organizations/${encodeURIComponent(installation.accountLogin)}/settings/installations/${installation.installationId}`
12297
+ );
12298
+ }
12299
+ return new URL(`https://github.com/settings/installations/${installation.installationId}`);
11971
12300
  }
11972
- function legacyInstallationChooserDisabled() {
11973
- return new HTTPException17(410, { message: legacyInstallationChooserDisabledMessage });
12301
+ function redirectToExactGitHubAuthorization(c, deps, sourceState, installationId) {
12302
+ const payload = readSignedState4(sourceState, deps.githubStateSecret);
12303
+ const clientId = deps.settings.githubClientId?.trim();
12304
+ if (!payload || typeof payload.accountId !== "string" || typeof payload.workspaceId !== "string" || !clientId) {
12305
+ throw new HTTPException17(409, { message: "GitHub user authorization is unavailable" });
12306
+ }
12307
+ const oauthState = createSignedState5(deps.githubStateSecret, {
12308
+ accountId: payload.accountId,
12309
+ workspaceId: payload.workspaceId,
12310
+ installationId,
12311
+ intent: "installation_authority_oauth",
12312
+ ...continuedGitHubBrowserGrantClaims(payload)
12313
+ });
12314
+ setGitHubStateCookie(c, deps, oauthState);
12315
+ return c.redirect(
12316
+ githubOAuthAuthorizeUrl({
12317
+ clientId,
12318
+ state: oauthState,
12319
+ redirectUri: `${openGeniBaseUrl(deps.settings, c)}/v1/github/oauth/callback`
12320
+ })
12321
+ );
11974
12322
  }
11975
12323
  function setGitHubStateCookie(c, deps, state) {
11976
12324
  setCookie(c, githubStateCookie, state, {
@@ -12047,9 +12395,30 @@ function githubSuccessHtml(envLines) {
12047
12395
  function githubSetupSuccessHtml(account, returnUrl) {
12048
12396
  return `<!doctype html><html><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>GitHub App Connected</title><style>body{font-family:system-ui,sans-serif;margin:0;min-height:100vh;display:grid;place-items:center;background:#0b0b0d;color:#f4f4f5}main{width:min(640px,calc(100vw - 32px));border:1px solid #27272a;border-radius:8px;padding:28px;background:#111114}h1{margin:0 0 10px;font-size:24px;line-height:1.2}p{margin:0 0 18px;color:#d4d4d8}.button{display:inline-flex;align-items:center;justify-content:center;min-height:36px;border-radius:6px;border:1px solid #3f3f46;padding:0 12px;background:#f4f4f5;color:#09090b;font:600 14px system-ui,sans-serif;text-decoration:none}</style></head><body><main><h1>GitHub App connected</h1><p>${escapeHtml2(account)} is now available to this OpenGeni workspace through an explicit repository allowlist.</p><a class="button" href="${escapeHtml2(returnUrl)}">Back to OpenGeni</a></main></body></html>`;
12049
12397
  }
12398
+ function githubInstallationChooserHtml(candidates, state, workspaceId, baseUrl) {
12399
+ const action = `${baseUrl}/v1/workspaces/${encodeURIComponent(workspaceId)}/github/installations/select`;
12400
+ const options = candidates.map(({ installation, authorityKind }) => {
12401
+ const account = escapeHtml2(
12402
+ installation.accountLogin ?? `installation ${installation.installationId}`
12403
+ );
12404
+ const label = authorityKind === "personal_owner" ? "Personal account" : "Organization owner";
12405
+ return `<label class="option"><input type="radio" name="installation_id" value="${installation.installationId}" required><span><strong>${account}</strong><small>${label}</small></span></label>`;
12406
+ }).join("");
12407
+ return `<!doctype html><html><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>Choose GitHub installation</title><style>body{font-family:system-ui,sans-serif;margin:0;min-height:100vh;display:grid;place-items:center;background:#0b0b0d;color:#f4f4f5}main{width:min(640px,calc(100vw - 32px));border:1px solid #27272a;border-radius:12px;padding:28px;background:#111114}h1{margin:0 0 10px;font-size:24px}p{margin:0 0 18px;color:#d4d4d8}.options{display:grid;gap:8px;margin-bottom:18px}.option{display:flex;align-items:center;gap:12px;border:1px solid #3f3f46;border-radius:8px;padding:12px;cursor:pointer}.option span{display:grid;gap:2px}.option small{color:#a1a1aa}button{min-height:38px;border-radius:7px;border:1px solid #3f3f46;padding:0 14px;background:#f4f4f5;color:#09090b;font:600 14px system-ui,sans-serif;cursor:pointer}.secondary{margin-left:8px;background:transparent;color:#f4f4f5}</style></head><body><main><h1>Choose a GitHub account</h1><p>Only installations where GitHub proved you are the personal owner or an active organization owner are shown.</p><form method="post" action="${escapeHtml2(action)}"><input type="hidden" name="state" value="${escapeHtml2(state)}"><div class="options">${options}</div><button type="submit">Connect selected</button><button class="secondary" type="submit" name="installation_id" value="new" formnovalidate>Install on another account</button></form></main></body></html>`;
12408
+ }
12050
12409
  function githubSetupPendingHtml() {
12051
12410
  return `<!doctype html><html><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>GitHub App Requested</title><style>body{font-family:system-ui,sans-serif;margin:0;min-height:100vh;display:grid;place-items:center;background:#0b0b0d;color:#f4f4f5}main{width:min(640px,calc(100vw - 32px));border:1px solid #27272a;border-radius:8px;padding:28px;background:#111114}h1{margin:0 0 10px;font-size:24px;line-height:1.2}p{margin:0;color:#d4d4d8}</style></head><body><main><h1>GitHub App request sent</h1><p>A GitHub organization owner must approve the installation. OpenGeni has not created a workspace binding.</p></main></body></html>`;
12052
12411
  }
12412
+ function isConsistentGitHubBindingCandidates(candidates) {
12413
+ const ids = /* @__PURE__ */ new Set();
12414
+ return candidates.every(({ installation, authorityKind }) => {
12415
+ if (!Number.isSafeInteger(installation.installationId) || installation.installationId <= 0 || !Number.isSafeInteger(installation.accountId) || installation.accountId <= 0 || !installation.accountLogin?.trim() || installation.suspended || ids.has(installation.installationId)) {
12416
+ return false;
12417
+ }
12418
+ ids.add(installation.installationId);
12419
+ return authorityKind === "personal_owner" ? installation.accountType === "User" : installation.accountType === "Organization";
12420
+ });
12421
+ }
12053
12422
  function parsePositiveInteger(value) {
12054
12423
  if (!value || !/^\d+$/.test(value)) {
12055
12424
  return null;
@@ -12618,7 +12987,8 @@ function registerScheduledTaskRoutes(app, deps) {
12618
12987
  await workflowClient.triggerScheduledTask({
12619
12988
  task,
12620
12989
  agentRunUsageIdempotencyKey,
12621
- triggerWorkflowId
12990
+ triggerWorkflowId,
12991
+ initiator: { kind: "subject", subjectId: grant.subjectId }
12622
12992
  });
12623
12993
  await recordWorkspaceUsage4(deps, {
12624
12994
  accountId: grant.accountId,
@@ -12750,6 +13120,7 @@ import {
12750
13120
  SessionContextBusyError,
12751
13121
  HumanInputResponseValidationError,
12752
13122
  latestWorkspaceCapture,
13123
+ sessionLatestWorkspaceCapture,
12753
13124
  workspaceCaptureAtRevision
12754
13125
  } from "@opengeni/db";
12755
13126
  import {
@@ -12758,7 +13129,7 @@ import {
12758
13129
  coalesceSessionEventDeltas,
12759
13130
  publishDurableSessionEvents as publishDurableSessionEvents2
12760
13131
  } from "@opengeni/events";
12761
- import { z as z3, ZodError } from "zod";
13132
+ import { z as z5, ZodError } from "zod";
12762
13133
 
12763
13134
  // src/sandbox/channel-a.ts
12764
13135
  import {
@@ -13560,11 +13931,69 @@ import { HTTPException as HTTPException21 } from "hono/http-exception";
13560
13931
  var CAPTURE_INLINE_MANIFEST_MAX_BYTES = 2 * 1024 * 1024;
13561
13932
  var CAPTURE_INLINE_FILE_MAX_BYTES = 256 * 1024;
13562
13933
  var CAPTURE_SIGNED_URL_TTL_SECONDS = 300;
13934
+ var WorkspaceCaptureManifestCache = class {
13935
+ constructor(maxEntries = 64, maxBytes = 16 * 1024 * 1024) {
13936
+ this.maxEntries = maxEntries;
13937
+ this.maxBytes = maxBytes;
13938
+ }
13939
+ #entries = /* @__PURE__ */ new Map();
13940
+ #retainedBytes = 0;
13941
+ get(row) {
13942
+ const key = row.manifestKey;
13943
+ if (!key) return null;
13944
+ const entry = this.#entries.get(key);
13945
+ if (!entry || entry.identity !== manifestIdentity(row)) return null;
13946
+ this.#entries.delete(key);
13947
+ this.#entries.set(key, entry);
13948
+ return entry;
13949
+ }
13950
+ getInlineResponse(row) {
13951
+ const key = row.manifestKey;
13952
+ if (!key) return null;
13953
+ const entry = this.#entries.get(key);
13954
+ if (!entry || entry.identity !== manifestIdentity(row) || !entry.inlineResponse) return null;
13955
+ this.#entries.delete(key);
13956
+ this.#entries.set(key, entry);
13957
+ return entry.inlineResponse;
13958
+ }
13959
+ setInlineResponse(row, response) {
13960
+ const key = row.manifestKey;
13961
+ if (!key) return;
13962
+ const entry = this.#entries.get(key);
13963
+ if (!entry || entry.identity !== manifestIdentity(row)) return;
13964
+ entry.inlineResponse = response;
13965
+ }
13966
+ set(row, loaded) {
13967
+ const key = row.manifestKey;
13968
+ if (!key || this.maxEntries <= 0 || this.maxBytes <= 0 || loaded.byteLength > CAPTURE_INLINE_MANIFEST_MAX_BYTES || loaded.byteLength > this.maxBytes) {
13969
+ return;
13970
+ }
13971
+ const previous = this.#entries.get(key);
13972
+ if (previous) {
13973
+ this.#retainedBytes -= previous.byteLength;
13974
+ this.#entries.delete(key);
13975
+ }
13976
+ this.#entries.set(key, { ...loaded, identity: manifestIdentity(row) });
13977
+ this.#retainedBytes += loaded.byteLength;
13978
+ while (this.#entries.size > this.maxEntries || this.#retainedBytes > this.maxBytes) {
13979
+ const oldestKey = this.#entries.keys().next().value;
13980
+ if (oldestKey === void 0) break;
13981
+ const oldest = this.#entries.get(oldestKey);
13982
+ this.#entries.delete(oldestKey);
13983
+ if (oldest) this.#retainedBytes -= oldest.byteLength;
13984
+ }
13985
+ }
13986
+ };
13987
+ function manifestIdentity(row) {
13988
+ return `${row.sessionId}\0${row.turnId}\0${row.revision}\0${row.leaseEpoch}\0${row.capturedAt}`;
13989
+ }
13563
13990
  function signedUrl(signed) {
13564
13991
  return { url: signed.url, expiresAt: signed.expiresAt.toISOString() };
13565
13992
  }
13566
- async function loadManifest(row, storage) {
13993
+ async function loadManifest(row, storage, cache) {
13567
13994
  if (!row.manifestKey) return null;
13995
+ const cached = cache?.get(row);
13996
+ if (cached) return cached;
13568
13997
  const stats = WorkspaceCaptureStats.safeParse(row.stats);
13569
13998
  if (!stats.success) {
13570
13999
  console.warn(
@@ -13599,9 +14028,11 @@ async function loadManifest(row, storage) {
13599
14028
  );
13600
14029
  return null;
13601
14030
  }
13602
- return { manifest, byteLength: blob.bytes.byteLength, stats: servedStats };
14031
+ const loaded = { manifest, byteLength: blob.bytes.byteLength, stats: servedStats };
14032
+ cache?.set(row, loaded);
14033
+ return loaded;
13603
14034
  }
13604
- async function serveWorkspaceCapture(row, storage) {
14035
+ async function serveWorkspaceCapture(row, storage, cache) {
13605
14036
  if (!row) {
13606
14037
  return { available: false };
13607
14038
  }
@@ -13620,7 +14051,9 @@ async function serveWorkspaceCapture(row, storage) {
13620
14051
  });
13621
14052
  }
13622
14053
  if (row.state !== "available" || !row.manifestKey) return { available: false };
13623
- const loaded = await loadManifest(row, storage);
14054
+ const cachedResponse = cache?.getInlineResponse(row);
14055
+ if (cachedResponse) return cachedResponse;
14056
+ const loaded = await loadManifest(row, storage, cache);
13624
14057
  if (!loaded) return { available: false };
13625
14058
  const meta = {
13626
14059
  available: true,
@@ -13632,11 +14065,16 @@ async function serveWorkspaceCapture(row, storage) {
13632
14065
  stats: loaded.stats
13633
14066
  };
13634
14067
  if (loaded.byteLength <= CAPTURE_INLINE_MANIFEST_MAX_BYTES) {
13635
- return GetWorkspaceCaptureResponse.parse({
14068
+ const response = GetWorkspaceCaptureResponse.parse({
13636
14069
  ...meta,
13637
14070
  manifest: loaded.manifest,
13638
14071
  manifestUrl: null
13639
14072
  });
14073
+ if (!response.available) {
14074
+ throw new Error("validated inline capture response lost its available discriminator");
14075
+ }
14076
+ cache?.setInlineResponse(row, response);
14077
+ return response;
13640
14078
  }
13641
14079
  const signed = await storage.createGetUrl({
13642
14080
  key: row.manifestKey,
@@ -13708,6 +14146,7 @@ async function serveWorkspaceCaptureFile(row, path, storage) {
13708
14146
  // src/routes/sessions.ts
13709
14147
  function registerSessionRoutes(app, deps) {
13710
14148
  const { settings, db, bus, workflowClient, objectStorage } = deps;
14149
+ const workspaceCaptureManifestCache = new WorkspaceCaptureManifestCache();
13711
14150
  const ptyIdentity = (pty) => ({
13712
14151
  leaseId: pty.leaseId,
13713
14152
  sandboxGroupId: pty.sandboxGroupId,
@@ -13962,7 +14401,7 @@ function registerSessionRoutes(app, deps) {
13962
14401
  const workspaceId = c.req.param("workspaceId");
13963
14402
  const grant = await requireAccessGrant14(c, deps, workspaceId, "sessions:read");
13964
14403
  const sessionId = c.req.param("sessionId");
13965
- if (!z3.string().uuid().safeParse(sessionId).success) {
14404
+ if (!z5.string().uuid().safeParse(sessionId).success) {
13966
14405
  throw new HTTPException22(404, { message: "session not found" });
13967
14406
  }
13968
14407
  const session = await getSessionForSubject(
@@ -13981,7 +14420,7 @@ function registerSessionRoutes(app, deps) {
13981
14420
  const workspaceId = c.req.param("workspaceId");
13982
14421
  const grant = await requireAccessGrant14(c, deps, workspaceId, "sessions:read");
13983
14422
  const sessionId = c.req.param("sessionId");
13984
- if (!z3.string().uuid().safeParse(sessionId).success) {
14423
+ if (!z5.string().uuid().safeParse(sessionId).success) {
13985
14424
  throw new HTTPException22(404, { message: "session not found" });
13986
14425
  }
13987
14426
  const parsed = UpdateSessionPinRequest.safeParse(await c.req.json().catch(() => null));
@@ -14671,8 +15110,6 @@ function registerSessionRoutes(app, deps) {
14671
15110
  text: payload.text,
14672
15111
  turnInstructions: payload.turnInstructions ?? null,
14673
15112
  resources: payload.resources,
14674
- tools: payload.tools,
14675
- toolsProvided: userMessagePayloadHasOwnProperty({ payload: raw }, "tools"),
14676
15113
  model: payload.model ?? null,
14677
15114
  reasoningEffort: payload.reasoningEffort ?? null,
14678
15115
  mcpCredentialUpdates: payload.mcpCredentialUpdates ?? [],
@@ -14707,8 +15144,6 @@ function registerSessionRoutes(app, deps) {
14707
15144
  text: event.payload.text,
14708
15145
  turnInstructions: event.payload.turnInstructions ?? null,
14709
15146
  resources: event.payload.resources ?? [],
14710
- tools: event.payload.tools ?? [],
14711
- toolsProvided: userMessagePayloadHasOwnProperty(rawEvent, "tools"),
14712
15147
  model: event.payload.model ?? null,
14713
15148
  reasoningEffort: event.payload.reasoningEffort ?? null,
14714
15149
  mcpCredentialUpdates: event.payload.mcpCredentialUpdates ?? [],
@@ -15305,15 +15740,16 @@ function registerSessionRoutes(app, deps) {
15305
15740
  const workspaceId = c.req.param("workspaceId") ?? "";
15306
15741
  await requireAccessGrant14(c, deps, workspaceId, "files:read");
15307
15742
  const sessionId = c.req.param("sessionId") ?? "";
15308
- const session = await getSession5(db, workspaceId, sessionId);
15309
- if (!session) {
15743
+ const lookup = await sessionLatestWorkspaceCapture(db, workspaceId, sessionId);
15744
+ if (!lookup.sessionExists) {
15310
15745
  throw new HTTPException22(404, { message: "session not found" });
15311
15746
  }
15312
15747
  if (!objectStorage) {
15313
15748
  return c.json({ available: false });
15314
15749
  }
15315
- const row = await latestWorkspaceCapture(db, workspaceId, sessionId);
15316
- return c.json(await serveWorkspaceCapture(row, objectStorage));
15750
+ return c.json(
15751
+ await serveWorkspaceCapture(lookup.capture, objectStorage, workspaceCaptureManifestCache)
15752
+ );
15317
15753
  });
15318
15754
  app.get("/v1/workspaces/:workspaceId/sessions/:sessionId/workspace/capture/file", async (c) => {
15319
15755
  const workspaceId = c.req.param("workspaceId") ?? "";
@@ -15681,7 +16117,7 @@ function eventEnumList(raw, schema, name) {
15681
16117
  }
15682
16118
  function sessionListQuery(query, allowCursor = true) {
15683
16119
  const parentSessionId = query.parentSessionId;
15684
- if (parentSessionId !== void 0 && parentSessionId !== "null" && !z3.string().uuid().safeParse(parentSessionId).success) {
16120
+ if (parentSessionId !== void 0 && parentSessionId !== "null" && !z5.string().uuid().safeParse(parentSessionId).success) {
15685
16121
  throw new HTTPException22(400, {
15686
16122
  message: 'parentSessionId must be a session id or the literal "null"'
15687
16123
  });
@@ -15737,18 +16173,6 @@ function optionalEventSequence(raw) {
15737
16173
  }
15738
16174
  return Math.floor(sequence);
15739
16175
  }
15740
- function hasOwnProperty(value, key) {
15741
- return Boolean(
15742
- value && typeof value === "object" && Object.prototype.hasOwnProperty.call(value, key)
15743
- );
15744
- }
15745
- function userMessagePayloadHasOwnProperty(value, key) {
15746
- if (!value || typeof value !== "object") {
15747
- return false;
15748
- }
15749
- const payload = value.payload;
15750
- return hasOwnProperty(payload, key);
15751
- }
15752
16176
  function sessionCreateErrorResponse(c, error) {
15753
16177
  if (error instanceof SessionSpawnDeniedError2) {
15754
16178
  return c.json(
@@ -15837,7 +16261,7 @@ import {
15837
16261
  listSocialPosts as listSocialPosts2
15838
16262
  } from "@opengeni/db";
15839
16263
  import { HTTPException as HTTPException23 } from "hono/http-exception";
15840
- import { z as z5 } from "zod";
16264
+ import { z as z6 } from "zod";
15841
16265
  import { requireAccessGrant as requireAccessGrant15 } from "@opengeni/core";
15842
16266
  function registerSocialRoutes(app, deps) {
15843
16267
  const { db } = deps;
@@ -15927,7 +16351,7 @@ function parseConnectionIds(raw) {
15927
16351
  return void 0;
15928
16352
  }
15929
16353
  const values = raw.split(",").map((value) => value.trim()).filter(Boolean);
15930
- const parsed = z5.array(z5.string().uuid()).safeParse(values);
16354
+ const parsed = z6.array(z6.string().uuid()).safeParse(values);
15931
16355
  if (!parsed.success) {
15932
16356
  throw new HTTPException23(422, {
15933
16357
  message: "connectionIds must be a comma-separated list of UUIDs"
@@ -15988,7 +16412,7 @@ import {
15988
16412
  } from "@opengeni/db";
15989
16413
  import { boundWorkspaceControlHttpPage } from "@opengeni/events";
15990
16414
  import { HTTPException as HTTPException24 } from "hono/http-exception";
15991
- import { hasPermission as hasPermission8, requireAccessContext as requireAccessContext2, requireAccessGrant as requireAccessGrant16 } from "@opengeni/core";
16415
+ import { hasPermission as hasPermission9, requireAccessContext as requireAccessContext2, requireAccessGrant as requireAccessGrant16 } from "@opengeni/core";
15992
16416
  import { requireLimit as requireLimit7 } from "@opengeni/core";
15993
16417
  import {
15994
16418
  assertWorkspaceDeletable,
@@ -16235,7 +16659,7 @@ function registerWorkspaceRoutes(app, deps) {
16235
16659
  const context = await requireAccessContext2(c, deps);
16236
16660
  const readableWorkspaceIds = [
16237
16661
  ...new Set(
16238
- context.workspaceGrants.filter((grant) => hasPermission8(grant.permissions, "workspace:read")).map((grant) => grant.workspaceId)
16662
+ context.workspaceGrants.filter((grant) => hasPermission9(grant.permissions, "workspace:read")).map((grant) => grant.workspaceId)
16239
16663
  )
16240
16664
  ];
16241
16665
  if (readableWorkspaceIds.length > 0) {
@@ -16524,8 +16948,8 @@ import {
16524
16948
  WorkspaceInstructionPolicyNotFoundError
16525
16949
  } from "@opengeni/db";
16526
16950
  import { HTTPException as HTTPException25 } from "hono/http-exception";
16527
- import { z as z6 } from "zod";
16528
- var WorkspaceInstructionPolicyRevisionId = z6.string().uuid();
16951
+ import { z as z7 } from "zod";
16952
+ var WorkspaceInstructionPolicyRevisionId = z7.string().uuid();
16529
16953
  async function parseBody(context, schema) {
16530
16954
  const parsed = schema.safeParse(await context.req.json().catch(() => null));
16531
16955
  if (!parsed.success) {
@@ -17025,7 +17449,7 @@ function createApp(deps) {
17025
17449
  }
17026
17450
  const workspace = await getWorkspace2(routeDeps.db, workspaceId);
17027
17451
  const workspaceMemoryEnabled = resolveWorkspaceMemoryEnabled(workspace?.settings);
17028
- const transport = new WebStandardStreamableHTTPServerTransport2({
17452
+ const transport = new WebStandardStreamableHTTPServerTransport3({
17029
17453
  enableJsonResponse: true
17030
17454
  });
17031
17455
  const mcp = buildOpenGeniMcpServer(routeDeps, grant, {
@@ -17099,7 +17523,7 @@ function createApp(deps) {
17099
17523
  }
17100
17524
  async function requireMcpAccessGrant(c, deps, workspaceId) {
17101
17525
  const grant = await requireAccessGrant18(c, deps, workspaceId);
17102
- if (hasPermission9(grant.permissions, "workspace:read")) {
17526
+ if (hasPermission10(grant.permissions, "workspace:read")) {
17103
17527
  return grant;
17104
17528
  }
17105
17529
  if (isToolspaceGrant(deps.settings, grant)) {
@@ -17280,6 +17704,10 @@ var routeLabelPatterns = [
17280
17704
  pattern: /^\/v1\/workspaces\/[^/]+\/mcp\/docs$/,
17281
17705
  label: "/v1/workspaces/:workspaceId/mcp/docs"
17282
17706
  },
17707
+ {
17708
+ pattern: /^\/v1\/workspaces\/[^/]+\/mcp\/files$/,
17709
+ label: "/v1/workspaces/:workspaceId/mcp/files"
17710
+ },
17283
17711
  {
17284
17712
  pattern: /^\/v1\/workspaces\/[^/]+\/default-rig$/,
17285
17713
  label: "/v1/workspaces/:workspaceId/default-rig"
@@ -17460,6 +17888,14 @@ var routeLabelPatterns = [
17460
17888
  pattern: /^\/v1\/workspaces\/[^/]+\/github\/connect$/,
17461
17889
  label: "/v1/workspaces/:workspaceId/github/connect"
17462
17890
  },
17891
+ {
17892
+ pattern: /^\/v1\/workspaces\/[^/]+\/github\/installations\/select$/,
17893
+ label: "/v1/workspaces/:workspaceId/github/installations/select"
17894
+ },
17895
+ {
17896
+ pattern: /^\/v1\/workspaces\/[^/]+\/github\/installations\/[^/]+\/configure$/,
17897
+ label: "/v1/workspaces/:workspaceId/github/installations/:installationId/configure"
17898
+ },
17463
17899
  {
17464
17900
  pattern: /^\/v1\/workspaces\/[^/]+\/github\/installations$/,
17465
17901
  label: "/v1/workspaces/:workspaceId/github/installations"
@@ -17641,4 +18077,4 @@ export {
17641
18077
  withDefaultEnabledCapabilityMcpTools,
17642
18078
  workflowIdForSession2 as workflowIdForSession
17643
18079
  };
17644
- //# sourceMappingURL=chunk-3BHOMOSD.js.map
18080
+ //# sourceMappingURL=chunk-5TULDPWX.js.map