@opengeni/api-router 0.12.7 → 0.14.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (89) hide show
  1. package/dist/app.d.ts +18 -38
  2. package/dist/app.js +3 -1
  3. package/dist/auth/managed-auth.d.ts +35 -0
  4. package/dist/{chunk-SD5GJTZ4.js → chunk-36PW33ND.js} +3744 -638
  5. package/dist/chunk-36PW33ND.js.map +1 -0
  6. package/dist/codex-redemption-security.d.ts +15 -0
  7. package/dist/github-access.d.ts +12 -0
  8. package/dist/github-browser-flow.d.ts +14 -0
  9. package/dist/http/auth.d.ts +3 -0
  10. package/dist/http/common.d.ts +3 -0
  11. package/dist/http/sse.d.ts +70 -0
  12. package/dist/index.d.ts +11 -13
  13. package/dist/index.js +1 -1
  14. package/dist/integrations/google-drive.d.ts +76 -0
  15. package/dist/integrations/oauth-client.d.ts +42 -0
  16. package/dist/integrations/provider-domain.d.ts +7 -0
  17. package/dist/integrations/slack-bot.d.ts +343 -0
  18. package/dist/mcp/documents.d.ts +8 -0
  19. package/dist/mcp/files.d.ts +5 -0
  20. package/dist/mcp/server.d.ts +107 -0
  21. package/dist/mcp/session-view.d.ts +191 -0
  22. package/dist/mcp/toolspace.d.ts +61 -0
  23. package/dist/model-catalog.d.ts +39 -0
  24. package/dist/observability.d.ts +3 -0
  25. package/dist/routes/api-keys.d.ts +3 -0
  26. package/dist/routes/billing.d.ts +16 -0
  27. package/dist/routes/capabilities.d.ts +3 -0
  28. package/dist/routes/catalog-assets.d.ts +5 -0
  29. package/dist/routes/codex.d.ts +10 -0
  30. package/dist/routes/connections.d.ts +3 -0
  31. package/dist/routes/documents.d.ts +3 -0
  32. package/dist/routes/enrollments.d.ts +3 -0
  33. package/dist/routes/environments.d.ts +5 -0
  34. package/dist/routes/files.d.ts +4 -0
  35. package/dist/routes/github.d.ts +3 -0
  36. package/dist/routes/insights.d.ts +3 -0
  37. package/dist/routes/install.d.ts +5 -0
  38. package/dist/routes/machines.d.ts +3 -0
  39. package/dist/routes/packs.d.ts +3 -0
  40. package/dist/routes/preference-registry.d.ts +5 -0
  41. package/dist/routes/rigs.d.ts +3 -0
  42. package/dist/routes/scheduled-tasks.d.ts +3 -0
  43. package/dist/routes/sessions.d.ts +15 -0
  44. package/dist/routes/social.d.ts +3 -0
  45. package/dist/routes/transcriptions.d.ts +3 -0
  46. package/dist/routes/workspace-capture.d.ts +59 -0
  47. package/dist/routes/workspace-instruction-policies.d.ts +3 -0
  48. package/dist/routes/workspace-state.d.ts +3 -0
  49. package/dist/routes/workspaces.d.ts +5 -0
  50. package/dist/sandbox/access.d.ts +21 -0
  51. package/dist/sandbox/auth-callout.d.ts +30 -0
  52. package/dist/sandbox/channel-a.d.ts +37 -0
  53. package/dist/sandbox/enrollment.d.ts +120 -0
  54. package/dist/sandbox/machines.d.ts +29 -0
  55. package/dist/sandbox/metrics-ingestion.d.ts +128 -0
  56. package/dist/sandbox/rematerialize.d.ts +24 -0
  57. package/dist/sandbox/viewer.d.ts +251 -0
  58. package/dist/transcription/providers/azure-openai.d.ts +9 -0
  59. package/dist/transcription/providers/codex-subscription.d.ts +9 -0
  60. package/dist/transcription/providers/openai.d.ts +9 -0
  61. package/dist/transcription/service.d.ts +10 -0
  62. package/dist/workspace-state-projection.d.ts +16 -0
  63. package/package.json +13 -13
  64. package/src/app.ts +75 -5
  65. package/src/github-access.ts +1 -0
  66. package/src/integrations/google-drive.ts +869 -0
  67. package/src/integrations/oauth-client.ts +41 -6
  68. package/src/integrations/slack-bot.ts +756 -26
  69. package/src/mcp/server.ts +226 -42
  70. package/src/mcp/session-view.ts +1 -0
  71. package/src/mcp/toolspace.ts +17 -9
  72. package/src/routes/connections.ts +137 -7
  73. package/src/routes/documents.ts +13 -2
  74. package/src/routes/github.ts +327 -20
  75. package/src/routes/insights.ts +30 -0
  76. package/src/routes/preference-registry.ts +482 -0
  77. package/src/routes/sessions.ts +22 -23
  78. package/src/routes/transcriptions.ts +155 -0
  79. package/src/routes/workspace-capture.ts +108 -8
  80. package/src/routes/workspace-state.ts +61 -0
  81. package/src/sandbox/channel-a.ts +2 -0
  82. package/src/sandbox/rematerialize.ts +111 -4
  83. package/src/sandbox/viewer.ts +29 -12
  84. package/src/transcription/providers/azure-openai.ts +45 -0
  85. package/src/transcription/providers/codex-subscription.ts +100 -0
  86. package/src/transcription/providers/openai.ts +93 -0
  87. package/src/transcription/service.ts +121 -0
  88. package/src/workspace-state-projection.ts +244 -0
  89. package/dist/chunk-SD5GJTZ4.js.map +0 -1
