@opengeni/api-router 0.30.3 → 2.2.0-canary.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (165) hide show
  1. package/dist/api-websocket.d.ts +18 -0
  2. package/dist/app.js +1 -1
  3. package/dist/auth/managed-auth.d.ts +6 -0
  4. package/dist/{chunk-WCXHI3FF.js → chunk-3TP54PPX.js} +26692 -15389
  5. package/dist/chunk-3TP54PPX.js.map +1 -0
  6. package/dist/codemode.d.ts +8 -1
  7. package/dist/company-brain-okf.d.ts +24 -0
  8. package/dist/connected-machine-computer-access.d.ts +11 -0
  9. package/dist/connection-authority-owner.d.ts +59 -0
  10. package/dist/connection-ownership.d.ts +47 -0
  11. package/dist/controller-data-plane.d.ts +16 -0
  12. package/dist/editable-artifact-websocket.d.ts +5 -15
  13. package/dist/editable-artifact-workspace-files.d.ts +1 -1
  14. package/dist/http/api-error.d.ts +10 -0
  15. package/dist/http/cors.d.ts +5 -0
  16. package/dist/http/interaction-control-error.d.ts +13 -0
  17. package/dist/http/sse.d.ts +2 -3
  18. package/dist/index.d.ts +5 -2
  19. package/dist/index.js +326 -82
  20. package/dist/index.js.map +1 -1
  21. package/dist/integrations/atlassian.d.ts +17 -0
  22. package/dist/integrations/fiken.d.ts +7 -2
  23. package/dist/integrations/google-drive.d.ts +18 -0
  24. package/dist/integrations/oauth-client.d.ts +22 -3
  25. package/dist/integrations/oauth-profiles.d.ts +195 -0
  26. package/dist/integrations/personal-github-repositories.d.ts +45 -0
  27. package/dist/integrations/personal-github.d.ts +40 -0
  28. package/dist/integrations/pr-review-provider.d.ts +22 -0
  29. package/dist/integrations/provider-oauth.d.ts +8 -0
  30. package/dist/integrations/slack-app-home.d.ts +24 -0
  31. package/dist/integrations/slack-bot.d.ts +157 -9
  32. package/dist/integrations/slack-interactions.d.ts +39 -5
  33. package/dist/integrations/slack-routing.d.ts +120 -0
  34. package/dist/integrations/social-oauth.d.ts +5 -0
  35. package/dist/interaction-frame-proxy.d.ts +68 -0
  36. package/dist/mcp/company-brain-governed-writes.d.ts +26 -0
  37. package/dist/mcp/company-profile-agent-admin.d.ts +44 -0
  38. package/dist/mcp/documents.d.ts +1 -0
  39. package/dist/mcp/remember.d.ts +28 -0
  40. package/dist/mcp/request-abort.d.ts +19 -0
  41. package/dist/mcp/scheduled-task-view.d.ts +4 -4
  42. package/dist/mcp/server.d.ts +15 -2
  43. package/dist/mcp/session-view.d.ts +4 -0
  44. package/dist/mcp/session-wait.d.ts +137 -0
  45. package/dist/routes/automations.d.ts +4 -0
  46. package/dist/routes/billing.d.ts +5 -0
  47. package/dist/routes/browser-sessions.d.ts +4 -1
  48. package/dist/routes/company-brain.d.ts +3 -0
  49. package/dist/routes/company-profile.d.ts +2 -1
  50. package/dist/routes/connection-authorities.d.ts +3 -0
  51. package/dist/routes/personal-github-git-broker.d.ts +29 -0
  52. package/dist/routes/personal-github.d.ts +3 -0
  53. package/dist/routes/pr-review.d.ts +3 -0
  54. package/dist/routes/sessions.d.ts +1 -0
  55. package/dist/routes/user-resource-authorities.d.ts +3 -0
  56. package/dist/routes/workspace-capture.d.ts +10 -2
  57. package/dist/routes/workspace-learning.d.ts +3 -0
  58. package/dist/routes/workspaces.d.ts +19 -0
  59. package/dist/sandbox/channel-a.d.ts +11 -1
  60. package/dist/sandbox/connection-authority.d.ts +3 -0
  61. package/dist/sandbox/enrollment.d.ts +2 -0
  62. package/dist/sandbox/machines.d.ts +2 -2
  63. package/dist/sandbox/metrics-ingestion.d.ts +10 -20
  64. package/dist/sandbox/viewer.d.ts +24 -1
  65. package/dist/sandbox-file-artifacts.d.ts +11 -0
  66. package/dist/scheduled-task-deletion.d.ts +14 -0
  67. package/dist/slack-reaction-files.d.ts +1 -1
  68. package/dist/temporal-schedule-cleanup.d.ts +1 -0
  69. package/package.json +20 -18
  70. package/src/api-websocket.ts +23 -0
  71. package/src/app.ts +161 -23
  72. package/src/auth/managed-auth.ts +25 -3
  73. package/src/codemode.ts +53 -23
  74. package/src/codex-realtime.ts +8 -2
  75. package/src/company-brain-okf.ts +340 -0
  76. package/src/connected-machine-computer-access.ts +33 -0
  77. package/src/connection-authority-owner.ts +61 -0
  78. package/src/connection-ownership.ts +180 -0
  79. package/src/controller-data-plane.ts +47 -0
  80. package/src/editable-artifact-native-kernel.ts +26 -15
  81. package/src/editable-artifact-office-import.ts +28 -3
  82. package/src/editable-artifact-production.ts +13 -7
  83. package/src/editable-artifact-websocket.ts +11 -18
  84. package/src/editable-artifact-workspace-files.ts +31 -6
  85. package/src/http/api-error.ts +28 -0
  86. package/src/http/auth.ts +4 -0
  87. package/src/http/cors.ts +35 -0
  88. package/src/http/interaction-control-error.ts +164 -0
  89. package/src/http/sse.ts +112 -66
  90. package/src/index.ts +46 -6
  91. package/src/integrations/atlassian.ts +146 -35
  92. package/src/integrations/fiken.ts +102 -22
  93. package/src/integrations/google-drive.ts +301 -92
  94. package/src/integrations/oauth-client.ts +272 -143
  95. package/src/integrations/oauth-profiles.ts +477 -0
  96. package/src/integrations/personal-github-repositories.ts +445 -0
  97. package/src/integrations/personal-github.ts +705 -0
  98. package/src/integrations/pr-review-provider.ts +246 -0
  99. package/src/integrations/provider-oauth.ts +121 -5
  100. package/src/integrations/slack-app-home.ts +300 -0
  101. package/src/integrations/slack-bot.ts +342 -57
  102. package/src/integrations/slack-interactions.ts +1898 -259
  103. package/src/integrations/slack-routing.ts +337 -0
  104. package/src/integrations/social-oauth.ts +25 -0
  105. package/src/interaction-frame-proxy.ts +409 -0
  106. package/src/mcp/company-brain-governed-writes.ts +262 -0
  107. package/src/mcp/company-profile-agent-admin.ts +205 -0
  108. package/src/mcp/documents.ts +37 -6
  109. package/src/mcp/files.ts +16 -1
  110. package/src/mcp/remember.ts +181 -0
  111. package/src/mcp/request-abort.ts +44 -0
  112. package/src/mcp/scheduled-task-view.ts +1 -0
  113. package/src/mcp/server.ts +719 -369
  114. package/src/mcp/session-view.ts +54 -0
  115. package/src/mcp/session-wait.ts +554 -0
  116. package/src/model-catalog.ts +20 -12
  117. package/src/routes/api-integrations.ts +4 -0
  118. package/src/routes/api-keys.ts +1 -0
  119. package/src/routes/automations.ts +534 -0
  120. package/src/routes/billing.ts +57 -1
  121. package/src/routes/browser-sessions.ts +329 -79
  122. package/src/routes/channels.ts +17 -1
  123. package/src/routes/codex.ts +50 -0
  124. package/src/routes/company-brain.ts +367 -0
  125. package/src/routes/company-profile.ts +1 -1
  126. package/src/routes/computer-sessions.ts +320 -93
  127. package/src/routes/connection-authorities.ts +139 -0
  128. package/src/routes/connections.ts +144 -34
  129. package/src/routes/documents.ts +353 -4
  130. package/src/routes/editable-artifacts.ts +11 -3
  131. package/src/routes/enrollments.ts +116 -26
  132. package/src/routes/environments.ts +127 -55
  133. package/src/routes/files.ts +72 -11
  134. package/src/routes/install.ts +82 -28
  135. package/src/routes/integration-facets.ts +29 -0
  136. package/src/routes/interaction-resources.ts +20 -2
  137. package/src/routes/machines.ts +275 -13
  138. package/src/routes/organization-memberships.ts +717 -28
  139. package/src/routes/packs.ts +4 -3
  140. package/src/routes/personal-github-git-broker.ts +785 -0
  141. package/src/routes/personal-github.ts +319 -0
  142. package/src/routes/pr-review.ts +531 -0
  143. package/src/routes/rigs.ts +104 -39
  144. package/src/routes/scheduled-tasks.ts +19 -21
  145. package/src/routes/sessions.ts +775 -38
  146. package/src/routes/social.ts +14 -2
  147. package/src/routes/supergrok.ts +23 -4
  148. package/src/routes/transcription-recordings.ts +8 -2
  149. package/src/routes/user-resource-authorities.ts +138 -0
  150. package/src/routes/workspace-artifacts.ts +4 -1
  151. package/src/routes/workspace-capture.ts +21 -0
  152. package/src/routes/workspace-learning.ts +228 -0
  153. package/src/routes/workspaces.ts +59 -6
  154. package/src/sandbox/auth-callout.ts +38 -12
  155. package/src/sandbox/channel-a.ts +242 -19
  156. package/src/sandbox/connection-authority.ts +3 -0
  157. package/src/sandbox/enrollment.ts +15 -3
  158. package/src/sandbox/machines.ts +186 -64
  159. package/src/sandbox/metrics-ingestion.ts +220 -58
  160. package/src/sandbox/viewer.ts +243 -19
  161. package/src/sandbox-file-artifacts.ts +329 -0
  162. package/src/scheduled-task-deletion.ts +127 -0
  163. package/src/slack-reaction-files.ts +16 -5
  164. package/src/temporal-schedule-cleanup.ts +13 -0
  165. package/dist/chunk-WCXHI3FF.js.map +0 -1
