@opengeni/api-router 0.30.1 → 2.1.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 (167) 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-2PQMSRCB.js → chunk-QKDFBBUE.js} +29110 -18137
  5. package/dist/chunk-QKDFBBUE.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-office-import.d.ts +7 -4
  13. package/dist/editable-artifact-websocket.d.ts +5 -15
  14. package/dist/editable-artifact-workspace-files.d.ts +1 -1
  15. package/dist/http/api-error.d.ts +10 -0
  16. package/dist/http/cors.d.ts +5 -0
  17. package/dist/http/interaction-control-error.d.ts +13 -0
  18. package/dist/http/sse.d.ts +2 -3
  19. package/dist/index.d.ts +5 -2
  20. package/dist/index.js +364 -86
  21. package/dist/index.js.map +1 -1
  22. package/dist/integrations/atlassian.d.ts +17 -0
  23. package/dist/integrations/fiken.d.ts +7 -2
  24. package/dist/integrations/google-drive.d.ts +19 -0
  25. package/dist/integrations/oauth-client.d.ts +22 -3
  26. package/dist/integrations/oauth-profiles.d.ts +195 -0
  27. package/dist/integrations/personal-github-repositories.d.ts +45 -0
  28. package/dist/integrations/personal-github.d.ts +40 -0
  29. package/dist/integrations/pr-review-provider.d.ts +22 -0
  30. package/dist/integrations/provider-oauth.d.ts +8 -0
  31. package/dist/integrations/slack-app-home.d.ts +24 -0
  32. package/dist/integrations/slack-bot.d.ts +149 -9
  33. package/dist/integrations/slack-interactions.d.ts +10 -4
  34. package/dist/integrations/slack-routing.d.ts +120 -0
  35. package/dist/integrations/social-oauth.d.ts +5 -0
  36. package/dist/interaction-frame-proxy.d.ts +68 -0
  37. package/dist/mcp/company-brain-governed-writes.d.ts +26 -0
  38. package/dist/mcp/company-profile-agent-admin.d.ts +44 -0
  39. package/dist/mcp/documents.d.ts +1 -0
  40. package/dist/mcp/remember.d.ts +28 -0
  41. package/dist/mcp/request-abort.d.ts +19 -0
  42. package/dist/mcp/scheduled-task-view.d.ts +4 -4
  43. package/dist/mcp/server.d.ts +15 -2
  44. package/dist/mcp/session-view.d.ts +4 -0
  45. package/dist/mcp/session-wait.d.ts +137 -0
  46. package/dist/routes/automations.d.ts +4 -0
  47. package/dist/routes/billing.d.ts +5 -0
  48. package/dist/routes/browser-sessions.d.ts +17 -3
  49. package/dist/routes/company-brain.d.ts +3 -0
  50. package/dist/routes/company-profile.d.ts +2 -1
  51. package/dist/routes/connection-authorities.d.ts +3 -0
  52. package/dist/routes/files.d.ts +4 -0
  53. package/dist/routes/personal-github-git-broker.d.ts +29 -0
  54. package/dist/routes/personal-github.d.ts +3 -0
  55. package/dist/routes/pr-review.d.ts +3 -0
  56. package/dist/routes/sessions.d.ts +1 -0
  57. package/dist/routes/user-resource-authorities.d.ts +3 -0
  58. package/dist/routes/workspace-capture.d.ts +10 -2
  59. package/dist/routes/workspace-learning.d.ts +3 -0
  60. package/dist/routes/workspaces.d.ts +19 -0
  61. package/dist/sandbox/channel-a.d.ts +11 -1
  62. package/dist/sandbox/connection-authority.d.ts +3 -0
  63. package/dist/sandbox/enrollment.d.ts +2 -0
  64. package/dist/sandbox/machines.d.ts +2 -2
  65. package/dist/sandbox/metrics-ingestion.d.ts +10 -20
  66. package/dist/sandbox/viewer.d.ts +24 -1
  67. package/dist/sandbox-file-artifacts.d.ts +11 -0
  68. package/dist/scheduled-task-deletion.d.ts +14 -0
  69. package/dist/slack-reaction-files.d.ts +1 -1
  70. package/dist/temporal-schedule-cleanup.d.ts +1 -0
  71. package/package.json +20 -18
  72. package/src/api-websocket.ts +23 -0
  73. package/src/app.ts +160 -27
  74. package/src/auth/managed-auth.ts +25 -3
  75. package/src/codemode.ts +53 -23
  76. package/src/codex-realtime.ts +8 -2
  77. package/src/company-brain-okf.ts +340 -0
  78. package/src/connected-machine-computer-access.ts +33 -0
  79. package/src/connection-authority-owner.ts +61 -0
  80. package/src/connection-ownership.ts +180 -0
  81. package/src/controller-data-plane.ts +47 -0
  82. package/src/editable-artifact-native-kernel.ts +26 -15
  83. package/src/editable-artifact-office-import.ts +77 -9
  84. package/src/editable-artifact-production.ts +13 -7
  85. package/src/editable-artifact-websocket.ts +11 -18
  86. package/src/editable-artifact-workspace-files.ts +31 -6
  87. package/src/http/api-error.ts +28 -0
  88. package/src/http/auth.ts +4 -0
  89. package/src/http/cors.ts +35 -0
  90. package/src/http/interaction-control-error.ts +164 -0
  91. package/src/http/sse.ts +112 -66
  92. package/src/index.ts +46 -6
  93. package/src/integrations/atlassian.ts +146 -35
  94. package/src/integrations/fiken.ts +102 -22
  95. package/src/integrations/google-drive.ts +416 -101
  96. package/src/integrations/oauth-client.ts +272 -143
  97. package/src/integrations/oauth-profiles.ts +477 -0
  98. package/src/integrations/personal-github-repositories.ts +445 -0
  99. package/src/integrations/personal-github.ts +705 -0
  100. package/src/integrations/pr-review-provider.ts +246 -0
  101. package/src/integrations/provider-oauth.ts +121 -5
  102. package/src/integrations/slack-app-home.ts +300 -0
  103. package/src/integrations/slack-bot.ts +337 -57
  104. package/src/integrations/slack-interactions.ts +1274 -214
  105. package/src/integrations/slack-routing.ts +324 -0
  106. package/src/integrations/social-oauth.ts +25 -0
  107. package/src/interaction-frame-proxy.ts +409 -0
  108. package/src/mcp/company-brain-governed-writes.ts +262 -0
  109. package/src/mcp/company-profile-agent-admin.ts +205 -0
  110. package/src/mcp/documents.ts +37 -6
  111. package/src/mcp/files.ts +22 -2
  112. package/src/mcp/remember.ts +181 -0
  113. package/src/mcp/request-abort.ts +44 -0
  114. package/src/mcp/scheduled-task-view.ts +1 -0
  115. package/src/mcp/server.ts +719 -369
  116. package/src/mcp/session-view.ts +54 -0
  117. package/src/mcp/session-wait.ts +554 -0
  118. package/src/model-catalog.ts +20 -12
  119. package/src/routes/api-integrations.ts +4 -0
  120. package/src/routes/api-keys.ts +1 -0
  121. package/src/routes/automations.ts +534 -0
  122. package/src/routes/billing.ts +57 -1
  123. package/src/routes/browser-sessions.ts +388 -98
  124. package/src/routes/channels.ts +17 -1
  125. package/src/routes/codex.ts +50 -0
  126. package/src/routes/company-brain.ts +367 -0
  127. package/src/routes/company-profile.ts +1 -1
  128. package/src/routes/computer-sessions.ts +320 -93
  129. package/src/routes/connection-authorities.ts +139 -0
  130. package/src/routes/connections.ts +145 -34
  131. package/src/routes/documents.ts +221 -3
  132. package/src/routes/editable-artifacts.ts +11 -3
  133. package/src/routes/enrollments.ts +116 -26
  134. package/src/routes/environments.ts +127 -55
  135. package/src/routes/files.ts +152 -27
  136. package/src/routes/install.ts +82 -28
  137. package/src/routes/integration-facets.ts +29 -0
  138. package/src/routes/interaction-resources.ts +20 -2
  139. package/src/routes/machines.ts +275 -13
  140. package/src/routes/organization-memberships.ts +717 -28
  141. package/src/routes/packs.ts +4 -3
  142. package/src/routes/personal-github-git-broker.ts +785 -0
  143. package/src/routes/personal-github.ts +319 -0
  144. package/src/routes/pr-review.ts +531 -0
  145. package/src/routes/rigs.ts +104 -39
  146. package/src/routes/scheduled-tasks.ts +19 -21
  147. package/src/routes/sessions.ts +777 -40
  148. package/src/routes/social.ts +14 -2
  149. package/src/routes/supergrok.ts +23 -4
  150. package/src/routes/transcription-recordings.ts +8 -2
  151. package/src/routes/user-resource-authorities.ts +138 -0
  152. package/src/routes/workspace-artifacts.ts +4 -1
  153. package/src/routes/workspace-capture.ts +21 -0
  154. package/src/routes/workspace-learning.ts +228 -0
  155. package/src/routes/workspaces.ts +59 -6
  156. package/src/sandbox/auth-callout.ts +38 -12
  157. package/src/sandbox/channel-a.ts +242 -19
  158. package/src/sandbox/connection-authority.ts +3 -0
  159. package/src/sandbox/enrollment.ts +15 -3
  160. package/src/sandbox/machines.ts +186 -64
  161. package/src/sandbox/metrics-ingestion.ts +220 -58
  162. package/src/sandbox/viewer.ts +243 -19
  163. package/src/sandbox-file-artifacts.ts +329 -0
  164. package/src/scheduled-task-deletion.ts +127 -0
  165. package/src/slack-reaction-files.ts +16 -5
  166. package/src/temporal-schedule-cleanup.ts +13 -0
  167. package/dist/chunk-2PQMSRCB.js.map +0 -1
