@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
@@ -16,6 +16,7 @@ import {
16
16
  workspaceControlUtf8Bytes,
17
17
  type AccessContext,
18
18
  type Permission,
19
+ type WorkspaceMember as WorkspaceMemberValue,
19
20
  } from "@opengeni/contracts";
20
21
  import {
21
22
  allWorkspacePermissions,
@@ -25,6 +26,7 @@ import {
25
26
  getWorkspaceModelPolicy,
26
27
  grantWorkspaceAccess,
27
28
  listWorkspaceMembers,
29
+ normalizeWorkspaceMembershipPermissions,
28
30
  listWorkspaceControlEvents,
29
31
  listWorkspacesForSubject,
30
32
  removeWorkspaceMember,
@@ -35,13 +37,19 @@ import {
35
37
  updateWorkspaceSettings,
36
38
  upsertWorkspaceModelPolicy,
37
39
  workspaceCodexSubscriptionActive,
40
+ workspaceControlRequestLockTimeoutMs,
38
41
  workspaceXaiSubscriptionActive,
39
42
  workspaceVercelAiGatewayConnectionActive,
40
43
  } from "@opengeni/db";
41
44
  import { boundWorkspaceControlHttpPage } from "@opengeni/events";
42
45
  import type { Hono } from "hono";
43
46
  import { HTTPException } from "hono/http-exception";
44
- import { hasPermission, requireAccessContext, requireAccessGrant } from "@opengeni/core";
47
+ import {
48
+ hasPermission,
49
+ requireAccessContext,
50
+ requireAccessGrant,
51
+ requireFreshAccessGrant,
52
+ } from "@opengeni/core";
45
53
  import { requireLimit } from "@opengeni/core";
46
54
  import type { ApiRouteDeps } from "@opengeni/core";
47
55
  import {
@@ -71,6 +79,24 @@ export function canonicalWorkspacePolicyModelIds(
71
79
  return [...new Set(modelIds.map((modelId) => canonicalizeConfiguredModelId(settings, modelId)))];
72
80
  }
73
81
 
82
+ type WorkspaceMemberProjectionInput = Omit<WorkspaceMemberValue, "permissions"> & {
83
+ permissions: unknown;
84
+ };
85
+
86
+ /**
87
+ * Keep the roster readable across stored-permission contract changes. Unknown
88
+ * values are never re-authorized: they are omitted from the public projection,
89
+ * while every currently recognized permission is preserved in stored order.
90
+ */
91
+ export function workspaceMembersResponse(members: readonly WorkspaceMemberProjectionInput[]) {
92
+ return ListWorkspaceMembersResponse.parse({
93
+ members: members.map((member) => ({
94
+ ...member,
95
+ permissions: normalizeWorkspaceMembershipPermissions(member.permissions),
96
+ })),
97
+ });
98
+ }
99
+
74
100
  export function registerWorkspaceRoutes(app: Hono, deps: ApiRouteDeps): void {
75
101
  app.get("/v1/access/me", async (c) => {
76
102
  return c.json(await requireAccessContext(c, deps));
@@ -157,7 +183,11 @@ export function registerWorkspaceRoutes(app: Hono, deps: ApiRouteDeps): void {
157
183
  if (!parsed.success) {
158
184
  throw new HTTPException(400, { message: "invalid workspace settings patch" });
159
185
  }
160
- const workspace = await updateWorkspaceSettings(deps.db, workspaceId, parsed.data);
186
+ // Request-scoped: bound the exclusive control-prefix wait so a busy
187
+ // workspace yields the retryable 503 instead of parking this request.
188
+ const workspace = await updateWorkspaceSettings(deps.db, workspaceId, parsed.data, {
189
+ controlLockTimeoutMs: workspaceControlRequestLockTimeoutMs(),
190
+ });
161
191
  return c.json(Workspace.parse(workspace));
162
192
  });
163
193
 
@@ -290,7 +320,14 @@ export function registerWorkspaceRoutes(app: Hono, deps: ApiRouteDeps): void {
290
320
  }
291
321
  return c.json(
292
322
  await controlHumanWorkspace(
293
- { db: deps.db, bus: deps.bus, workflowClient: deps.workflowClient },
323
+ {
324
+ db: deps.db,
325
+ bus: deps.bus,
326
+ workflowClient: deps.workflowClient,
327
+ ...(deps.schedulePromptPostCommit
328
+ ? { schedulePromptPostCommit: deps.schedulePromptPostCommit }
329
+ : {}),
330
+ },
294
331
  { accountId: grant.accountId, workspaceId, subjectId: grant.subjectId },
295
332
  parsed.data,
296
333
  ),
@@ -324,7 +361,12 @@ export function registerWorkspaceRoutes(app: Hono, deps: ApiRouteDeps): void {
324
361
  workspaceId,
325
362
  after,
326
363
  c.req.raw.signal,
327
- { observability: deps.observability },
364
+ {
365
+ observability: deps.observability,
366
+ reauthorize: async () => {
367
+ await requireFreshAccessGrant(c, deps, workspaceId, "workspace:read");
368
+ },
369
+ },
328
370
  );
329
371
  });
330
372
 
@@ -369,6 +411,11 @@ export function registerWorkspaceRoutes(app: Hono, deps: ApiRouteDeps): void {
369
411
  message: "wait for the workspace's active video generations to finish before deleting it",
370
412
  });
371
413
  }
414
+ if (deleted.status === "active_background_commands") {
415
+ throw new HTTPException(409, {
416
+ message: "pause or cancel the workspace's background commands before deleting it",
417
+ });
418
+ }
372
419
  if (deleted.status === "live_sandboxes") {
373
420
  throw new HTTPException(409, {
374
421
  message: "wait for the workspace's active sandboxes to finish draining before deleting it",
@@ -399,7 +446,7 @@ export function registerWorkspaceRoutes(app: Hono, deps: ApiRouteDeps): void {
399
446
  const workspaceId = c.req.param("workspaceId");
400
447
  await requireAccessGrant(c, deps, workspaceId, "workspace:read");
401
448
  const members = await listWorkspaceMembers(deps.db, workspaceId);
402
- return c.json(ListWorkspaceMembersResponse.parse({ members }));
449
+ return c.json(workspaceMembersResponse(members));
403
450
  });
404
451
 
405
452
  app.post("/v1/workspaces/:workspaceId/members", async (c) => {
@@ -458,8 +505,14 @@ export function registerWorkspaceRoutes(app: Hono, deps: ApiRouteDeps): void {
458
505
  const subjectId = decodeURIComponent(c.req.param("subjectId"));
459
506
  const members = await listWorkspaceMembers(deps.db, workspaceId);
460
507
  // Never remove yourself, and never remove the last administering member.
508
+ // The fenced removal command re-enforces both guards fail-closed.
461
509
  assertWorkspaceMemberRemovable({ members, subjectId, callerSubjectId: grant.subjectId });
462
- await removeWorkspaceMember(deps.db, workspaceId, subjectId);
510
+ await removeWorkspaceMember(deps.db, {
511
+ accountId: grant.accountId,
512
+ workspaceId,
513
+ actorSubjectId: grant.subjectId,
514
+ targetSubjectId: subjectId,
515
+ });
463
516
  return c.body(null, 204);
464
517
  });
465
518
  }
@@ -12,15 +12,15 @@
12
12
  // resolveEnrollmentSigningSecret) — an invalid/expired/forged bearer is denied;
13
13
  // 3. confirms the enrollment is still ACTIVE in the DB at the exact credential
14
14
  // generation (a revoked or re-enrolled machine denies an old bearer);
15
- // 4. signs a NATS user JWT granting pub/sub ONLY `agent.<ws>.>` + `_INBOX.>`
16
- // (deny-all-else by an allow-list) and returns it inside a signed
15
+ // 4. atomically claims the daemon instance named in CONNECT;
16
+ // 5. signs a NATS user JWT granting pub/sub ONLY that exact process subtree +
17
+ // `_INBOX.>` (deny-all-else by an allow-list) and returns it inside a signed
17
18
  // authorization-response JWT.
18
19
  //
19
- // That per-subject scope is the per-workspace ISOLATION: workspace A's agent is
20
- // cryptographically incapable of pub/sub on `agent.B.>`. nats-server enforces the
21
- // signed permission set; the boundary does not rely on subject naming alone (the M4
22
- // transport test proved the CODE constructs scoped subjects; THIS proves the SERVER
23
- // refuses cross-workspace access).
20
+ // That exact scope is both the tenancy boundary and routing fence: workspace A
21
+ // cannot reach B, and an old/duplicate process cannot subscribe to its successor's
22
+ // work. nats-server enforces the signed permission set; the boundary does not rely
23
+ // on naming alone.
24
24
  //
25
25
  // SECURITY (§18): the bearer value + the account signing seed are NEVER logged. A
26
26
  // validation failure → a DENIAL response (the server refuses the connection); a
@@ -34,23 +34,24 @@ import {
34
34
  type Settings,
35
35
  } from "@opengeni/config";
36
36
  import { verifyEnrollmentBearer } from "@opengeni/contracts";
37
- import { getEnrollment, type Database } from "@opengeni/db";
37
+ import { claimEnrollmentConnection, getEnrollment, type Database } from "@opengeni/db";
38
38
  import {
39
39
  createResponderConnection,
40
40
  decodeAuthRequest,
41
41
  mintAuthResponse,
42
42
  mintUserJwt,
43
+ parseAgentConnectionName,
43
44
  workspaceAgentPermissions,
44
45
  type ResponderConnection,
45
46
  } from "@opengeni/events";
46
47
  import type { Observability } from "@opengeni/observability";
47
48
  import { observabilityEventLogger } from "../observability";
49
+ import { AGENT_CONNECTION_LEASE_MS } from "./connection-authority";
48
50
 
49
51
  /** The NATS subject nats-server publishes authorization requests on (ADR-26). */
50
52
  export const AUTH_CALLOUT_SUBJECT = "$SYS.REQ.USER.AUTH";
51
53
  /** Keep live NATS credentials short-lived while never outliving the bearer. */
52
54
  export const NATS_USER_JWT_TTL_SECONDS = 5 * 60;
53
-
54
55
  export interface AuthCalloutDeps {
55
56
  db: Database;
56
57
  settings: Settings;
@@ -138,9 +139,33 @@ export async function handleAuthorizationRequest(
138
139
  return deny("enrollment credential generation mismatch");
139
140
  }
140
141
 
141
- // GRANT: a user JWT scoped to ONLY this workspace's agent subtree + the reply
142
- // inbox. This allow-list IS the per-workspace isolation boundary.
143
- const permissions = workspaceAgentPermissions(claims.workspaceId);
142
+ const connectionInstanceId = parseAgentConnectionName(decoded.name);
143
+ if (!connectionInstanceId) {
144
+ return deny("missing or invalid agent connection instance");
145
+ }
146
+ const claim = await claimEnrollmentConnection(deps.db, {
147
+ workspaceId: claims.workspaceId,
148
+ enrollmentId: claims.enrollmentId,
149
+ credentialGeneration: claims.credentialGeneration,
150
+ connectionInstanceId,
151
+ leaseMs: AGENT_CONNECTION_LEASE_MS,
152
+ });
153
+ if (!claim.claimed) {
154
+ deps.observability?.warn?.("auth-callout: denied a duplicate live runner", {
155
+ workspaceId: claims.workspaceId,
156
+ agentId: claims.agentId,
157
+ });
158
+ return deny("another runner instance currently owns this enrollment");
159
+ }
160
+
161
+ // GRANT: the operational subject includes the exact claimed process instance.
162
+ // A previous socket may remain open until its short JWT expires, but no control
163
+ // request is routed to its old subject after a successor claims authority.
164
+ const permissions = workspaceAgentPermissions(
165
+ claims.workspaceId,
166
+ claims.agentId,
167
+ connectionInstanceId,
168
+ );
144
169
  const nowSeconds = Math.floor(Date.now() / 1000);
145
170
  const userJwt = mintUserJwt({
146
171
  userPublicKey: decoded.userNkey,
@@ -165,6 +190,7 @@ export async function handleAuthorizationRequest(
165
190
  deps.observability?.info?.("auth-callout: granted a workspace-scoped NATS credential", {
166
191
  workspaceId: claims.workspaceId,
167
192
  agentId: claims.agentId,
193
+ connectionGeneration: claim.connectionGeneration,
168
194
  });
169
195
  return Buffer.from(response, "utf8");
170
196
  }