@@ -0,0 +1,11 @@
1
+ import { SandboxFileArtifactReceipt, type AccessGrant, type Session } from "@opengeni/contracts";
2
+ import { type ApiRouteDeps } from "@opengeni/core";
3
+ export declare function publishSandboxFileArtifact(deps: ApiRouteDeps, input: {
4
+ grant: AccessGrant;
5
+ session: Session;
6
+ path: string;
7
+ signal?: AbortSignal | undefined;
8
+ }): Promise<SandboxFileArtifactReceipt>;
9
+ export declare function sandboxArtifactRelativePath(value: string): string;
10
+ export declare function sandboxArtifactSafeFilename(filename: string): string;
11
+ export declare function sandboxFileContentType(filename: string): string;
@@ -0,0 +1,14 @@
1
+ import type { ScheduledTask as ScheduledTaskValue } from "@opengeni/contracts";
2
+ import { type ApiRouteDeps } from "@opengeni/core";
3
+ import type { TemporalScheduleCleanupClaim } from "@opengeni/db";
4
+ /** Execute one exact connector obligation frozen into the tombstone outbox row. */
5
+ export declare function cleanupScheduledTaskConnectorAuthorization(deps: ApiRouteDeps, claim: TemporalScheduleCleanupClaim): Promise<void>;
6
+ /** Shared HTTP/MCP domain operation: authorize, tombstone, persist, then accelerate cleanup. */
7
+ export declare function deleteScheduledTaskWithDurableCleanup(deps: ApiRouteDeps, input: {
8
+ workspaceId: string;
9
+ taskId: string;
10
+ subjectId: string;
11
+ }): Promise<{
12
+ task: ScheduledTaskValue;
13
+ changed: boolean;
14
+ }>;
@@ -1,6 +1,6 @@
1
1
  import type { FileResourceRef } from "@opengeni/contracts";