package/src/mcp/server.ts CHANGED
@@ -21,6 +21,7 @@ import {
21
21
  type Permission,
22
22
  type ResourceRef,
23
23
  type SessionAuthorizationOperation,
24
+ type SessionAuthorizationSurface,
24
25
  type Session,
25
26
  UpdateScheduledTaskRequest,
26
27
  } from "@opengeni/contracts";
@@ -35,6 +36,8 @@ import {
35
36
  getSessionGoal,
36
37
  getSessionQueueSnapshot,
37
38
  getSessionTurn,
39
+ getOrCreatePreferenceRegistrySnapshot,
40
+ getPreferenceRegistryFullContent,
38
41
  getVariableSet,
39
42
  getVariableSetByName,
40
43
  areGitHubRepositoriesAllowedForWorkspace,
@@ -185,6 +188,8 @@ const FIRST_PARTY_TOOL_AUTHORIZATION = {
185
188
  memory_search: { sessionRequired: true, allOf: ["documents:search"] },
186
189
  memory_save: { sessionRequired: true, allOf: ["documents:search"] },
187
190
  memory_correct: { sessionRequired: true, allOf: ["documents:search"] },
191
+ preference_registry_summary: { sessionRequired: true, allOf: ["workspace:read"] },
192
+ preference_registry_get: { sessionRequired: true, allOf: ["workspace:read"] },
188
193
  sandboxes_list: { sessionRequired: true, allOf: ["sessions:read"] },
189
194
  sandbox_attach: { sessionRequired: true, allOf: ["sessions:control"] },
190
195
  sandbox_swap: { sessionRequired: true, allOf: ["sessions:control"] },
@@ -225,8 +230,13 @@ const FIRST_PARTY_TOOL_AUTHORIZATION = {
225
230
  scheduled_task_runs_list: { anyOf: ["scheduled_tasks:manage", "scheduled_tasks:run"] },
226
231
  slack_bot_list_channels: { allOf: ["connections:read"] },
227
232
  slack_bot_channel_history: { allOf: ["connections:read"] },
233
+ slack_bot_thread_replies: { allOf: ["connections:read"] },
228
234
  slack_bot_list_users: { allOf: ["connections:read"] },
235
+ slack_bot_list_files: { allOf: ["connections:read"] },
236
+ slack_bot_file_info: { allOf: ["connections:read"] },
237
+ slack_bot_file_content: { allOf: ["connections:read"] },
229
238
  slack_bot_post_message: { allOf: ["connections:read"] },
239
+ slack_bot_delete_message: { allOf: ["connections:read"] },
230
240
  } satisfies Record<FirstPartyMcpToolName, FirstPartyToolAuthorization>;
231
241
 
232
242
  const FIRST_PARTY_MCP_TOOL_NAME_SET = new Set<string>(FIRST_PARTY_MCP_TOOL_NAMES);
@@ -365,6 +375,9 @@ export function buildOpenGeniMcpServer(
365
375
  if (!toolspaceMode && sessionId !== null && options.workspaceMemoryEnabled === true) {
366
376
  registerMemoryTools(server, deps, grant, sessionId, json);
367
377
  }
378
+ if (!toolspaceMode && sessionId !== null && preferenceAttemptClaims(grant) !== null) {
379
+ registerPreferenceRegistryTools(server, deps, grant, json);
380
+ }
368
381
 
369
382
  // Fleet tools (M7 bring-your-own-compute): list / attach / swap / run_on /
370
383
  // provision over the session's Modal box + the workspace's enrolled machines.
@@ -837,6 +850,32 @@ function registerSlackBotTools(
837
850
  ),
838
851
  );
839
852
 
853
+ server.registerTool(
854
+ "slack_bot_thread_replies",
855
+ {
856
+ description:
857
+ "Read a Slack thread as the workspace-shared OpenGeni bot. Pass the channel ID and the parent message timestamp returned by channel history. The result includes the parent followed by its replies.",
858
+ inputSchema: {
859
+ connectionId: z4.string().uuid().optional(),
860
+ channelId: z4.string().min(1).max(64),
861
+ threadTimestamp: z4.string().min(1).max(64),
862
+ cursor: z4.string().max(1024).optional(),
863
+ limit: z4.number().int().min(1).max(100).optional(),
864
+ },
865
+ },
866
+ async ({ connectionId, channelId, threadTimestamp, cursor, limit }) =>
867
+ json(
868
+ await (
869
+ await clientFor(connectionId)
870
+ ).threadReplies({
871
+ channelId,
872
+ threadTimestamp,
873
+ ...(cursor ? { cursor } : {}),
874
+ ...(limit !== undefined ? { limit } : {}),
875
+ }),
876
+ ),
877
+ );
878
+
840
879
  server.registerTool(
841
880
  "slack_bot_list_users",
842
881
  {
@@ -858,20 +897,95 @@ function registerSlackBotTools(
858
897
  ),
859
898
  );
860
899
 
900
+ server.registerTool(
901
+ "slack_bot_list_files",
902
+ {
903
+ description:
904
+ "List Slack files and canvases shared with a channel where the workspace-shared OpenGeni bot is already a member.",
905
+ inputSchema: {
906
+ connectionId: z4.string().uuid().optional(),
907
+ channelId: z4.string().min(1).max(64),
908
+ cursor: z4.string().max(1024).optional(),
909
+ limit: z4.number().int().min(1).max(200).optional(),
910
+ },
911
+ },
912
+ async ({ connectionId, channelId, cursor, limit }) =>
913
+ json(
914
+ await (
915
+ await clientFor(connectionId)
916
+ ).listFiles({
917
+ channelId,
918
+ ...(cursor ? { cursor } : {}),
919
+ ...(limit !== undefined ? { limit } : {}),
920
+ }),
921
+ ),
922
+ );
923
+
924
+ server.registerTool(
925
+ "slack_bot_file_info",
926
+ {
927
+ description:
928
+ "Read safe metadata for a Slack file or canvas shared with a channel where the workspace-shared OpenGeni bot is already a member. For an embedded huddle transcript, also pass the shared canvas file ID as parentFileId so OpenGeni can verify the indirect share.",
929
+ inputSchema: {
930
+ connectionId: z4.string().uuid().optional(),
931
+ channelId: z4.string().min(1).max(64),
932
+ fileId: z4.string().min(1).max(64),
933
+ parentFileId: z4.string().min(1).max(64).optional(),
934
+ },
935
+ },
936
+ async ({ connectionId, channelId, fileId, parentFileId }) =>
937
+ json(
938
+ await (
939
+ await clientFor(connectionId)
940
+ ).fileInfo({
941
+ channelId,
942
+ fileId,
943
+ ...(parentFileId ? { parentFileId } : {}),
944
+ }),
945
+ ),
946
+ );
947
+
948
+ server.registerTool(
949
+ "slack_bot_file_content",
950
+ {
951
+ description:
952
+ "Read a bounded page of UTF-8 text from a Slack file or canvas shared with a channel where the workspace-shared OpenGeni bot is already a member. For an embedded huddle transcript, also pass the shared canvas file ID as parentFileId so OpenGeni can verify the channel-to-canvas-to-transcript chain. Slack may still restrict a huddle transcript body to participants; that returns huddle_transcript_requires_participant_access. Private Slack URLs and credentials are never returned. Continue with nextOffset when truncated is true.",
953
+ inputSchema: {
954
+ connectionId: z4.string().uuid().optional(),
955
+ channelId: z4.string().min(1).max(64),
956
+ fileId: z4.string().min(1).max(64),
957
+ parentFileId: z4.string().min(1).max(64).optional(),
958
+ offset: z4.number().int().min(0).max(4_000_000).optional(),
959
+ },
960
+ },
961
+ async ({ connectionId, channelId, fileId, parentFileId, offset }) =>
962
+ json(
963
+ await (
964
+ await clientFor(connectionId)
965
+ ).fileContent({
966
+ channelId,
967
+ fileId,
968
+ ...(parentFileId ? { parentFileId } : {}),
969
+ ...(offset !== undefined ? { offset } : {}),
970
+ }),
971
+ ),
972
+ );
973
+
861
974
  server.registerTool(
862
975
  "slack_bot_post_message",
863
976
  {
864
977
  description:
865
- "Post as the workspace-shared OpenGeni bot. Pass channelId for a channel where the bot is already a member, or userId to open/post a DM; pass exactly one. Generate one operationId UUID per intended message and reuse that same UUID on every retry.",
978
+ "Post as the workspace-shared OpenGeni bot. Pass channelId for a channel where the bot is already a member, or userId to open/post a DM; pass exactly one. To reply in a thread, also pass its parent message timestamp as threadTimestamp. Generate one operationId UUID per intended message and reuse that same UUID on every retry.",
866
979
  inputSchema: {
867
980
  connectionId: z4.string().uuid().optional(),
868
981
  operationId: z4.string().uuid(),
869
982
  channelId: z4.string().min(1).max(64).optional(),
870
983
  userId: z4.string().min(1).max(64).optional(),
984
+ threadTimestamp: z4.string().min(1).max(64).optional(),
871
985
  text: z4.string().min(1).max(40_000),
872
986
  },
873
987
  },
874
- async ({ connectionId, operationId, channelId, userId, text }) => {
988
+ async ({ connectionId, operationId, channelId, userId, threadTimestamp, text }) => {
875
989
  if (Boolean(channelId) === Boolean(userId)) {
876
990
  throw new Error("exactly one of channelId or userId is required");
877
991
  }
@@ -882,11 +996,30 @@ function registerSlackBotTools(
882
996
  operationId,
883
997
  ...(channelId ? { channelId } : {}),
884
998
  ...(userId ? { userId } : {}),
999
+ ...(threadTimestamp ? { threadTimestamp } : {}),
885
1000
  text,
886
1001
  }),
887
1002
  );
888
1003
  },
889
1004
  );
1005
+
1006
+ server.registerTool(
1007
+ "slack_bot_delete_message",
1008
+ {
1009
+ description:
1010
+ "Delete a message authored by the workspace-shared OpenGeni bot. Pass the channel ID and exact message timestamp returned by a prior post or channel/thread read. Generate one operationId UUID per intended deletion and reuse it on every retry, including after an unknown outcome. Slack refuses deletion of messages not authored by this bot.",
1011
+ inputSchema: {
1012
+ connectionId: z4.string().uuid().optional(),
1013
+ operationId: z4.string().uuid(),
1014
+ channelId: z4.string().min(1).max(64),
1015
+ timestamp: z4.string().min(1).max(64),
1016
+ },
1017
+ },
1018
+ async ({ connectionId, operationId, channelId, timestamp }) =>
1019
+ json(
1020
+ await (await clientFor(connectionId)).deleteMessage({ operationId, channelId, timestamp }),
1021
+ ),
1022
+ );
890
1023
  }
891
1024
 
892
1025
  function registerToolspaceProxyTools(server: McpServer, surface: ToolspaceMcpSurface | null): void {
@@ -1149,6 +1282,70 @@ async function authorizeFirstPartySession(
1149
1282
  });
1150
1283
  }