@@ -51,7 +51,10 @@ export type ViewerAttachResult = {
51
51
  * cannot change manifest environment variables" whenever a viewer attach (an open
52
52
  * session page) won the cold-create race against the first turn.
53
53
  */
54
- export declare function sessionAttachEnvironment(services: ViewerServices, workspaceId: string, session: Session): Promise<Record<string, string>>;
54
+ export declare function sessionAttachEnvironment(services: ViewerServices, workspaceId: string, session: Session,
55
+ /** The authenticated route subject driving this attach (0282); recorded on
56
+ * the materialization audit fact. Null records the legacy service sentinel. */
57
+ attachSubjectId: string | null): Promise<Record<string, string>>;
55
58
  /**
56
59
  * Acquire a `viewer` holder on the group lease, spinning up the box IN-PROCESS
57
60
  * when cold. Mirrors the worker's resumeBoxForTurn spawner/attached branches,
@@ -65,6 +68,9 @@ export declare function attachViewer(services: ViewerServices, input: {
65
68
  workspaceId: string;
66
69
  session: Session;
67
70
  viewerId?: string;
71
+ /** The authenticated subject attaching this viewer (0281): recorded on
72
+ * the holder row together with the live session authority epoch. */
73
+ viewerSubjectId?: string;
68
74
  /** Cancel lifecycle waiting when the originating HTTP request disconnects. */