2
2
  import { completeFileUpload, prepareGeneratedWorkspaceFile, type Database } from "@opengeni/db";
3
- import type { ObjectStorage } from "@opengeni/storage";
3
+ import { type ObjectStorage } from "@opengeni/storage";
4
4
  import type { DownloadedSlackReactionImage } from "./integrations/slack-bot.js";
5
5
  export type SlackReactionImageImportSource = Readonly<{
6
6
  accountId: string;
@@ -2,6 +2,7 @@ import { type Database, type TemporalScheduleCleanupClaim } from "@opengeni/db";
2
2
  import type { Observability } from "@opengeni/observability";
3
3
  type TemporalScheduleCleanupDependencies = {
4
4
  db: Database;
5
+ cleanupConnectorAuthorization?: (claim: TemporalScheduleCleanupClaim) => Promise<void>;
5
6
  deleteSchedule: (temporalScheduleId: string) => Promise<void>;
6
7
  observability?: Pick<Observability, "info" | "warn" | "error">;
7
8
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@opengeni/api-router",
3
- "version": "0.30.3",
3
+ "version": "2.2.0-canary.0",
4
4
  "description": "OpenGeni HTTP surface: the Hono adapter/router (createApp), routes, MCP HTTP transport, and HTTP access adapters over @opengeni/core. An engine-distribution surface — its runtime closure includes engine-internal packages.",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {
@@ -43,30 +43,32 @@
43
43
  "@hono/zod-validator": "^0.7.6",
44
44
  "@llamaindex/liteparse": "^1.5.3",
45
45
  "@modelcontextprotocol/sdk": "^1.29.0",
46
- "@opengeni/agent-proto": "^0.5.0",
47
- "@opengeni/artifact-tool": "^0.2.10",
48
- "@opengeni/capabilities": "^0.2.0",
49
- "@opengeni/codemode": "^0.4.4",
50
- "@opengeni/codex": "^0.2.17",
51
- "@opengeni/config": "^0.16.4",
52
- "@opengeni/contracts": "^1.0.1",
53
- "@opengeni/core": "^1.0.1",
54
- "@opengeni/db": "^1.0.1",
55
- "@opengeni/documents": "^0.5.41",
56
- "@opengeni/events": "^0.3.112",
57
- "@opengeni/github": "^0.4.59",
58
- "@opengeni/network": "^0.2.2",
59
- "@opengeni/observability": "^0.7.7",
60
- "@opengeni/runtime": "^1.0.1",
61
- "@opengeni/storage": "^0.2.96",
62
- "@opengeni/xai-subscription": "^0.1.0",
46
+ "@opengeni/agent-proto": "^0.5.1-canary.1",
47
+ "@opengeni/artifact-tool": "^0.3.5-canary.0",
48
+ "@opengeni/capabilities": "^0.3.1-canary.0",
49
+ "@opengeni/codemode": "^0.4.13-canary.0",
50
+ "@opengeni/codex": "^0.2.19-canary.0",
51
+ "@opengeni/config": "^0.19.1-canary.0",
52
+ "@opengeni/contracts": "^2.3.0-canary.0",
53
+ "@opengeni/core": "^2.2.0-canary.0",
54
+ "@opengeni/db": "^3.2.0-canary.0",
55
+ "@opengeni/documents": "^0.7.0-canary.0",
56
+ "@opengeni/events": "^0.3.124-canary.0",
57
+ "@opengeni/github": "^0.5.3-canary.0",
58
+ "@opengeni/network": "^0.2.3-canary.0",
59
+ "@opengeni/observability": "^0.8.4-canary.0",
60
+ "@opengeni/runtime": "^1.3.1-canary.0",
61
+ "@opengeni/storage": "^0.2.106-canary.0",
62
+ "@opengeni/xai-subscription": "^0.1.2-canary.0",
63
63
  "@temporalio/client": "^1.17.0",
64
64
  "better-auth": "^1.6.14",
65
65
  "hono": "^4.12.18",
66
+ "json-bigint": "^1.0.0",
66
67
  "pg": "^8.21.0",
67
68
  "resend": "^6.12.4",
68
69
  "sharp": "^0.34.5",
69
70
  "stripe": "^22.2.0",
71
+ "ws": "8.21.1",
70
72
  "zod": "^4.2.1"
71
73
  },
72
74
  "engines": {
@@ -0,0 +1,23 @@
1
+ export type ApiWebSocketConnection = Readonly<{
2
+ attach(socket: ApiWebSocketLike): void;
3
+ receive(message: string | Uint8Array | ArrayBuffer): void;
4
+ transportClosed(): void;
5
+ }>;
6
+
7
+ export type ApiWebSocketLike = Readonly<{
8
+ data: ApiWebSocketConnection;
9
+ send(data: Uint8Array, compress?: boolean): number;
10
+ close(code?: number, reason?: string): void;
11
+ readonly bufferedAmount?: number;
12
+ getBufferedAmount?(): number;
13
+ }>;
14
+
15
+ export type ApiWebSocketUpgradeServer = Readonly<{
16
+ upgrade(
17
+ request: Request,
18
+ options: Readonly<{
19
+ data: ApiWebSocketConnection;
20
+ headers: HeadersInit;
21
+ }>,
22
+ ): boolean;
23
+ }>;
package/src/app.ts CHANGED
@@ -28,12 +28,13 @@ import {
28
28
  type DocumentServices,
29
29
  } from "@opengeni/documents";
30
30
  import {
31
- CodemodeCallBudgetExceededError,
32
31
  CodemodeOperationConflictError,
33
32
  CodemodeOperationNotExecutableError,
34
33
  CodemodePayloadTooLargeError,
35
34
  CodemodeToolApprovalRequiredError,
36
35
  CodemodeToolNotInCatalogError,
36
+ configureChildLifecycleNotices,
37
+ configureWorkspaceControlRequestLockTimeoutMs,
37
38
  dbSql,
38
39
  getWorkspace,
39
40
  rlsContextForWorkspace,
@@ -42,12 +43,13 @@ import {
42
43
  import { createObservability } from "@opengeni/observability";
43
44
  import { createObjectStorage } from "@opengeni/storage";
44
45
  import { WebStandardStreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/webStandardStreamableHttp.js";
46
+ import { handleMcpRequestWithClientAbort } from "./mcp/request-abort";
45
47
  import { Hono } from "hono";
46
48
  import { bodyLimit } from "hono/body-limit";
47
49
  import { compress } from "hono/compress";
48
50
  import { cors } from "hono/cors";
49
51
  import { HTTPException } from "hono/http-exception";
50
- import { ApiHttpError } from "./http/api-error";
52
+ import { ApiHttpError, workspaceControlBusyHttpError } from "./http/api-error";
51
53
  import type { ContentfulStatusCode } from "hono/utils/http-status";
52
54
  import type { ApiRouteDeps, AppDependencies } from "@opengeni/core";
53
55
  import {
@@ -66,6 +68,7 @@ import { requireLimit } from "@opengeni/core";
66
68
  import { buildOpenGeniMcpServer } from "./mcp/server";
67
69
  import {
68
70
  CodemodeAuthorityError,
71
+ CodemodeCatalogNotReadyError,
69
72
  isCodemodeGrant,
70
73
  readCodemodeOperation,
71
74
  requireActiveCodemodeCatalog,
@@ -95,11 +98,18 @@ import { registerBrowserIdentityRoutes } from "./routes/browser-identities";
95
98
  import { registerBrowserSessionRoutes } from "./routes/browser-sessions";
96
99
  import { registerComputerSessionRoutes } from "./routes/computer-sessions";
97
100
  import { registerGitHubRoutes } from "./routes/github";
101
+ import { registerPersonalGitHubRoutes } from "./routes/personal-github";
102
+ import {
103
+ isPersonalGitHubGitBrokerRequest,
104
+ registerPersonalGitHubGitBrokerRoutes,
105
+ } from "./routes/personal-github-git-broker";
98
106
  import { registerInstallRoutes } from "./routes/install";
99
107
  import { registerApiIntegrationRoutes } from "./routes/api-integrations";
100
108
  import { registerIntegrationFacetRoutes } from "./routes/integration-facets";
101
109
  import { registerInteractionResourceRoutes } from "./routes/interaction-resources";
110
+ import { registerPrReviewRoutes } from "./routes/pr-review";
102
111
  import { registerPackRoutes } from "./routes/packs";
112
+ import { registerAutomationRoutes } from "./routes/automations";
103
113
  import { registerPluginRoutes } from "./routes/plugins";
104
114
  import { registerSkillRoutes } from "./routes/skills";
105
115
  import { registerChannelRoutes } from "./routes/channels";
@@ -109,7 +119,9 @@ import { registerSessionRoutes } from "./routes/sessions";
109
119
  import { registerSocialRoutes } from "./routes/social";
110
120
  import { registerWorkspaceRoutes } from "./routes/workspaces";
111
121
  import { registerWorkspaceInstructionPolicyRoutes } from "./routes/workspace-instruction-policies";
122
+ import { registerWorkspaceLearningRoutes } from "./routes/workspace-learning";
112
123
  import { registerCompanyProfileRoutes } from "./routes/company-profile";
124
+ import { registerCompanyBrainRoutes } from "./routes/company-brain";
113
125
  import { registerSlackTaskPolicyRoutes } from "./routes/slack-task-policy";
114
126
  import { registerWorkspaceStateRoutes } from "./routes/workspace-state";
115
127
  import { registerWorkspaceArtifactRoutes } from "./routes/workspace-artifacts";
@@ -120,6 +132,8 @@ import { registerEditableArtifactRoutes } from "./routes/editable-artifacts";
120
132
  import { registerVideoGenerationRoutes } from "./routes/video-generation";
121
133
  import { registerCanonicalHumanIdentityRoutes } from "./routes/canonical-human-identities";
122
134
  import { registerOrganizationMembershipRoutes } from "./routes/organization-memberships";
135
+ import { registerUserResourceAuthorityRoutes } from "./routes/user-resource-authorities";
136
+ import { registerConnectionAuthorityRoutes } from "./routes/connection-authorities";
123
137
  import { projectClientModel } from "./model-catalog";
124
138
  import { createTranscriptionService } from "./transcription/service";
125
139
  import { createFfmpegTranscriptionSegmenter } from "./transcription/segmenter";
@@ -171,6 +185,14 @@ export function createAppComposition(deps: AppDependencies): {
171
185
  app: Hono;
172
186
  routeDeps: ApiRouteDeps;
173
187
  } {
188
+ // The request-scoped workspace control-prefix budget is validated once by
189
+ // @opengeni/config at boot; install it for every request-scoped db command
190
+ // (Send/Steer/control/queue/settings/delete) built by this app.
191
+ configureWorkspaceControlRequestLockTimeoutMs(deps.settings.workspaceControlLockTimeoutMs);
192
+ // Child lifecycle notice producers (human-input/approval resolutions, direct
193
+ // Pause) run inside API-originated db commands; install the boot-validated
194
+ // rollout flag once for this process.
195
+ configureChildLifecycleNotices({ enabled: deps.settings.childLifecycleNoticesEnabled });
174
196
  const managedAuth = deps.managedAuth ?? createManagedAuth(deps.settings, deps.db);
175
197
  const objectStorage =
176
198
  deps.objectStorage === undefined ? createObjectStorage(deps.settings) : deps.objectStorage;
@@ -332,14 +354,21 @@ export function createAppComposition(deps: AppDependencies): {
332
354
  return middleware(c, next);
333
355
  });
334
356
 
335
- app.use(
336
- "*",
337
- bodyLimit({
338
- maxSize: apiRequestBodyLimitBytes(deps.settings),
339
- onError: (c) =>
340
- c.json({ code: "PAYLOAD_TOO_LARGE", message: "Request body is too large." }, 413),
341
- }),
342
- );
357
+ const standardRequestBodyLimit = bodyLimit({
358
+ maxSize: apiRequestBodyLimitBytes(deps.settings),
359
+ onError: (c) =>
360
+ c.json({ code: "PAYLOAD_TOO_LARGE", message: "Request body is too large." }, 413),
361
+ });
362
+ app.use("*", async (c, next) => {
363
+ // Git packfiles must stay streaming and can legitimately exceed the JSON
364
+ // request ceiling. The exact closed broker routes apply their own method,
365
+ // content-type, authority, and idle-deadline checks.
366
+ if (isPersonalGitHubGitBrokerRequest(c.req.method, new URL(c.req.url).pathname)) {
367
+ await next();
368
+ return;
369
+ }
370
+ return await standardRequestBodyLimit(c, next);
371
+ });
343
372
 
344
373
  // Large catalog, capture, and session-list responses are on the browser's
345
374
  // critical path. Compress JSON at the API boundary while leaving SSE and
@@ -426,7 +455,17 @@ export function createAppComposition(deps: AppDependencies): {
426
455
  }
427
456
  });
428
457
 
429
- app.use("*", requireAccessKey(deps.settings));
458
+ const accessKeyBoundary = requireAccessKey(deps.settings);
459
+ app.use("*", async (c, next) => {
460
+ // Git's credential helper supplies the exact encrypted broker bearer in
461
+ // Authorization. It cannot also supply the deployment access key, and the
462
+ // route performs its own attempt-bound authorization before every use.
463
+ if (isPersonalGitHubGitBrokerRequest(c.req.method, new URL(c.req.url).pathname)) {
464
+ await next();
465
+ return;
466
+ }
467
+ return await accessKeyBoundary(c, next);
468
+ });
430
469
 
431
470
  app.use("/v1/*", async (c, next) => {
432
471
  c.header(OPENGENI_API_CONTRACT_HEADER, OPENGENI_API_CONTRACT_REVISION);
@@ -597,7 +636,7 @@ export function createAppComposition(deps: AppDependencies): {
597
636
  }
598
637
  const workspace = await getWorkspace(routeDeps.db, workspaceId);
599
638
  const workspaceMemoryEnabled = resolveWorkspaceMemoryEnabled(workspace?.settings);
600
- const workspaceMemoryPromptMode = resolveWorkspaceMemoryPromptMode(workspace?.settings);
639
+ const workspaceMemoryPromptMode = resolveWorkspaceMemoryPromptMode();
601
640
  const transport = new WebStandardStreamableHTTPServerTransport({
602
641
  enableJsonResponse: true,
603
642
  });
@@ -607,7 +646,9 @@ export function createAppComposition(deps: AppDependencies): {
607
646
  workspaceMemoryPromptMode,
608
647
  });
609
648
  await mcp.connect(transport);
610
- return await transport.handleRequest(boundedRequest);
649
+ // Bind tool handlers' `extra.signal` to the HTTP client's connection: a
650
+ // worker that drops the call (Steer/Pause) aborts a blocking tool here.
651
+ return await handleMcpRequestWithClientAbort(transport, boundedRequest, c.req.raw.signal);
611
652
  });
612
653
  });
613
654
 
@@ -677,13 +718,17 @@ export function createAppComposition(deps: AppDependencies): {
677
718
  registerWorkspaceRoutes(app, routeDeps);
678
719
  registerInsightsRoutes(app, routeDeps);
679
720
  registerWorkspaceInstructionPolicyRoutes(app, routeDeps);
721
+ registerWorkspaceLearningRoutes(app, routeDeps);
680
722
  registerCompanyProfileRoutes(app, routeDeps);
723
+ registerCompanyBrainRoutes(app, routeDeps);
681
724
  registerSlackTaskPolicyRoutes(app, routeDeps);
682
725
  registerWorkspaceStateRoutes(app, routeDeps);
683
726
  registerMemorySlackPublicationRoutes(app, routeDeps);
684
727
  registerWorkspaceArtifactRoutes(app, routeDeps);
685
728
  registerPreferenceRegistryRoutes(app, routeDeps);
686
729
  registerSocialRoutes(app, routeDeps);
730
+ registerPersonalGitHubRoutes(app, routeDeps);
731
+ registerPersonalGitHubGitBrokerRoutes(app, routeDeps);
687
732
  registerConnectionRoutes(app, routeDeps);
688
733
  registerCapabilityRoutes(app, routeDeps);
689
734
  registerApiIntegrationRoutes(app, routeDeps);
@@ -695,6 +740,8 @@ export function createAppComposition(deps: AppDependencies): {
695
740
  registerChannelRoutes(app, routeDeps);
696
741
  registerRigRoutes(app, routeDeps);
697
742
  registerPackRoutes(app, routeDeps);
743
+ registerAutomationRoutes(app, routeDeps);
744
+ registerPrReviewRoutes(app, routeDeps);
698
745
  registerPluginRoutes(app, routeDeps);
699
746
  registerSkillRoutes(app, routeDeps);
700
747
  registerSessionRoutes(app, routeDeps);
@@ -706,6 +753,8 @@ export function createAppComposition(deps: AppDependencies): {
706
753
  registerVideoGenerationRoutes(app, routeDeps);
707
754
  registerCanonicalHumanIdentityRoutes(app, routeDeps);
708
755
  registerOrganizationMembershipRoutes(app, routeDeps);
756
+ registerUserResourceAuthorityRoutes(app, routeDeps);
757
+ registerConnectionAuthorityRoutes(app, routeDeps);
709
758
  registerSlackInteractionRoutes(app, routeDeps);
710
759
 
711
760
  app.notFound((c) => {
@@ -725,7 +774,10 @@ export function createAppComposition(deps: AppDependencies): {
725
774
  );
726
775
  });
727
776
 
728
- app.onError((error, c) => {
777
+ app.onError((rawError, c) => {
778
+ // One central mapping for every Send/Steer/control route: a bounded
779
+ // control-prefix wait that expired is a known, retryable, not-applied 503.
780
+ const error = workspaceControlBusyHttpError(rawError) ?? rawError;
729
781
  const compactionLock = codexCompactionV2ProviderLockedError(error);
730
782
  const apiError = error instanceof ApiHttpError ? error : null;
731
783
  const status = compactionLock ? 422 : httpStatusForError(error);
@@ -747,7 +799,9 @@ export function createAppComposition(deps: AppDependencies): {
747
799
  ? (boundedPublicMessage(apiError.message) ?? "Request failed.")
748
800
  : publicErrorMessage(error, status),
749
801
  retryable: apiError?.retryable ?? retryableHttpStatus(status),
750
- ...(mutationOutcomeUnknown(error, c.req.method) ? { outcomeUnknown: true } : {}),
802
+ ...((apiError?.outcomeUnknown ?? mutationOutcomeUnknown(error, c.req.method))
803
+ ? { outcomeUnknown: true }
804
+ : {}),
751
805
  requestId,
752
806
  ...(apiError?.details ? { details: apiError.details } : {}),
753
807
  },
@@ -870,7 +924,11 @@ async function requireMcpAccessGrant(
870
924
 
871
925
  function clientAuthConfig(settings: AppDependencies["settings"]) {
872
926
  if (settings.productAccessMode === "managed") {
873
- return { mode: "managedSession" as const, session: "cookie" as const };
927
+ return {
928
+ mode: "managedSession" as const,
929
+ session: "cookie" as const,
930
+ emailVerificationRequired: settings.environment !== "local",
931
+ };
874
932
  }
875
933
  if (settings.productAccessMode === "configured") {
876
934
  return {
@@ -901,18 +959,30 @@ function codemodeHttpError(error: unknown): HTTPException {
901
959
  cause: error,
902
960
  });
903
961
  }
904
- if (
905
- error instanceof CodemodeAuthorityError ||
906
- error instanceof CodemodeOperationNotExecutableError
907
- ) {
962
+ if (error instanceof CodemodeCatalogNotReadyError) {
963
+ return new ApiHttpError(409, {
964
+ code: "conflict",
965
+ message: error.message,
966
+ retryable: true,
967
+ outcomeUnknown: false,
968
+ details: { code: error.code },
969
+ });
970
+ }
971
+ if (error instanceof CodemodeAuthorityError) {
972
+ return new ApiHttpError(error.reason === "invalid_grant" ? 403 : 409, {
973
+ code: error.reason === "invalid_grant" ? "forbidden" : "conflict",
974
+ message: error.message,
975
+ retryable: false,
976
+ outcomeUnknown: false,
977
+ details: { code: error.code },
978
+ });
979
+ }
980
+ if (error instanceof CodemodeOperationNotExecutableError) {
908
981
  return new HTTPException(409, { message: error.message, cause: error });
909
982
  }
910
983
  if (error instanceof CodemodeOperationConflictError) {
911
984
  return new HTTPException(409, { message: error.message, cause: error });
912
985
  }
913
- if (error instanceof CodemodeCallBudgetExceededError) {
914
- return new HTTPException(429, { message: error.message, cause: error });
915
- }
916
986
  if (error instanceof CodemodeToolNotInCatalogError) {
917
987
  return new HTTPException(404, { message: error.message, cause: error });
918
988
  }
@@ -981,6 +1051,9 @@ export function httpStatusForError(error: unknown): number {
981
1051
  if (codexCompactionV2ProviderLockedError(error)) {
982
1052
  return 422;
983
1053
  }
1054
+ if (workspaceControlBusyHttpError(error)) {
1055
+ return 503;
1056
+ }
984
1057
  if (error instanceof HTTPException) {
985
1058
  return error.status;
986
1059
  }
@@ -1171,6 +1244,10 @@ const routeLabelPatterns: Array<{
1171
1244
  label: "/v1/billing/entitlements",
1172
1245
  },
1173
1246
  { pattern: /^\/v1\/webhooks\/stripe$/, label: "/v1/webhooks/stripe" },
1247
+ {
1248
+ pattern: /^\/v1\/workspaces\/[^/]+\/pr-review\/(registrations|repositories)(?:\/[^/]+)?$/,
1249
+ label: (match) => `/v1/workspaces/:workspaceId/pr-review/${match[1]}`,
1250
+ },
1174
1251
  {
1175
1252
  pattern: /^\/v1\/workspaces\/[^/]+\/mcp$/,
1176
1253
  label: "/v1/workspaces/:workspaceId/mcp",
@@ -1372,6 +1449,10 @@ const routeLabelPatterns: Array<{
1372
1449
  pattern: /^\/v1\/workspaces\/[^/]+\/sessions\/[^/]+\/events\/stream$/,
1373
1450
  label: "/v1/workspaces/:workspaceId/sessions/:id/events/stream",
1374
1451
  },
1452
+ {
1453
+ pattern: /^\/v1\/workspaces\/[^/]+\/sessions\/[^/]+\/(visibility|forks)$/,
1454
+ label: (match) => `/v1/workspaces/:workspaceId/sessions/:id/${match[1]}`,
1455
+ },
1375
1456
  {
1376
1457
  pattern: /^\/v1\/workspaces\/[^/]+\/sessions\/[^/]+\/lineage$/,
1377
1458
  label: "/v1/workspaces/:workspaceId/sessions/:id/lineage",
@@ -1380,6 +1461,10 @@ const routeLabelPatterns: Array<{
1380
1461
  pattern: /^\/v1\/workspaces\/[^/]+\/sessions\/[^/]+\/events$/,
1381
1462
  label: "/v1/workspaces/:workspaceId/sessions/:id/events",
1382
1463
  },
1464
+ {
1465
+ pattern: /^\/v1\/workspaces\/[^/]+\/sessions\/[^/]+\/turns$/,
1466
+ label: "/v1/workspaces/:workspaceId/sessions/:id/turns",
1467
+ },
1383
1468
  {
1384
1469
  pattern: /^\/v1\/workspaces\/[^/]+\/sessions\/[^/]+\/queue\/[^/]+\/(move|edit|steer|delete)$/,
1385
1470
  label: "/v1/workspaces/:workspaceId/sessions/:id/queue/:turnId/:action",
@@ -1388,6 +1473,10 @@ const routeLabelPatterns: Array<{
1388
1473
  pattern: /^\/v1\/workspaces\/[^/]+\/sessions\/[^/]+\/queue$/,
1389
1474
  label: "/v1/workspaces/:workspaceId/sessions/:id/queue",
1390
1475
  },
1476
+ {
1477
+ pattern: /^\/v1\/workspaces\/[^/]+\/sessions\/[^/]+\/composer-draft\/submit$/,
1478
+ label: "/v1/workspaces/:workspaceId/sessions/:id/composer-draft/submit",
1479
+ },
1391
1480
  {
1392
1481
  pattern: /^\/v1\/workspaces\/[^/]+\/sessions\/[^/]+\/composer-draft$/,
1393
1482
  label: "/v1/workspaces/:workspaceId/sessions/:id/composer-draft",
@@ -1706,6 +1795,26 @@ const routeLabelPatterns: Array<{
1706
1795
  pattern: /^\/v1\/workspaces\/[^/]+\/connections\/oauth\/start$/,
1707
1796
  label: "/v1/workspaces/:workspaceId/connections/oauth/start",
1708
1797
  },
1798
+ {
1799
+ pattern: /^\/v1\/workspaces\/[^/]+\/connections\/github$/,
1800
+ label: "/v1/workspaces/:workspaceId/connections/github",
1801
+ },
1802
+ {
1803
+ pattern: /^\/v1\/workspaces\/[^/]+\/connections\/github\/oauth\/start$/,
1804
+ label: "/v1/workspaces/:workspaceId/connections/github/oauth/start",
1805
+ },
1806
+ {
1807
+ pattern: /^\/v1\/workspaces\/[^/]+\/connections\/[^/]+\/github\/reconnect$/,
1808
+ label: "/v1/workspaces/:workspaceId/connections/:connectionId/github/reconnect",
1809
+ },
1810
+ {
1811
+ pattern: /^\/v1\/workspaces\/[^/]+\/connections\/[^/]+\/github\/repositories\/verify$/,
1812
+ label: "/v1/workspaces/:workspaceId/connections/:connectionId/github/repositories/verify",
1813
+ },
1814
+ {
1815
+ pattern: /^\/v1\/workspaces\/[^/]+\/connections\/[^/]+\/github\/repositories$/,
1816
+ label: "/v1/workspaces/:workspaceId/connections/:connectionId/github/repositories",
1817
+ },
1709
1818
  {
1710
1819
  pattern: /^\/v1\/workspaces\/[^/]+\/integrations\/oauth\/start$/,
1711
1820
  label: "/v1/workspaces/:workspaceId/integrations/oauth/start",
@@ -1730,6 +1839,18 @@ const routeLabelPatterns: Array<{
1730
1839
  pattern: /^\/v1\/workspaces\/[^/]+\/connections\/[^/]+$/,
1731
1840
  label: "/v1/workspaces/:workspaceId/connections/:connectionId",
1732
1841
  },
1842
+ {
1843
+ pattern: /^\/v1\/git\/personal\/[^/]+\/info\/refs$/,
1844
+ label: "/v1/git/personal/:routeId/info/refs",
1845
+ },
1846
+ {
1847
+ pattern: /^\/v1\/git\/personal\/[^/]+\/git-upload-pack$/,
1848
+ label: "/v1/git/personal/:routeId/git-upload-pack",
1849
+ },
1850
+ {
1851
+ pattern: /^\/v1\/git\/personal\/[^/]+\/git-receive-pack$/,
1852
+ label: "/v1/git/personal/:routeId/git-receive-pack",
1853
+ },
1733
1854
  { pattern: /^\/v1\/catalog-assets\/.+$/, label: "/v1/catalog-assets/*" },
1734
1855
  {
1735
1856
  pattern: /^\/v1\/integrations\/oauth\/callback$/,
@@ -1739,6 +1860,10 @@ const routeLabelPatterns: Array<{
1739
1860
  pattern: /^\/v1\/integrations\/provider-oauth\/callback$/,
1740
1861
  label: "/v1/integrations/provider-oauth/callback",
1741
1862
  },
1863
+ {
1864
+ pattern: /^\/v1\/integrations\/github-personal\/oauth\/callback$/,
1865
+ label: "/v1/integrations/github-personal/oauth/callback",
1866
+ },
1742
1867
  {
1743
1868
  pattern: /^\/v1\/integrations\/google-drive\/callback$/,
1744
1869
  label: "/v1/integrations/google-drive/callback",
@@ -1779,6 +1904,14 @@ const routeLabelPatterns: Array<{
1779
1904
  pattern: /^\/v1\/workspaces\/[^/]+\/machines\/[^/]+\/metrics\/series$/,
1780
1905
  label: "/v1/workspaces/:workspaceId/machines/:enrollmentId/metrics/series",
1781
1906
  },
1907
+ {
1908
+ pattern: /^\/v1\/workspaces\/[^/]+\/machines\/[^/]+\/operation-policy$/,
1909
+ label: "/v1/workspaces/:workspaceId/machines/:enrollmentId/operation-policy",
1910
+ },
1911
+ {
1912
+ pattern: /^\/v1\/workspaces\/[^/]+\/machines\/[^/]+\/update$/,
1913
+ label: "/v1/workspaces/:workspaceId/machines/:enrollmentId/update",
1914
+ },
1782
1915
  {
1783
1916
  pattern: /^\/v1\/workspaces\/[^/]+\/machines$/,
1784
1917
  label: "/v1/workspaces/:workspaceId/machines",
@@ -1796,6 +1929,10 @@ const routeLabelPatterns: Array<{
1796
1929
  pattern: /^\/v1\/github\/oauth\/callback$/,
1797
1930
  label: "/v1/github/oauth/callback",
1798
1931
  },
1932
+ {
1933
+ pattern: /^\/v1\/workspaces\/[^/]+$/,
1934
+ label: "/v1/workspaces/:workspaceId",
1935
+ },
1799
1936
  ];
1800
1937
 
1801
1938
  export function routeLabel(pathname: string): string {
@@ -1824,6 +1961,7 @@ export function isApiContractProtectedMutation(method: string, pathname: string)
1824
1961
  pathname.startsWith("/v1/auth/") ||
1825
1962
  pathname.startsWith("/v1/webhooks/") ||
1826
1963
  pathname.startsWith("/v1/integrations/oauth/") ||
1964
+ isPersonalGitHubGitBrokerRequest(method, pathname) ||
1827
1965
  pathname === "/v1/integrations/slack/events" ||
1828
1966
  pathname === "/v1/integrations/slack/commands" ||
1829
1967
  pathname === "/v1/integrations/slack/interactions" ||
@@ -20,10 +20,25 @@ import { decideCanonicalHumanSessionAdmission } from "./canonical-human-session-
20
20
  // importers (`app.ts`) keep the same import path.
21
21
  export type { ManagedAuth };
22
22
 
23
+ export function managedAuthRequiresEmailVerification(
24
+ settings: Pick<Settings, "environment">,
25
+ ): boolean {
26
+ return settings.environment !== "local";
27
+ }
28
+
29
+ export function managedAuthUserCreateOverride(
30
+ settings: Pick<Settings, "environment">,
31
+ user: { emailVerified: boolean } & Record<string, unknown>,
32
+ ): { data: typeof user } | undefined {
33
+ if (managedAuthRequiresEmailVerification(settings)) return undefined;
34
+ return { data: { ...user, emailVerified: true } };
35
+ }
36
+
23
37
  export function createManagedAuth(settings: Settings, db: Database): ManagedAuth | null {
24
38
  if (settings.productAccessMode !== "managed") {
25
39
  return null;
26
40
  }
41
+ const requireEmailVerification = managedAuthRequiresEmailVerification(settings);
27
42
  const pool = new Pool({ connectionString: settings.databaseUrl });
28
43
  return betterAuth({
29
44
  appName: "OpenGeni",
@@ -124,10 +139,13 @@ export function createManagedAuth(settings: Settings, db: Database): ManagedAuth
124
139
  },
125
140
  emailAndPassword: {
126
141
  enabled: true,
127
- requireEmailVerification: true,
142
+ // Local managed mode exists so the complete human/org tenancy flow can
143
+ // be exercised without first configuring a transactional-email vendor.
144
+ // Every non-local deployment retains the verified-email boundary.
145
+ requireEmailVerification,
128
146
  revokeSessionsOnPasswordReset: true,
129
147
  onExistingUserSignUp: async ({ user }) => {
130
- if (!user.emailVerified) {
148
+ if (requireEmailVerification && !user.emailVerified) {
131
149
  const url = await verificationUrl(settings, user.email);
132
150
  await sendEmail(settings, {
133
151
  to: user.email,
@@ -147,7 +165,7 @@ export function createManagedAuth(settings: Settings, db: Database): ManagedAuth
147
165
  },
148
166
  },
149
167
  emailVerification: {
150
- sendOnSignUp: true,
168
+ sendOnSignUp: requireEmailVerification,
151
169
  sendVerificationEmail: async ({ user, url }) => {
152
170
  await sendEmail(settings, {
153
171
  to: user.email,
@@ -161,6 +179,7 @@ export function createManagedAuth(settings: Settings, db: Database): ManagedAuth
161
179
  userId: user.id,
162
180
  email: user.email,
163
181
  name: user.name,
182
+ emailVerified: true,
164
183
  });
165
184
  },
166
185
  },
@@ -239,11 +258,14 @@ export function createManagedAuth(settings: Settings, db: Database): ManagedAuth
239
258
  },
240
259
  user: {
241
260
  create: {
261
+ before: async (user) => managedAuthUserCreateOverride(settings, user),
242
262
  after: async (user) => {
263
+ if (!user.emailVerified) return;
243
264
  await ensureManagedAccessForUser(db, {
244
265
  userId: user.id,
245
266
  email: user.email,
246
267
  name: user.name,
268
+ emailVerified: true,
247
269
  });
248
270
  },
249
271
  },