1151
1284
 
1285
+ function preferenceAttemptClaims(grant: AccessGrant): {
1286
+ sessionId: string;
1287
+ turnId: string;
1288
+ attemptId: string;
1289
+ executionGeneration: number;
1290
+ } | null {
1291
+ const metadata = grant.metadata ?? {};
1292
+ if (
1293
+ typeof metadata["sessionId"] !== "string" ||
1294
+ typeof metadata["turnId"] !== "string" ||
1295
+ typeof metadata["attemptId"] !== "string" ||
1296
+ typeof metadata["executionGeneration"] !== "number" ||
1297
+ !Number.isSafeInteger(metadata["executionGeneration"]) ||
1298
+ metadata["executionGeneration"] < 1 ||
1299
+ metadata["executionGeneration"] > 2_147_483_647
1300
+ ) {
1301
+ return null;
1302
+ }
1303
+ return {
1304
+ sessionId: metadata["sessionId"],
1305
+ turnId: metadata["turnId"],
1306
+ attemptId: metadata["attemptId"],
1307
+ executionGeneration: metadata["executionGeneration"],
1308
+ };
1309
+ }
1310
+
1311
+ function registerPreferenceRegistryTools(
1312
+ server: McpServer,
1313
+ deps: ApiRouteDeps,
1314
+ grant: AccessGrant,
1315
+ json: JsonResult,
1316
+ ): void {
1317
+ const attemptClaims = () => {
1318
+ const resolved = preferenceAttemptClaims(grant);
1319
+ if (!resolved) throw new Error("Exact signed preference attempt authority is required.");
1320
+ return {
1321
+ accountId: grant.accountId,
1322
+ workspaceId: grant.workspaceId,
1323
+ ...resolved,
1324
+ };
1325
+ };
1326
+
1327
+ server.registerTool(
1328
+ "preference_registry_summary",
1329
+ {
1330
+ description:
1331
+ "List bounded deterministic descriptors for organization, workspace, and immutable initiating-human preferences frozen to this exact attempt. Full content is omitted; retrieve only a relevant returned handle.",
1332
+ inputSchema: {},
1333
+ },
1334
+ async () => json(await getOrCreatePreferenceRegistrySnapshot(deps.db, attemptClaims())),
1335
+ );
1336
+
1337
+ server.registerTool(
1338
+ "preference_registry_get",
1339
+ {
1340
+ description:
1341
+ "Retrieve full content for one preference in this exact attempt snapshot. Handles from another account, workspace, human, or attempt are rejected.",
1342
+ inputSchema: { retrievalHandle: z4.string().min(1).max(512) },
1343
+ },
1344
+ async ({ retrievalHandle }) =>
1345
+ json(await getPreferenceRegistryFullContent(deps.db, attemptClaims(), retrievalHandle)),
1346
+ );
1347
+ }
1348
+
1152
1349
  const MemoryKindSchema = z4.enum(["preference", "semantic", "procedural", "decision", "episodic"]);