69
75
  waitSignal?: AbortSignal;
70
76
  }): Promise<ViewerAttachResult>;
@@ -84,6 +90,10 @@ export declare function ensureSessionGroupReady(services: ViewerServices, input:
84
90
  accountId: string;
85
91
  workspaceId: string;
86
92
  session: Session;
93
+ /** The authenticated subject driving the fleet attach/swap (0282): recorded
94
+ * on the viewer holder and the materialization audit fact. Omitting it
95
+ * records the explicit service sentinel, so forward the route subject. */
96
+ subjectId?: string | null;
87
97
  }): Promise<SessionGroupReadinessHold>;
88
98
  /**
89
99
  * Refresh a viewer holder's TTL (the app-level viewer heartbeat). Epoch-fenced:
@@ -148,6 +158,11 @@ export type DesktopStreamMint = {
148
158
  export type MintDesktopStreamInput = {
149
159
  accountId: string;
150
160
  workspaceId: string;
161
+ resourceSubjectId?: string;
162
+ /** True when the route grant is a signed delegated token (metadata.delegated):
163
+ * its authority is the token, not membership rows, so the mint-time
164
+ * live-authority recheck does not apply. */
165
+ resourceSubjectDelegated?: boolean;
151
166
  session: Session;
152
167
  /** The viewer holder id the scoped token is minted for. */
