@opengeni/api-router 0.12.7 → 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.
@@ -421,6 +421,7 @@ async function listWorkspaceGitHubInstallationBindings(deps, workspaceId) {
421
421
  ),
422
422
  repositoryScope: installation.repositoryScope,
423
423
  repositoryCount: installation.repositoryIds.length,
424
+ configureUrl: null,
424
425
  createdAt: installation.createdAt,
425
426
  updatedAt: installation.updatedAt
426
427
  }));
@@ -3732,7 +3733,7 @@ function registerRigTools(server, deps, grant, can, sessionId, json) {
3732
3733
  );
3733
3734
  }
3734
3735
  }
3735
- function exactAgentCommandContext(grant, callerSessionId) {
3736
+ function exactAgentCommandContext(grant, callerSessionId, authorizationSurface) {
3736
3737
  const turnId = grant.metadata?.["turnId"];
3737
3738
  const attemptId = grant.metadata?.["attemptId"];
3738
3739
  const executionGeneration = grant.metadata?.["executionGeneration"];
@@ -3746,7 +3747,8 @@ function exactAgentCommandContext(grant, callerSessionId) {
3746
3747
  callerSessionId,
3747
3748
  callerTurnId: turnId,
3748
3749
  callerAttemptId: attemptId,
3749
- callerExecutionGeneration: executionGeneration
3750
+ callerExecutionGeneration: executionGeneration,
3751
+ ...authorizationSurface ? { authorizationSurface } : {}
3750
3752
  };
3751
3753
  }
3752
3754
  function registerWorkspaceOrchestrationTools(server, deps, grant, can, callerSessionId, toolspaceMode, json) {
@@ -4064,7 +4066,6 @@ function registerWorkspaceOrchestrationTools(server, deps, grant, can, callerSes
4064
4066
  targetSessionId,
4065
4067
  {
4066
4068
  text,
4067
- toolsProvided: false,
4068
4069
  delivery: "send",
4069
4070
  origin: "operator",
4070
4071
  clientEventId: idempotencyKey,
@@ -4087,16 +4088,10 @@ function registerWorkspaceOrchestrationTools(server, deps, grant, can, callerSes
4087
4088
  }
4088
4089
  },
4089
4090
  async ({ sessionId, idempotencyKey, reason }) => {
4090
- const authorization = await authorizeFirstPartySession(
4091
- deps,
4092
- grant,
4093
- sessionId,
4094
- "session.control"
4095
- );
4096
4091
  if (callerSessionId !== null) {
4097
4092
  const controlled2 = await controlAgentSessionWorkstream(
4098
4093
  deps,
4099
- exactAgentCommandContext(grant, callerSessionId),
4094
+ exactAgentCommandContext(grant, callerSessionId, "first_party_mcp"),
4100
4095
  {
4101
4096
  targetSessionId: sessionId,
4102
4097
  action: "pause",
@@ -4109,7 +4104,7 @@ function registerWorkspaceOrchestrationTools(server, deps, grant, can, callerSes
4109
4104
  effectiveControl: projectEffectiveControlForRelatedAccess(
4110
4105
  serializeEffectiveSessionControl(controlled2.control),
4111
4106
  sessionId,
4112
- authorization?.relatedSessionAccess ?? "root"
4107
+ controlled2.authorization?.relatedSessionAccess ?? "root"
4113
4108
  ),
4114
4109
  interruptionCount: controlled2.interruptionCount,
4115
4110
  replay: controlled2.replay
@@ -4121,7 +4116,8 @@ function registerWorkspaceOrchestrationTools(server, deps, grant, can, callerSes
4121
4116
  accountId: grant.accountId,
4122
4117
  workspaceId: grant.workspaceId,
4123
4118
  sessionId,
4124
- subjectId: grant.subjectId
4119
+ subjectId: grant.subjectId,
4120
+ authorizationSurface: "first_party_mcp"
4125
4121
  },
4126
4122
  {
4127
4123
  action: "pause",
@@ -4129,14 +4125,7 @@ function registerWorkspaceOrchestrationTools(server, deps, grant, can, callerSes
4129
4125
  ...reason ? { reason } : {}
4130
4126
  }
4131
4127
  );
4132
- return json({
4133
- ...controlled,
4134
- effectiveControl: projectEffectiveControlForRelatedAccess(
4135
- controlled.effectiveControl,
4136
- sessionId,
4137
- authorization?.relatedSessionAccess ?? "root"
4138
- )
4139
- });
4128
+ return json(controlled);
4140
4129
  }
4141
4130
  );
4142
4131
  server.registerTool(
@@ -4150,16 +4139,10 @@ function registerWorkspaceOrchestrationTools(server, deps, grant, can, callerSes
4150
4139
  }
4151
4140
  },
4152
4141
  async ({ sessionId, idempotencyKey, reason }) => {
4153
- const authorization = await authorizeFirstPartySession(
4154
- deps,
4155
- grant,
4156
- sessionId,
4157
- "session.control"
4158
- );
4159
4142
  if (callerSessionId !== null) {
4160
4143
  const controlled2 = await controlAgentSessionWorkstream(
4161
4144
  deps,
4162
- exactAgentCommandContext(grant, callerSessionId),
4145
+ exactAgentCommandContext(grant, callerSessionId, "first_party_mcp"),
4163
4146
  {
4164
4147
  targetSessionId: sessionId,
4165
4148
  action: "resume",
@@ -4172,7 +4155,7 @@ function registerWorkspaceOrchestrationTools(server, deps, grant, can, callerSes
4172
4155
  effectiveControl: projectEffectiveControlForRelatedAccess(
4173
4156
  serializeEffectiveSessionControl(controlled2.control),
4174
4157
  sessionId,
4175
- authorization?.relatedSessionAccess ?? "root"
4158
+ controlled2.authorization?.relatedSessionAccess ?? "root"
4176
4159
  ),
4177
4160
  interruptionCount: controlled2.interruptionCount,
4178
4161
  replay: controlled2.replay
@@ -4184,7 +4167,8 @@ function registerWorkspaceOrchestrationTools(server, deps, grant, can, callerSes
4184
4167
  accountId: grant.accountId,
4185
4168
  workspaceId: grant.workspaceId,
4186
4169
  sessionId,
4187
- subjectId: grant.subjectId
4170
+ subjectId: grant.subjectId,
4171
+ authorizationSurface: "first_party_mcp"
4188
4172
  },
4189
4173
  {
4190
4174
  action: "resume",
@@ -4192,14 +4176,7 @@ function registerWorkspaceOrchestrationTools(server, deps, grant, can, callerSes
4192
4176
  ...reason ? { reason } : {}
4193
4177
  }
4194
4178
  );
4195
- return json({
4196
- ...controlled,
4197
- effectiveControl: projectEffectiveControlForRelatedAccess(
4198
- controlled.effectiveControl,
4199
- sessionId,
4200
- authorization?.relatedSessionAccess ?? "root"
4201
- )
4202
- });
4179
+ return json(controlled);
4203
4180
  }
4204
4181
  );
4205
4182
  if (callerSessionId !== null) {
@@ -4214,10 +4191,9 @@ function registerWorkspaceOrchestrationTools(server, deps, grant, can, callerSes
4214
4191
  }
4215
4192
  },
4216
4193
  async ({ sessionId, instruction, idempotencyKey }) => {
4217
- await authorizeFirstPartySession(deps, grant, sessionId, "session.steer");
4218
4194
  const result = await steerAgentSession(
4219
4195
  deps,
4220
- exactAgentCommandContext(grant, callerSessionId),
4196
+ exactAgentCommandContext(grant, callerSessionId, "first_party_mcp"),
4221
4197
  { targetSessionId: sessionId, instruction, idempotencyKey }
4222
4198
  );
4223
4199
  return json({
@@ -4391,14 +4367,16 @@ function registerGitHubConnectTool(server, deps, grant, options, json) {
4391
4367
  const { settings } = deps;
4392
4368
  const missing = githubAppMissingSettings(settings);
4393
4369
  const slug = settings.githubAppSlug?.trim() || null;
4370
+ const setupMode = settings.productAccessMode === "managed" ? "platform" : "operator";
4394
4371
  if (missing.length > 0 || !slug) {
4395
4372
  return json({
4396
4373
  configured: false,
4397
4374
  status: "disabled",
4398
- appSlug: slug,
4375
+ setupMode,
4376
+ appSlug: setupMode === "operator" ? slug : null,
4399
4377
  installUrl: null,
4400
4378
  linkUrl: null,
4401
- missing
4379
+ missing: setupMode === "operator" ? missing : []
4402
4380
  });
4403
4381
  }
4404
4382
  const installations = await listWorkspaceGitHubInstallationBindings(deps, grant.workspaceId);
@@ -4411,13 +4389,18 @@ function registerGitHubConnectTool(server, deps, grant, options, json) {
4411
4389
  ...githubBrowserGrantClaims(settings, grant)
4412
4390
  }) : null;
4413
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
+ }));
4414
4396
  return json({
4415
4397
  configured: true,
4416
4398
  status,
4417
- appSlug: slug,
4399
+ setupMode,
4400
+ appSlug: setupMode === "operator" ? slug : null,
4418
4401
  installUrl: connectUrl,
4419
4402
  linkUrl: connectUrl,
4420
- installations,
4403
+ installations: installationViews,
4421
4404
  missing: []
4422
4405
  });
4423
4406
  }
@@ -11812,6 +11795,7 @@ import {
11812
11795
  buildGitHubAppManifest,
11813
11796
  convertGitHubAppManifest,
11814
11797
  createSignedState as createSignedState5,
11798
+ discoverGitHubInstallationBindingCandidates,
11815
11799
  envLinesFromGitHubManifestConversion,
11816
11800
  GitHubAppApiError,
11817
11801
  GitHubAppConfigurationError as GitHubAppConfigurationError2,
@@ -11839,6 +11823,7 @@ function registerGitHubRoutes(app, deps) {
11839
11823
  const slug = settings.githubAppSlug?.trim() || null;
11840
11824
  const installations = missing.length === 0 ? await listWorkspaceGitHubInstallationBindings(deps, grant.workspaceId) : [];
11841
11825
  const status = githubBindingStatus(missing.length === 0, installations);
11826
+ const setupMode = settings.productAccessMode === "managed" ? "platform" : "operator";
11842
11827
  const canManage = hasPermission8(grant.permissions, "github:manage");
11843
11828
  const connectState = missing.length === 0 && slug && canManage ? createSignedState5(githubStateSecret, {
11844
11829
  accountId: grant.accountId,
@@ -11847,16 +11832,21 @@ function registerGitHubRoutes(app, deps) {
11847
11832
  ...githubBrowserGrantClaims(settings, grant)
11848
11833
  }) : null;
11849
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
+ }));
11850
11839
  return c.json({
11851
11840
  configured: missing.length === 0,
11852
11841
  status,
11853
- appId: settings.githubAppId ?? null,
11854
- clientId: settings.githubClientId ?? null,
11855
- 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,
11856
11846
  installUrl: connectUrl,
11857
11847
  linkUrl: connectUrl,
11858
- installations,
11859
- missing
11848
+ installations: installationViews,
11849
+ missing: setupMode === "operator" ? missing : []
11860
11850
  });
11861
11851
  });
11862
11852
  app.get("/v1/workspaces/:workspaceId/github/connect", async (c) => {
@@ -11869,8 +11859,8 @@ function registerGitHubRoutes(app, deps) {
11869
11859
  if (!statePayload || statePayload.intent !== "installation_authority" || statePayload.workspaceId !== workspaceId || typeof statePayload.accountId !== "string" || !isFreshGitHubBindingState(statePayload)) {
11870
11860
  throw new HTTPException17(400, { message: "invalid or expired GitHub installation state" });
11871
11861
  }
11872
- const slug = settings.githubAppSlug?.trim();
11873
- if (!slug || githubAppMissingSettings2(settings).length > 0) {
11862
+ const clientId = settings.githubClientId?.trim();
11863
+ if (!clientId || githubAppMissingSettings2(settings).length > 0) {
11874
11864
  throw new HTTPException17(409, {
11875
11865
  message: JSON.stringify({
11876
11866
  message: "GitHub App is not configured",
@@ -11878,9 +11868,19 @@ function registerGitHubRoutes(app, deps) {
11878
11868
  })
11879
11869
  });
11880
11870
  }
11881
- 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);
11882
11878
  return c.redirect(
11883
- `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
+ })
11884
11884
  );
11885
11885
  });
11886
11886
  app.get("/v1/workspaces/:workspaceId/github/repositories", async (c) => {
@@ -11932,7 +11932,46 @@ function registerGitHubRoutes(app, deps) {
11932
11932
  }
11933
11933
  return c.body(null, 204);
11934
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
+ );
11935
11973
  app.post("/v1/workspaces/:workspaceId/github/app-manifest", async (c) => {
11974
+ assertOperatorGitHubAppSetup(settings);
11936
11975
  const workspaceId = c.req.param("workspaceId");
11937
11976
  const grant = await requireAccessGrant10(c, deps, workspaceId, "github:manage");
11938
11977
  const payload = GitHubAppManifestCreate.parse(await c.req.json());
@@ -11961,6 +12000,7 @@ function registerGitHubRoutes(app, deps) {
11961
12000
  });
11962
12001
  });
11963
12002
  app.get("/v1/github/app-manifest/callback", async (c) => {
12003
+ assertOperatorGitHubAppSetup(settings);
11964
12004
  const code = c.req.query("code");
11965
12005
  const state = c.req.query("state");
11966
12006
  if (!code) {
@@ -11980,12 +12020,15 @@ function registerGitHubRoutes(app, deps) {
11980
12020
  }
11981
12021
  });
11982
12022
  const handleGitHubInstallCallback = async (c) => {
11983
- 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
+ });
11984
12027
  if (!state) {
11985
12028
  throw new HTTPException17(400, { message: "missing GitHub installation state" });
11986
12029
  }
11987
12030
  const statePayload = readSignedState4(state, githubStateSecret);
11988
- 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)) {
11989
12032
  throw new HTTPException17(400, { message: "invalid or expired GitHub installation state" });
11990
12033
  }
11991
12034
  requireGitHubStateCookie(c, state);
@@ -12006,6 +12049,11 @@ function registerGitHubRoutes(app, deps) {
12006
12049
  if (installationId === null) {
12007
12050
  throw new HTTPException17(400, { message: "missing or invalid GitHub installation_id" });
12008
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
+ }
12009
12057
  const clientId = settings.githubClientId?.trim();
12010
12058
  if (!clientId) {
12011
12059
  throw new HTTPException17(409, {
@@ -12043,7 +12091,62 @@ function registerGitHubRoutes(app, deps) {
12043
12091
  throw new HTTPException17(400, { message: "missing GitHub OAuth state" });
12044
12092
  }
12045
12093
  const statePayload = readSignedState4(state, githubStateSecret);
12046
- 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") {
12047
12150
  throw new HTTPException17(400, { message: "invalid or expired GitHub OAuth state" });
12048
12151
  }
12049
12152
  const installationId = parsePositiveInteger(String(statePayload.installationId ?? ""));
@@ -12114,6 +12217,41 @@ function registerGitHubRoutes(app, deps) {
12114
12217
  )
12115
12218
  );
12116
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
+ });
12117
12255
  app.post("/v1/workspaces/:workspaceId/github/installations", async (c) => {
12118
12256
  const workspaceId = c.req.param("workspaceId");
12119
12257
  const form = new URLSearchParams(await c.req.text());
@@ -12125,11 +12263,62 @@ function registerGitHubRoutes(app, deps) {
12125
12263
  if (!statePayload || typeof statePayload.accountId !== "string" || statePayload.accountId.length === 0 || statePayload.workspaceId !== workspaceId) {
12126
12264
  throw new HTTPException17(400, { message: "invalid or expired GitHub OAuth state" });
12127
12265
  }
12128
- 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)
12129
12287
  });
12288
+ setGitHubStateCookie(c, deps, installState);
12289
+ return c.redirect(
12290
+ `https://github.com/apps/${encodeURIComponent(slug)}/installations/new?state=${encodeURIComponent(installState)}`
12291
+ );
12130
12292
  }
12131
- function legacyInstallationChooserDisabled() {
12132
- return new HTTPException17(410, { message: legacyInstallationChooserDisabledMessage });
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}`);
12300
+ }
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
+ );
12133
12322
  }
12134
12323
  function setGitHubStateCookie(c, deps, state) {
12135
12324
  setCookie(c, githubStateCookie, state, {
@@ -12206,9 +12395,30 @@ function githubSuccessHtml(envLines) {
12206
12395
  function githubSetupSuccessHtml(account, returnUrl) {
12207
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>`;
12208
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
+ }
12209
12409
  function githubSetupPendingHtml() {
12210
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>`;
12211
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
+ }
12212
12422
  function parsePositiveInteger(value) {
12213
12423
  if (!value || !/^\d+$/.test(value)) {
12214
12424
  return null;
@@ -12910,6 +13120,7 @@ import {
12910
13120
  SessionContextBusyError,
12911
13121
  HumanInputResponseValidationError,
12912
13122
  latestWorkspaceCapture,
13123
+ sessionLatestWorkspaceCapture,
12913
13124
  workspaceCaptureAtRevision
12914
13125
  } from "@opengeni/db";
12915
13126
  import {
@@ -13720,11 +13931,69 @@ import { HTTPException as HTTPException21 } from "hono/http-exception";
13720
13931
  var CAPTURE_INLINE_MANIFEST_MAX_BYTES = 2 * 1024 * 1024;
13721
13932
  var CAPTURE_INLINE_FILE_MAX_BYTES = 256 * 1024;
13722
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
+ }
13723
13990
  function signedUrl(signed) {
13724
13991
  return { url: signed.url, expiresAt: signed.expiresAt.toISOString() };
13725
13992
  }
13726
- async function loadManifest(row, storage) {
13993
+ async function loadManifest(row, storage, cache) {
13727
13994
  if (!row.manifestKey) return null;
13995
+ const cached = cache?.get(row);
13996
+ if (cached) return cached;
13728
13997
  const stats = WorkspaceCaptureStats.safeParse(row.stats);
13729
13998
  if (!stats.success) {
13730
13999
  console.warn(
@@ -13759,9 +14028,11 @@ async function loadManifest(row, storage) {
13759
14028
  );
13760
14029
  return null;
13761
14030
  }
13762
- 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;
13763
14034
  }
13764
- async function serveWorkspaceCapture(row, storage) {
14035
+ async function serveWorkspaceCapture(row, storage, cache) {
13765
14036
  if (!row) {
13766
14037
  return { available: false };
13767
14038
  }
@@ -13780,7 +14051,9 @@ async function serveWorkspaceCapture(row, storage) {
13780
14051
  });
13781
14052
  }
13782
14053
  if (row.state !== "available" || !row.manifestKey) return { available: false };
13783
- 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);
13784
14057
  if (!loaded) return { available: false };
13785
14058
  const meta = {
13786
14059
  available: true,
@@ -13792,11 +14065,16 @@ async function serveWorkspaceCapture(row, storage) {
13792
14065
  stats: loaded.stats
13793
14066
  };
13794
14067
  if (loaded.byteLength <= CAPTURE_INLINE_MANIFEST_MAX_BYTES) {
13795
- return GetWorkspaceCaptureResponse.parse({
14068
+ const response = GetWorkspaceCaptureResponse.parse({
13796
14069
  ...meta,
13797
14070
  manifest: loaded.manifest,
13798
14071
  manifestUrl: null
13799
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;
13800
14078
  }
13801
14079
  const signed = await storage.createGetUrl({
13802
14080
  key: row.manifestKey,
@@ -13868,6 +14146,7 @@ async function serveWorkspaceCaptureFile(row, path, storage) {
13868
14146
  // src/routes/sessions.ts
13869
14147
  function registerSessionRoutes(app, deps) {
13870
14148
  const { settings, db, bus, workflowClient, objectStorage } = deps;
14149
+ const workspaceCaptureManifestCache = new WorkspaceCaptureManifestCache();
13871
14150
  const ptyIdentity = (pty) => ({
13872
14151
  leaseId: pty.leaseId,
13873
14152
  sandboxGroupId: pty.sandboxGroupId,
@@ -14831,8 +15110,6 @@ function registerSessionRoutes(app, deps) {
14831
15110
  text: payload.text,
14832
15111
  turnInstructions: payload.turnInstructions ?? null,
14833
15112
  resources: payload.resources,
14834
- tools: payload.tools,
14835
- toolsProvided: userMessagePayloadHasOwnProperty({ payload: raw }, "tools"),
14836
15113
  model: payload.model ?? null,
14837
15114
  reasoningEffort: payload.reasoningEffort ?? null,
14838
15115
  mcpCredentialUpdates: payload.mcpCredentialUpdates ?? [],
@@ -14867,8 +15144,6 @@ function registerSessionRoutes(app, deps) {
14867
15144
  text: event.payload.text,
14868
15145
  turnInstructions: event.payload.turnInstructions ?? null,
14869
15146
  resources: event.payload.resources ?? [],
14870
- tools: event.payload.tools ?? [],
14871
- toolsProvided: userMessagePayloadHasOwnProperty(rawEvent, "tools"),
14872
15147
  model: event.payload.model ?? null,
14873
15148
  reasoningEffort: event.payload.reasoningEffort ?? null,
14874
15149
  mcpCredentialUpdates: event.payload.mcpCredentialUpdates ?? [],
@@ -15465,15 +15740,16 @@ function registerSessionRoutes(app, deps) {
15465
15740
  const workspaceId = c.req.param("workspaceId") ?? "";
15466
15741
  await requireAccessGrant14(c, deps, workspaceId, "files:read");
15467
15742
  const sessionId = c.req.param("sessionId") ?? "";
15468
- const session = await getSession5(db, workspaceId, sessionId);
15469
- if (!session) {
15743
+ const lookup = await sessionLatestWorkspaceCapture(db, workspaceId, sessionId);
15744
+ if (!lookup.sessionExists) {
15470
15745
  throw new HTTPException22(404, { message: "session not found" });
15471
15746
  }
15472
15747
  if (!objectStorage) {
15473
15748
  return c.json({ available: false });
15474
15749
  }
15475
- const row = await latestWorkspaceCapture(db, workspaceId, sessionId);
15476
- return c.json(await serveWorkspaceCapture(row, objectStorage));
15750
+ return c.json(
15751
+ await serveWorkspaceCapture(lookup.capture, objectStorage, workspaceCaptureManifestCache)
15752
+ );
15477
15753
  });
15478
15754
  app.get("/v1/workspaces/:workspaceId/sessions/:sessionId/workspace/capture/file", async (c) => {
15479
15755
  const workspaceId = c.req.param("workspaceId") ?? "";
@@ -15897,18 +16173,6 @@ function optionalEventSequence(raw) {
15897
16173
  }
15898
16174
  return Math.floor(sequence);
15899
16175
  }
15900
- function hasOwnProperty(value, key) {
15901
- return Boolean(
15902
- value && typeof value === "object" && Object.prototype.hasOwnProperty.call(value, key)
15903
- );
15904
- }
15905
- function userMessagePayloadHasOwnProperty(value, key) {
15906
- if (!value || typeof value !== "object") {
15907
- return false;
15908
- }
15909
- const payload = value.payload;
15910
- return hasOwnProperty(payload, key);
15911
- }
15912
16176
  function sessionCreateErrorResponse(c, error) {
15913
16177
  if (error instanceof SessionSpawnDeniedError2) {
15914
16178
  return c.json(
@@ -17624,6 +17888,14 @@ var routeLabelPatterns = [
17624
17888
  pattern: /^\/v1\/workspaces\/[^/]+\/github\/connect$/,
17625
17889
  label: "/v1/workspaces/:workspaceId/github/connect"
17626
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
+ },
17627
17899
  {
17628
17900
  pattern: /^\/v1\/workspaces\/[^/]+\/github\/installations$/,
17629
17901
  label: "/v1/workspaces/:workspaceId/github/installations"
@@ -17805,4 +18077,4 @@ export {
17805
18077
  withDefaultEnabledCapabilityMcpTools,
17806
18078
  workflowIdForSession2 as workflowIdForSession
17807
18079
  };
17808
- //# sourceMappingURL=chunk-SD5GJTZ4.js.map
18080
+ //# sourceMappingURL=chunk-5TULDPWX.js.map