1153
1350
 
1154
1351
  function memoryPreview(text: string): string {
@@ -1348,7 +1545,7 @@ function registerFleetTools(
1348
1545
  "run_on",
1349
1546
  {
1350
1547
  description:
1351
- "Run a ONE-OFF op on a SPECIFIC enrolled selfhosted machine WITHOUT changing this session's active sandbox (a side-channel to another machine). Ops: exec (run a command), read (read a file), write (write a file). `target` = a selfhosted sandboxes_list `id`. To make a machine the active sandbox instead, use sandbox_swap.",
1548
+ "Run a ONE-OFF op on a SPECIFIC enrolled selfhosted machine WITHOUT changing this session's active sandbox (a side-channel to another machine). Ops: exec (run a command), read (read a file), write (write a file). Exec returns exact exitCode plus typed timedOut and the effective deadlineMs; a deadline kill or missing exit proof is never ok:true, and an ambiguous transport loss is not replayed. `target` = a selfhosted sandboxes_list `id`. To make a machine the active sandbox instead, use sandbox_swap.",
1352
1549
  inputSchema: {
1353
1550
  target: z4.string().min(1),
1354
1551
  op: z4.discriminatedUnion("kind", [
@@ -1567,6 +1764,7 @@ function registerRigTools(
1567
1764
  function exactAgentCommandContext(
1568
1765
  grant: AccessGrant,
1569
1766
  callerSessionId: string,
1767
+ authorizationSurface?: SessionAuthorizationSurface,
1570
1768
  ): AgentSessionCommandContext {
1571
1769
  const turnId = grant.metadata?.["turnId"];
1572
1770
  const attemptId = grant.metadata?.["attemptId"];
@@ -1588,6 +1786,7 @@ function exactAgentCommandContext(
1588
1786
  callerTurnId: turnId,
1589
1787
  callerAttemptId: attemptId,
1590
1788
  callerExecutionGeneration: executionGeneration,
1789
+ ...(authorizationSurface ? { authorizationSurface } : {}),
1591
1790
  };
1592
1791
  }
1593
1792
 
@@ -1822,6 +2021,7 @@ function registerWorkspaceOrchestrationTools(
1822
2021
  rigId: z4.string().uuid().optional(),
1823
2022
  model: z4.string().min(1).optional(),
1824
2023
  reasoningEffort: z4.string().optional(),
2024
+ latencyMode: z4.enum(["standard", "priority", "fast"]).optional(),
1825
2025
  sandboxBackend: z4.string().optional(),
1826
2026
  // Create-time machine targeting: an enrolled sandbox id (from
1827
2027
  // sandboxes_list) to run the spawned session on. Seeds the active-sandbox
@@ -1953,7 +2153,6 @@ function registerWorkspaceOrchestrationTools(
1953
2153
  targetSessionId,
1954
2154
  {
1955
2155
  text,
1956
- toolsProvided: false,
1957
2156
  delivery: "send",
1958
2157
  origin: "operator",
1959
2158
  clientEventId: idempotencyKey,
@@ -1977,16 +2176,10 @@ function registerWorkspaceOrchestrationTools(
1977
2176
  },
1978
2177
  },
1979
2178
  async ({ sessionId, idempotencyKey, reason }) => {
1980
- const authorization = await authorizeFirstPartySession(
1981
- deps,
1982
- grant,
1983
- sessionId,
1984
- "session.control",
1985
- );
1986
2179
  if (callerSessionId !== null) {
1987
2180
  const controlled = await controlAgentSessionWorkstream(
1988
2181
  deps,
1989
- exactAgentCommandContext(grant, callerSessionId),
2182
+ exactAgentCommandContext(grant, callerSessionId, "first_party_mcp"),
1990
2183
  {
1991
2184
  targetSessionId: sessionId,
1992
2185
  action: "pause",
@@ -1999,7 +2192,7 @@ function registerWorkspaceOrchestrationTools(
1999
2192
  effectiveControl: projectEffectiveControlForRelatedAccess(
2000
2193
  serializeEffectiveSessionControl(controlled.control),
2001
2194
  sessionId,
2002
- authorization?.relatedSessionAccess ?? "root",
2195
+ controlled.authorization?.relatedSessionAccess ?? "root",
2003
2196
  ),
2004
2197
  interruptionCount: controlled.interruptionCount,
2005
2198
  replay: controlled.replay,
@@ -2012,6 +2205,7 @@ function registerWorkspaceOrchestrationTools(
2012
2205
  workspaceId: grant.workspaceId,
2013
2206
  sessionId,
2014
2207
  subjectId: grant.subjectId,
2208
+ authorizationSurface: "first_party_mcp",
2015
2209
  },
2016
2210
  {
2017
2211
  action: "pause",
@@ -2019,14 +2213,7 @@ function registerWorkspaceOrchestrationTools(
2019
2213
  ...(reason ? { reason } : {}),
2020
2214
  },
2021
2215
  );
2022
- return json({
2023
- ...controlled,
2024
- effectiveControl: projectEffectiveControlForRelatedAccess(
2025
- controlled.effectiveControl,
2026
- sessionId,
2027
- authorization?.relatedSessionAccess ?? "root",
2028
- ),
2029
- });
2216
+ return json(controlled);
2030
2217
  },
2031
2218
  );
2032
2219
 
@@ -2042,16 +2229,10 @@ function registerWorkspaceOrchestrationTools(
2042
2229
  },
2043
2230
  },
2044
2231
  async ({ sessionId, idempotencyKey, reason }) => {
2045
- const authorization = await authorizeFirstPartySession(
2046
- deps,
2047
- grant,
2048
- sessionId,
2049
- "session.control",
2050
- );
2051
2232
  if (callerSessionId !== null) {
2052
2233
  const controlled = await controlAgentSessionWorkstream(
2053
2234
  deps,
2054
- exactAgentCommandContext(grant, callerSessionId),
2235
+ exactAgentCommandContext(grant, callerSessionId, "first_party_mcp"),
2055
2236
  {
2056
2237
  targetSessionId: sessionId,
2057
2238
  action: "resume",
@@ -2064,7 +2245,7 @@ function registerWorkspaceOrchestrationTools(
2064
2245
  effectiveControl: projectEffectiveControlForRelatedAccess(
2065
2246
  serializeEffectiveSessionControl(controlled.control),
2066
2247
  sessionId,
2067
- authorization?.relatedSessionAccess ?? "root",
2248
+ controlled.authorization?.relatedSessionAccess ?? "root",
2068
2249
  ),
2069
2250
  interruptionCount: controlled.interruptionCount,
2070
2251
  replay: controlled.replay,
@@ -2077,6 +2258,7 @@ function registerWorkspaceOrchestrationTools(
2077
2258
  workspaceId: grant.workspaceId,
2078
2259
  sessionId,
2079
2260
  subjectId: grant.subjectId,
2261
+ authorizationSurface: "first_party_mcp",
2080
2262
  },
2081
2263
  {
2082
2264
  action: "resume",
@@ -2084,14 +2266,7 @@ function registerWorkspaceOrchestrationTools(
2084
2266
  ...(reason ? { reason } : {}),
2085
2267
  },
2086
2268
  );
2087
- return json({
2088
- ...controlled,
2089
- effectiveControl: projectEffectiveControlForRelatedAccess(
2090
- controlled.effectiveControl,
2091
- sessionId,
2092
- authorization?.relatedSessionAccess ?? "root",
2093
- ),
2094
- });
2269
+ return json(controlled);
2095
2270
  },
2096
2271
  );
2097
2272
 
@@ -2108,10 +2283,9 @@ function registerWorkspaceOrchestrationTools(
2108
2283
  },
2109
2284
  },
2110
2285
  async ({ sessionId, instruction, idempotencyKey }) => {
2111
- await authorizeFirstPartySession(deps, grant, sessionId, "session.steer");
2112
2286
  const result = await steerAgentSession(
2113
2287
  deps,
2114
- exactAgentCommandContext(grant, callerSessionId),
2288
+ exactAgentCommandContext(grant, callerSessionId, "first_party_mcp"),
2115
2289
  { targetSessionId: sessionId, instruction, idempotencyKey },
2116
2290
  );
2117
2291
  return json({
@@ -2317,14 +2491,16 @@ function registerGitHubConnectTool(
2317
2491
  const { settings } = deps;
2318
2492
  const missing = githubAppMissingSettings(settings);
2319
2493
  const slug = settings.githubAppSlug?.trim() || null;
2494
+ const setupMode = settings.productAccessMode === "managed" ? "platform" : "operator";
2320
2495
  if (missing.length > 0 || !slug) {
2321
2496
  return json({
2322
2497
  configured: false,
2323
2498
  status: "disabled",
2324
- appSlug: slug,
2499
+ setupMode,
2500
+ appSlug: setupMode === "operator" ? slug : null,
2325
2501
  installUrl: null,
2326
2502
  linkUrl: null,
2327
- missing,
2503
+ missing: setupMode === "operator" ? missing : [],
2328
2504
  });
2329
2505
  }
2330
2506
  const installations = await listWorkspaceGitHubInstallationBindings(deps, grant.workspaceId);
@@ -2342,13 +2518,21 @@ function registerGitHubConnectTool(
2342
2518
  const connectUrl = state
2343
2519
  ? `${baseUrl}/v1/workspaces/${grant.workspaceId}/github/connect?state=${encodeURIComponent(state)}`
2344
2520
  : null;
2521
+ const installationViews = installations.map((installation) => ({
2522
+ ...installation,
2523
+ configureUrl:
2524
+ state && baseUrl
2525
+ ? `${baseUrl}/v1/workspaces/${grant.workspaceId}/github/installations/${installation.installationId}/configure?state=${encodeURIComponent(state)}`
2526
+ : null,
2527
+ }));
2345
2528
  return json({
2346
2529
  configured: true,
2347
2530
  status,
2348
- appSlug: slug,
2531
+ setupMode,
2532
+ appSlug: setupMode === "operator" ? slug : null,
2349
2533
  installUrl: connectUrl,
2350
2534
  linkUrl: connectUrl,
2351
- installations,
2535
+ installations: installationViews,
2352
2536
  missing: [],
2353
2537
  });
2354
2538
  },
@@ -564,6 +564,7 @@ export function boundSessionDetailMcp(
564
564
  lastSequence: session.lastSequence,
565
565
  codexPinnedCredentialId: session.codexPinnedCredentialId,
566
566
  codexLastCredentialId: session.codexLastCredentialId,
567
+ codexCompactionMode: session.codexCompactionMode,
567
568
  pinned: session.pinned,
568
569
  pinnedAt: session.pinnedAt,
569
570
  pinVersion: session.pinVersion,
@@ -76,9 +76,12 @@ type McpTool = {
76
76
  };
77
77
 
78
78
  const APPROVAL_REQUIRED_MESSAGE = "requires approval - invoke via the agent";
79
- const TOOLSPACE_AUTH_NEEDED_ERROR_CODE = -32001;
80
- const TOOLSPACE_AUTH_NEEDED_MESSAGE =
81
- "Authentication required - a connection link was posted to the session.";
79
+ const TOOLSPACE_AUTH_NEEDED_ERROR = {
80
+ // OpenGeni application-defined JSON-RPC code. Keep this positive so it cannot
81
+ // collide with MCP SDK transport errors such as RequestTimeout (-32001).
82
+ code: 40_101,
83
+ message: "Authentication required - a connection link was posted to the session.",
84
+ } as const;
82
85
  const TOOLSPACE_NO_ACTIVE_TURN_MESSAGE =
83
86
  "no active turn - toolspace calls require an in-flight turn";
84
87
  // First-party OpenGeni MCP proxies (files/docs) route back through the same
@@ -721,7 +724,7 @@ async function callRemoteTool(
721
724
  return mcpError("upstream tool result exceeded the safety limit");
722
725
  }
723
726
  if (isToolspaceAuthNeededError(error)) {
724
- return mcpError(TOOLSPACE_AUTH_NEEDED_MESSAGE);
727
+ return mcpError(TOOLSPACE_AUTH_NEEDED_ERROR.message);
725
728
  }
726
729
  // The raw upstream error can carry provider-specific detail; log it
727
730
  // server-side and return only a generic result to the sandbox so no header
@@ -1037,8 +1040,8 @@ async function authNeededFetchResponse(
1037
1040
  jsonrpc: "2.0",
1038
1041
  id: request.id ?? null,
1039
1042
  error: {
1040
- code: TOOLSPACE_AUTH_NEEDED_ERROR_CODE,
1041
- message: TOOLSPACE_AUTH_NEEDED_MESSAGE,
1043
+ code: TOOLSPACE_AUTH_NEEDED_ERROR.code,
1044
+ message: TOOLSPACE_AUTH_NEEDED_ERROR.message,
1042
1045
  },
1043
1046
  }),
1044
1047
  {
@@ -1110,9 +1113,14 @@ function fetchInputForAttempt(input: string | URL | Request): string | URL | Req
1110
1113
  }
1111
1114
 
1112
1115
  function isToolspaceAuthNeededError(error: unknown): boolean {
1116
+ if (!(error instanceof Error)) {
1117
+ return false;
1118
+ }
1119
+ const code = (error as { code?: unknown }).code;
1113
1120
  return (
1114
- error instanceof Error &&
1115
- ((error as { code?: unknown }).code === TOOLSPACE_AUTH_NEEDED_ERROR_CODE ||
1116
- error.message.includes(TOOLSPACE_AUTH_NEEDED_MESSAGE))
1121
+ code === TOOLSPACE_AUTH_NEEDED_ERROR.code &&
1122
+ (error.message === TOOLSPACE_AUTH_NEEDED_ERROR.message ||
1123
+ error.message ===
1124
+ `MCP error ${TOOLSPACE_AUTH_NEEDED_ERROR.code}: ${TOOLSPACE_AUTH_NEEDED_ERROR.message}`)
1117
1125
  );
1118
1126
  }