153
168
  viewerId: string;
@@ -197,6 +212,9 @@ type SelfhostedStreamMint = Omit<TerminalStreamMint, "transport">;
197
212
  export type MintTerminalStreamInput = {
198
213
  accountId: string;
199
214
  workspaceId: string;
215
+ resourceSubjectId?: string;
216
+ /** See MintDesktopStreamInput.resourceSubjectDelegated. */
217
+ resourceSubjectDelegated?: boolean;
200
218
  session: Session;
201
219
  /** The viewer holder / principal id the scoped token is minted for. */
202
220
  viewerId: string;
@@ -233,6 +251,11 @@ export type MintSelfhostedStreamInput = {
233
251
  * THIS as its leaseEpoch claim so the relay rejects a stale-epoch (swapped-away)
234
252
  * viewer. */
235
253
  activeEpoch: number;
254
+ /** 0281 viewer authority claims stamped into the relay stream token. */
255
+ viewerAuthority?: {
256
+ subjectId?: string;
257
+ authorityEpoch: number;
258
+ };
236
259
  /** The exposed stream port (6080 desktop / 7681 terminal). */
237
260
  port: number;
238
261
  /** The resolvable selfhosted session (the routing proxy resolves the active
@@ -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.1",
3
+ "version": "2.1.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.8",
48
- "@opengeni/capabilities": "^0.2.0",
49
- "@opengeni/codemode": "^0.4.2",
50
- "@opengeni/codex": "^0.2.17",
51
- "@opengeni/config": "^0.16.2",
52
- "@opengeni/contracts": "^0.50.0",
53
- "@opengeni/core": "^0.28.1",
54
- "@opengeni/db": "^0.36.1",
55
- "@opengeni/documents": "^0.5.39",
56
- "@opengeni/events": "^0.3.110",
57
- "@opengeni/github": "^0.4.57",
58
- "@opengeni/network": "^0.2.2",
59
- "@opengeni/observability": "^0.7.5",
60
- "@opengeni/runtime": "^0.23.1",
61
- "@opengeni/storage": "^0.2.94",
62
- "@opengeni/xai-subscription": "^0.1.0",
46
+ "@opengeni/agent-proto": "^0.5.1-canary.0",
47
+ "@opengeni/artifact-tool": "^0.3.4-canary.0",
48
+ "@opengeni/capabilities": "^0.3.0-canary.0",
49
+ "@opengeni/codemode": "^0.4.12-canary.0",
50
+ "@opengeni/codex": "^0.2.18-canary.0",
51
+ "@opengeni/config": "^0.19.0-canary.0",
52
+ "@opengeni/contracts": "^2.2.0-canary.0",
53
+ "@opengeni/core": "^2.1.0-canary.0",
54
+ "@opengeni/db": "^3.1.0-canary.0",
55
+ "@opengeni/documents": "^0.6.9-canary.0",
56
+ "@opengeni/events": "^0.3.123-canary.0",
57
+ "@opengeni/github": "^0.5.2-canary.0",
58
+ "@opengeni/network": "^0.2.2-canary.0",
59
+ "@opengeni/observability": "^0.8.3-canary.0",
60
+ "@opengeni/runtime": "^1.3.0-canary.0",
61
+ "@opengeni/storage": "^0.2.105-canary.0",
62
+ "@opengeni/xai-subscription": "^0.1.1-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
@@ -23,17 +23,18 @@ import {
23
23
  } from "@opengeni/contracts";
24
24
  import {
25
25
  createDocumentServices,
26
- getDocument,
26
+ getDocumentForIndexing,
27
27
  indexDocumentNow,
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;
@@ -204,9 +226,12 @@ export function createAppComposition(deps: AppDependencies): {
204
226
  if (context.accountId !== accountId) {
205
227
  throw new Error("document account/workspace authority mismatch");
206
228
  }
207
- const claimedDocument = await getDocument(deps.db, workspaceId, documentId, {
208
- viewerSubjectId: authoritySubjectId,
209
- });
229
+ // This is a metadata-only authority-tuple fence. Workspace and
230
+ // organization documents intentionally have no authority subject, so a
231
+ // viewer-scoped public read cannot supply the immutable human needed for
232
+ // Drive ACL evaluation. The byte boundary below reconstructs that
233
+ // subject from the stored document through indexDocumentNow.
234
+ const claimedDocument = await getDocumentForIndexing(deps.db, workspaceId, documentId);
210
235
  if (
211
236
  !claimedDocument ||
212
237
  claimedDocument.authorityKind !== authorityKind ||
@@ -329,14 +354,21 @@ export function createAppComposition(deps: AppDependencies): {
329
354
  return middleware(c, next);
330
355
  });
331
356
 
332
- app.use(
333
- "*",
334
- bodyLimit({
335
- maxSize: apiRequestBodyLimitBytes(deps.settings),
336
- onError: (c) =>
337
- c.json({ code: "PAYLOAD_TOO_LARGE", message: "Request body is too large." }, 413),
338
- }),
339
- );
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
+ });
340
372
 
341
373
  // Large catalog, capture, and session-list responses are on the browser's
342
374
  // critical path. Compress JSON at the API boundary while leaving SSE and
@@ -423,7 +455,17 @@ export function createAppComposition(deps: AppDependencies): {
423
455
  }
424
456
  });
425
457
 
426
- 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
+ });
427
469
 
428
470
  app.use("/v1/*", async (c, next) => {
429
471
  c.header(OPENGENI_API_CONTRACT_HEADER, OPENGENI_API_CONTRACT_REVISION);
@@ -594,7 +636,7 @@ export function createAppComposition(deps: AppDependencies): {
594
636
  }
595
637
  const workspace = await getWorkspace(routeDeps.db, workspaceId);
596
638
  const workspaceMemoryEnabled = resolveWorkspaceMemoryEnabled(workspace?.settings);
597
- const workspaceMemoryPromptMode = resolveWorkspaceMemoryPromptMode(workspace?.settings);
639
+ const workspaceMemoryPromptMode = resolveWorkspaceMemoryPromptMode();
598
640
  const transport = new WebStandardStreamableHTTPServerTransport({
599
641
  enableJsonResponse: true,
600
642
  });
@@ -604,7 +646,9 @@ export function createAppComposition(deps: AppDependencies): {
604
646
  workspaceMemoryPromptMode,
605
647
  });
606
648
  await mcp.connect(transport);
607
- 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);
608
652
  });
609
653
  });
610
654
 
@@ -674,13 +718,17 @@ export function createAppComposition(deps: AppDependencies): {
674
718
  registerWorkspaceRoutes(app, routeDeps);
675
719
  registerInsightsRoutes(app, routeDeps);
676
720
  registerWorkspaceInstructionPolicyRoutes(app, routeDeps);
721
+ registerWorkspaceLearningRoutes(app, routeDeps);
677
722
  registerCompanyProfileRoutes(app, routeDeps);
723
+ registerCompanyBrainRoutes(app, routeDeps);
678
724
  registerSlackTaskPolicyRoutes(app, routeDeps);
679
725
  registerWorkspaceStateRoutes(app, routeDeps);
680
726
  registerMemorySlackPublicationRoutes(app, routeDeps);
681
727
  registerWorkspaceArtifactRoutes(app, routeDeps);
682
728
  registerPreferenceRegistryRoutes(app, routeDeps);
683
729
  registerSocialRoutes(app, routeDeps);
730
+ registerPersonalGitHubRoutes(app, routeDeps);
731
+ registerPersonalGitHubGitBrokerRoutes(app, routeDeps);
684
732
  registerConnectionRoutes(app, routeDeps);
685
733
  registerCapabilityRoutes(app, routeDeps);
686
734
  registerApiIntegrationRoutes(app, routeDeps);
@@ -692,6 +740,8 @@ export function createAppComposition(deps: AppDependencies): {
692
740
  registerChannelRoutes(app, routeDeps);
693
741
  registerRigRoutes(app, routeDeps);
694
742
  registerPackRoutes(app, routeDeps);
743
+ registerAutomationRoutes(app, routeDeps);
744
+ registerPrReviewRoutes(app, routeDeps);
695
745
  registerPluginRoutes(app, routeDeps);
696
746
  registerSkillRoutes(app, routeDeps);
697
747
  registerSessionRoutes(app, routeDeps);
@@ -703,6 +753,8 @@ export function createAppComposition(deps: AppDependencies): {
703
753
  registerVideoGenerationRoutes(app, routeDeps);
704
754
  registerCanonicalHumanIdentityRoutes(app, routeDeps);
705
755
  registerOrganizationMembershipRoutes(app, routeDeps);
756
+ registerUserResourceAuthorityRoutes(app, routeDeps);
757
+ registerConnectionAuthorityRoutes(app, routeDeps);
706
758
  registerSlackInteractionRoutes(app, routeDeps);
707
759
 
708
760
  app.notFound((c) => {
@@ -722,7 +774,10 @@ export function createAppComposition(deps: AppDependencies): {
722
774
  );
723
775
  });
724
776
 
725
- 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;
726
781
  const compactionLock = codexCompactionV2ProviderLockedError(error);
727
782
  const apiError = error instanceof ApiHttpError ? error : null;
728
783
  const status = compactionLock ? 422 : httpStatusForError(error);
@@ -744,7 +799,9 @@ export function createAppComposition(deps: AppDependencies): {
744
799
  ? (boundedPublicMessage(apiError.message) ?? "Request failed.")
745
800
  : publicErrorMessage(error, status),
746
801
  retryable: apiError?.retryable ?? retryableHttpStatus(status),
747
- ...(mutationOutcomeUnknown(error, c.req.method) ? { outcomeUnknown: true } : {}),
802
+ ...((apiError?.outcomeUnknown ?? mutationOutcomeUnknown(error, c.req.method))
803
+ ? { outcomeUnknown: true }
804
+ : {}),
748
805
  requestId,
749
806
  ...(apiError?.details ? { details: apiError.details } : {}),
750
807
  },
@@ -867,7 +924,11 @@ async function requireMcpAccessGrant(
867
924
 
868
925
  function clientAuthConfig(settings: AppDependencies["settings"]) {
869
926
  if (settings.productAccessMode === "managed") {
870
- 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
+ };
871
932
  }
872
933
  if (settings.productAccessMode === "configured") {
873
934
  return {
@@ -898,18 +959,30 @@ function codemodeHttpError(error: unknown): HTTPException {
898
959
  cause: error,
899
960
  });
900
961
  }
901
- if (
902
- error instanceof CodemodeAuthorityError ||
903
- error instanceof CodemodeOperationNotExecutableError
904
- ) {
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) {
905
981
  return new HTTPException(409, { message: error.message, cause: error });
906
982
  }
907
983
  if (error instanceof CodemodeOperationConflictError) {
908
984
  return new HTTPException(409, { message: error.message, cause: error });
909
985
  }
910
- if (error instanceof CodemodeCallBudgetExceededError) {
911
- return new HTTPException(429, { message: error.message, cause: error });
912
- }
913
986
  if (error instanceof CodemodeToolNotInCatalogError) {
914
987
  return new HTTPException(404, { message: error.message, cause: error });
915
988
  }
@@ -978,6 +1051,9 @@ export function httpStatusForError(error: unknown): number {
978
1051
  if (codexCompactionV2ProviderLockedError(error)) {
979
1052
  return 422;
980
1053
  }
1054
+ if (workspaceControlBusyHttpError(error)) {
1055
+ return 503;
1056
+ }
981
1057
  if (error instanceof HTTPException) {
982
1058
  return error.status;
983
1059
  }
@@ -1168,6 +1244,10 @@ const routeLabelPatterns: Array<{
1168
1244
  label: "/v1/billing/entitlements",
1169
1245
  },
1170
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
+ },
1171
1251
  {
1172
1252
  pattern: /^\/v1\/workspaces\/[^/]+\/mcp$/,
1173
1253
  label: "/v1/workspaces/:workspaceId/mcp",
@@ -1369,6 +1449,10 @@ const routeLabelPatterns: Array<{
1369
1449
  pattern: /^\/v1\/workspaces\/[^/]+\/sessions\/[^/]+\/events\/stream$/,
1370
1450
  label: "/v1/workspaces/:workspaceId/sessions/:id/events/stream",
1371
1451
  },
1452
+ {
1453
+ pattern: /^\/v1\/workspaces\/[^/]+\/sessions\/[^/]+\/(visibility|forks)$/,
1454
+ label: (match) => `/v1/workspaces/:workspaceId/sessions/:id/${match[1]}`,
1455
+ },
1372
1456
  {
1373
1457
  pattern: /^\/v1\/workspaces\/[^/]+\/sessions\/[^/]+\/lineage$/,
1374
1458
  label: "/v1/workspaces/:workspaceId/sessions/:id/lineage",
@@ -1385,6 +1469,10 @@ const routeLabelPatterns: Array<{
1385
1469
  pattern: /^\/v1\/workspaces\/[^/]+\/sessions\/[^/]+\/queue$/,
1386
1470
  label: "/v1/workspaces/:workspaceId/sessions/:id/queue",
1387
1471
  },
1472
+ {
1473
+ pattern: /^\/v1\/workspaces\/[^/]+\/sessions\/[^/]+\/composer-draft\/submit$/,
1474
+ label: "/v1/workspaces/:workspaceId/sessions/:id/composer-draft/submit",
1475
+ },
1388
1476
  {
1389
1477
  pattern: /^\/v1\/workspaces\/[^/]+\/sessions\/[^/]+\/composer-draft$/,
1390
1478
  label: "/v1/workspaces/:workspaceId/sessions/:id/composer-draft",
@@ -1703,6 +1791,26 @@ const routeLabelPatterns: Array<{
1703
1791
  pattern: /^\/v1\/workspaces\/[^/]+\/connections\/oauth\/start$/,
1704
1792
  label: "/v1/workspaces/:workspaceId/connections/oauth/start",
1705
1793
  },
1794
+ {
1795
+ pattern: /^\/v1\/workspaces\/[^/]+\/connections\/github$/,
1796
+ label: "/v1/workspaces/:workspaceId/connections/github",
1797
+ },
1798
+ {
1799
+ pattern: /^\/v1\/workspaces\/[^/]+\/connections\/github\/oauth\/start$/,
1800
+ label: "/v1/workspaces/:workspaceId/connections/github/oauth/start",
1801
+ },
1802
+ {
1803
+ pattern: /^\/v1\/workspaces\/[^/]+\/connections\/[^/]+\/github\/reconnect$/,
1804
+ label: "/v1/workspaces/:workspaceId/connections/:connectionId/github/reconnect",
1805
+ },
1806
+ {
1807
+ pattern: /^\/v1\/workspaces\/[^/]+\/connections\/[^/]+\/github\/repositories\/verify$/,
1808
+ label: "/v1/workspaces/:workspaceId/connections/:connectionId/github/repositories/verify",
1809
+ },
1810
+ {
1811
+ pattern: /^\/v1\/workspaces\/[^/]+\/connections\/[^/]+\/github\/repositories$/,
1812
+ label: "/v1/workspaces/:workspaceId/connections/:connectionId/github/repositories",
1813
+ },
1706
1814
  {
1707
1815
  pattern: /^\/v1\/workspaces\/[^/]+\/integrations\/oauth\/start$/,
1708
1816
  label: "/v1/workspaces/:workspaceId/integrations/oauth/start",
@@ -1727,6 +1835,18 @@ const routeLabelPatterns: Array<{
1727
1835
  pattern: /^\/v1\/workspaces\/[^/]+\/connections\/[^/]+$/,
1728
1836
  label: "/v1/workspaces/:workspaceId/connections/:connectionId",
1729
1837
  },
1838
+ {
1839
+ pattern: /^\/v1\/git\/personal\/[^/]+\/info\/refs$/,
1840
+ label: "/v1/git/personal/:routeId/info/refs",
1841
+ },
1842
+ {
1843
+ pattern: /^\/v1\/git\/personal\/[^/]+\/git-upload-pack$/,
1844
+ label: "/v1/git/personal/:routeId/git-upload-pack",
1845
+ },
1846
+ {
1847
+ pattern: /^\/v1\/git\/personal\/[^/]+\/git-receive-pack$/,
1848
+ label: "/v1/git/personal/:routeId/git-receive-pack",
1849
+ },
1730
1850
  { pattern: /^\/v1\/catalog-assets\/.+$/, label: "/v1/catalog-assets/*" },
1731
1851
  {
1732
1852
  pattern: /^\/v1\/integrations\/oauth\/callback$/,
@@ -1736,6 +1856,10 @@ const routeLabelPatterns: Array<{
1736
1856
  pattern: /^\/v1\/integrations\/provider-oauth\/callback$/,
1737
1857
  label: "/v1/integrations/provider-oauth/callback",
1738
1858
  },
1859
+ {
1860
+ pattern: /^\/v1\/integrations\/github-personal\/oauth\/callback$/,
1861
+ label: "/v1/integrations/github-personal/oauth/callback",
1862
+ },
1739
1863
  {
1740
1864
  pattern: /^\/v1\/integrations\/google-drive\/callback$/,
1741
1865
  label: "/v1/integrations/google-drive/callback",
@@ -1776,6 +1900,14 @@ const routeLabelPatterns: Array<{
1776
1900
  pattern: /^\/v1\/workspaces\/[^/]+\/machines\/[^/]+\/metrics\/series$/,
1777
1901
  label: "/v1/workspaces/:workspaceId/machines/:enrollmentId/metrics/series",
1778
1902
  },
1903
+ {
1904
+ pattern: /^\/v1\/workspaces\/[^/]+\/machines\/[^/]+\/operation-policy$/,
1905
+ label: "/v1/workspaces/:workspaceId/machines/:enrollmentId/operation-policy",
1906
+ },
1907
+ {
1908
+ pattern: /^\/v1\/workspaces\/[^/]+\/machines\/[^/]+\/update$/,
1909
+ label: "/v1/workspaces/:workspaceId/machines/:enrollmentId/update",
1910
+ },
1779
1911
  {
1780
1912
  pattern: /^\/v1\/workspaces\/[^/]+\/machines$/,
1781
1913
  label: "/v1/workspaces/:workspaceId/machines",
@@ -1821,6 +1953,7 @@ export function isApiContractProtectedMutation(method: string, pathname: string)
1821
1953
  pathname.startsWith("/v1/auth/") ||
1822
1954
  pathname.startsWith("/v1/webhooks/") ||
1823
1955
  pathname.startsWith("/v1/integrations/oauth/") ||
1956
+ isPersonalGitHubGitBrokerRequest(method, pathname) ||
1824
1957
  pathname === "/v1/integrations/slack/events" ||
1825
1958
  pathname === "/v1/integrations/slack/commands" ||
1826
1959
  pathname === "/v1/integrations/slack/interactions" ||