@opengeni/api-router 0.22.2 → 0.26.1

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 (124) hide show
  1. package/dist/app.d.ts +2 -2
  2. package/dist/app.js +1 -1
  3. package/dist/auth/managed-auth.d.ts +0 -30
  4. package/dist/browser-controller-authority.d.ts +43 -0
  5. package/dist/browser-state-authority.d.ts +27 -0
  6. package/dist/{chunk-HWXJW5C7.js → chunk-JIKNR5YL.js} +27993 -14546
  7. package/dist/chunk-JIKNR5YL.js.map +1 -0
  8. package/dist/codemode.d.ts +23 -0
  9. package/dist/editable-artifact-live-hints.d.ts +11 -0
  10. package/dist/editable-artifact-native-kernel.d.ts +37 -0
  11. package/dist/editable-artifact-office-import.d.ts +22 -0
  12. package/dist/editable-artifact-production.d.ts +29 -0
  13. package/dist/editable-artifact-websocket.d.ts +49 -0
  14. package/dist/editable-artifact-workspace-files.d.ts +23 -0
  15. package/dist/github-browser-flow.d.ts +6 -0
  16. package/dist/http/cors.d.ts +1 -0
  17. package/dist/http/sse.d.ts +2 -0
  18. package/dist/index.d.ts +3 -2
  19. package/dist/index.js +1824 -30
  20. package/dist/index.js.map +1 -1
  21. package/dist/integrations/api-integrations.d.ts +24 -0
  22. package/dist/integrations/atlassian.d.ts +176 -0
  23. package/dist/integrations/github-skill-source.d.ts +5 -0
  24. package/dist/integrations/google-drive.d.ts +85 -0
  25. package/dist/integrations/oauth-client.d.ts +30 -1
  26. package/dist/integrations/provider-oauth.d.ts +19 -0
  27. package/dist/integrations/slack-bot.d.ts +4 -0
  28. package/dist/integrations/slack-interactions.d.ts +14 -2
  29. package/dist/integrations/social-api.d.ts +2 -1
  30. package/dist/mcp/editable-artifact-query-schema.d.ts +4 -0
  31. package/dist/mcp/editable-artifacts.d.ts +13 -0
  32. package/dist/mcp/receipts.d.ts +28 -0
  33. package/dist/mcp/scheduled-task-view.d.ts +518 -0
  34. package/dist/mcp/server.d.ts +14 -3
  35. package/dist/memory-slack-delivery.d.ts +9 -0
  36. package/dist/routes/api-integrations.d.ts +8 -0
  37. package/dist/routes/browser-identities.d.ts +5 -0
  38. package/dist/routes/browser-sessions.d.ts +6 -0
  39. package/dist/routes/company-profile.d.ts +3 -0
  40. package/dist/routes/computer-sessions.d.ts +6 -0
  41. package/dist/routes/editable-artifacts.d.ts +44 -0
  42. package/dist/routes/integration-features.d.ts +3 -0
  43. package/dist/routes/memory-slack-publications.d.ts +6 -0
  44. package/dist/routes/plugins.d.ts +8 -0
  45. package/dist/routes/sessions.d.ts +17 -2
  46. package/dist/routes/skills.d.ts +6 -0
  47. package/dist/routes/video-generation.d.ts +3 -0
  48. package/dist/sandbox/auth-callout.d.ts +2 -0
  49. package/dist/sandbox/channel-a.d.ts +59 -2
  50. package/dist/sandbox/metrics-ingestion.d.ts +6 -1
  51. package/dist/sandbox/viewer.d.ts +4 -2
  52. package/dist/temporal-schedule-cleanup.d.ts +26 -0
  53. package/package.json +19 -14
  54. package/src/app.ts +277 -41
  55. package/src/auth/managed-auth.ts +0 -16
  56. package/src/browser-controller-authority.ts +137 -0
  57. package/src/browser-state-authority.ts +236 -0
  58. package/src/codemode.ts +186 -0
  59. package/src/editable-artifact-live-hints.ts +64 -0
  60. package/src/editable-artifact-native-kernel.ts +659 -0
  61. package/src/editable-artifact-office-import.ts +230 -0
  62. package/src/editable-artifact-production.ts +419 -0
  63. package/src/editable-artifact-websocket.ts +311 -0
  64. package/src/editable-artifact-workspace-files.ts +186 -0
  65. package/src/github-browser-flow.ts +35 -6
  66. package/src/http/auth.ts +2 -0
  67. package/src/http/cors.ts +3 -0
  68. package/src/http/sse.ts +101 -6
  69. package/src/index.ts +147 -23
  70. package/src/integrations/api-integrations.ts +350 -0
  71. package/src/integrations/atlassian.ts +1621 -0
  72. package/src/integrations/github-skill-source.ts +142 -0
  73. package/src/integrations/google-drive.ts +1000 -64
  74. package/src/integrations/oauth-client.ts +159 -89
  75. package/src/integrations/provider-oauth.ts +777 -0
  76. package/src/integrations/slack-bot.ts +31 -2
  77. package/src/integrations/slack-interactions.ts +610 -42
  78. package/src/integrations/social-api.ts +11 -0
  79. package/src/mcp/documents.ts +74 -26
  80. package/src/mcp/editable-artifact-query-schema.ts +236 -0
  81. package/src/mcp/editable-artifacts.ts +448 -0
  82. package/src/mcp/receipts.ts +95 -0
  83. package/src/mcp/scheduled-task-view.ts +642 -0
  84. package/src/mcp/server.ts +1718 -310
  85. package/src/memory-slack-delivery.ts +209 -0
  86. package/src/observability.ts +3 -3
  87. package/src/routes/api-integrations.ts +407 -0
  88. package/src/routes/api-keys.ts +7 -1
  89. package/src/routes/browser-identities.ts +136 -0
  90. package/src/routes/browser-sessions.ts +2543 -0
  91. package/src/routes/codex.ts +7 -4
  92. package/src/routes/company-profile.ts +255 -0
  93. package/src/routes/computer-sessions.ts +1247 -0
  94. package/src/routes/connections.ts +358 -102
  95. package/src/routes/documents.ts +22 -3
  96. package/src/routes/editable-artifacts.ts +1159 -0
  97. package/src/routes/enrollments.ts +54 -12
  98. package/src/routes/environments.ts +60 -11
  99. package/src/routes/files.ts +277 -65
  100. package/src/routes/github.ts +18 -2
  101. package/src/routes/install.ts +38 -2
  102. package/src/routes/integration-features.ts +258 -0
  103. package/src/routes/machines.ts +1 -1
  104. package/src/routes/memory-slack-publications.ts +216 -0
  105. package/src/routes/packs.ts +437 -7
  106. package/src/routes/plugins.ts +751 -0
  107. package/src/routes/rigs.ts +77 -20
  108. package/src/routes/scheduled-tasks.ts +94 -42
  109. package/src/routes/sessions.ts +475 -234
  110. package/src/routes/skills.ts +174 -0
  111. package/src/routes/transcription-recordings.ts +65 -33
  112. package/src/routes/video-generation.ts +132 -0
  113. package/src/routes/workspaces.ts +46 -24
  114. package/src/sandbox/auth-callout.ts +16 -4
  115. package/src/sandbox/channel-a.ts +809 -85
  116. package/src/sandbox/enrollment.ts +13 -3
  117. package/src/sandbox/machines.ts +1 -1
  118. package/src/sandbox/metrics-ingestion.ts +121 -3
  119. package/src/sandbox/rematerialize.ts +35 -47
  120. package/src/sandbox/viewer.ts +58 -29
  121. package/src/temporal-schedule-cleanup.ts +135 -0
  122. package/dist/chunk-HWXJW5C7.js.map +0 -1
  123. package/dist/mcp/toolspace.d.ts +0 -62
  124. package/src/mcp/toolspace.ts +0 -1186
@@ -20,6 +20,7 @@
20
20
  // workspace can approve it (the approve's user_code lookup is workspace-scoped) — a
21
21
  // start can never grant access to a workspace no authorized user later approves in.
22
22
 
23
+ import { randomUUID } from "node:crypto";
23
24
  import {
24
25
  DeviceEnrollmentApproveRequest,
25
26
  DeviceEnrollmentApproveResponse,
@@ -35,11 +36,18 @@ import {
35
36
  ListEnrollmentsResponse,
36
37
  MintEnrollTokenRequest,
37
38
  MintEnrollTokenResponse,
39
+ RemoveEnrollmentRequest,
38
40
  RevokeEnrollmentResponse,
39
41
  type EnrollmentArch,
40
42
  type EnrollmentOs,
41
43
  } from "@opengeni/contracts";
42
- import { getWorkspace, listEnrollments, revokeEnrollment } from "@opengeni/db";
44
+ import {
45
+ getWorkspace,
46
+ listEnrollments,
47
+ MachineRemovalIdempotencyError,
48
+ MachineRemovalRevisionConflictError,
49
+ removeEnrollment,
50
+ } from "@opengeni/db";
43
51
  import type { Context, Hono } from "hono";
44
52
  import { HTTPException } from "hono/http-exception";
45
53
  import { requireAccessGrant } from "@opengeni/core";
@@ -301,11 +309,12 @@ export function registerEnrollmentRoutes(app: Hono, deps: ApiRouteDeps): void {
301
309
  await requireAccessGrant(c, deps, workspaceId, "enrollments:read");
302
310
  assertSelfhostedEnabled();
303
311
  const statusFilter = c.req.query("status");
304
- const rows = await listEnrollments(
305
- db,
306
- workspaceId,
307
- statusFilter === "active" ? { status: "active" } : {},
308
- );
312
+ if (statusFilter !== undefined && statusFilter !== "active" && statusFilter !== "revoked") {
313
+ throw new HTTPException(400, { message: "status must be active or revoked" });
314
+ }
315
+ const rows = await listEnrollments(db, workspaceId, {
316
+ status: statusFilter === "revoked" ? "revoked" : "active",
317
+ });
309
318
  return c.json(
310
319
  ListEnrollmentsResponse.parse({
311
320
  enrollments: rows.map((row) =>
@@ -333,12 +342,45 @@ export function registerEnrollmentRoutes(app: Hono, deps: ApiRouteDeps): void {
333
342
  const workspaceId = c.req.param("workspaceId");
334
343
  const grant = await requireAccessGrant(c, deps, workspaceId, "enrollments:manage");
335
344
  assertSelfhostedEnabled();
336
- const result = await revokeEnrollment(db, {
337
- accountId: grant.accountId,
338
- workspaceId,
339
- enrollmentId: c.req.param("enrollmentId"),
340
- });
341
- return c.json(RevokeEnrollmentResponse.parse(result));
345
+ const parsed = RemoveEnrollmentRequest.safeParse(await c.req.json().catch(() => ({})));
346
+ if (!parsed.success) {
347
+ throw new HTTPException(400, { message: "invalid machine removal request" });
348
+ }
349
+ const idempotencyKey =
350
+ parsed.data.idempotencyKey?.trim() || c.req.header("idempotency-key")?.trim() || randomUUID();
351
+ try {
352
+ const result = await removeEnrollment(db, {
353
+ accountId: grant.accountId,
354
+ workspaceId,
355
+ enrollmentId: c.req.param("enrollmentId"),
356
+ operationKey: idempotencyKey,
357
+ ...(parsed.data.expectedUpdatedAt
358
+ ? { expectedUpdatedAt: parsed.data.expectedUpdatedAt }
359
+ : {}),
360
+ subjectId: grant.subjectId,
361
+ });
362
+ if (!result) {
363
+ throw new HTTPException(404, { message: "machine enrollment not found" });
364
+ }
365
+ return c.json(
366
+ RevokeEnrollmentResponse.parse({
367
+ revoked: result.removed,
368
+ ...result,
369
+ }),
370
+ 200,
371
+ );
372
+ } catch (error) {
373
+ if (error instanceof MachineRemovalIdempotencyError) {
374
+ throw new HTTPException(409, { message: error.message });
375
+ }
376
+ if (error instanceof MachineRemovalRevisionConflictError) {
377
+ throw new HTTPException(409, {
378
+ message: error.message,
379
+ cause: { code: "stale_revision", currentUpdatedAt: error.currentUpdatedAt },
380
+ });
381
+ }
382
+ throw error;
383
+ }
342
384
  });
343
385
  }
344
386
 
@@ -9,17 +9,19 @@ import {
9
9
  countScheduledTasksUsingVariableSet,
10
10
  countVariableSets,
11
11
  createVariableSet,
12
+ decryptVariableSetValue,
12
13
  deleteVariableSet,
13
14
  deleteVariableSetVariable,
14
15
  encryptVariableSetValue,
15
16
  getVariableSetByName,
16
17
  listVariableSets,
18
+ readVariableSetSecretAtomically,
17
19
  setVariableSetVariable,
18
20
  updateVariableSet,
19
21
  } from "@opengeni/db";
20
22
  import type { Hono } from "hono";
21
23
  import { HTTPException } from "hono/http-exception";
22
- import { requireAccessGrant } from "@opengeni/core";
24
+ import { requireAccessGrant, requireLiteralPermission, requirePermission } from "@opengeni/core";
23
25
  import type { ApiRouteDeps } from "@opengeni/core";
24
26
  import {
25
27
  assertAllowedVariableSetVariableName,
@@ -40,15 +42,21 @@ export function registerVariableSetRoutes(app: Hono, deps: ApiRouteDeps): void {
40
42
  for (const prefix of prefixes) {
41
43
  app.get(`${prefix}`, async (c) => {
42
44
  const workspaceId = c.req.param("workspaceId")!;
43
- await requireAccessGrant(c, deps, workspaceId, "variable-sets:use");
45
+ const grant = await requireAccessGrant(c, deps, workspaceId);
46
+ requirePermission(grant, "variable-sets:list");
47
+ requirePermission(grant, "secrets:list");
44
48
  return c.json(await listVariableSets(db, workspaceId));
45
49
  });
46
50
 
47
51
  app.post(`${prefix}`, async (c) => {
48
52
  const workspaceId = c.req.param("workspaceId")!;
49
- const grant = await requireAccessGrant(c, deps, workspaceId, "variable-sets:manage");
53
+ const grant = await requireAccessGrant(c, deps, workspaceId);
54
+ requirePermission(grant, "variable-sets:write");
50
55
  const key = requireVariableSetEncryption(settings);
51
56
  const payload = CreateVariableSetRequest.parse(await c.req.json());
57
+ if (payload.variables.length > 0) {
58
+ requirePermission(grant, "secrets:write");
59
+ }
52
60
  const name = trimmedVariableSetName(payload.name);
53
61
  if (payload.variables.length > MAX_VARIABLES_PER_ENVIRONMENT) {
54
62
  throw new HTTPException(422, {
@@ -71,7 +79,9 @@ export function registerVariableSetRoutes(app: Hono, deps: ApiRouteDeps): void {
71
79
  });
72
80
  }
73
81
  if (await getVariableSetByName(db, workspaceId, name)) {
74
- throw new HTTPException(409, { message: `variable set name is already in use: ${name}` });
82
+ throw new HTTPException(409, {
83
+ message: `variable set name is already in use: ${name}`,
84
+ });
75
85
  }
76
86
  // Values are encrypted up front and the variableSet plus all initial
77
87
  // variables are written in one transaction: a failure leaves nothing.
@@ -95,13 +105,42 @@ export function registerVariableSetRoutes(app: Hono, deps: ApiRouteDeps): void {
95
105
 
96
106
  app.get(`${prefix}/:variableSetId`, async (c) => {
97
107
  const workspaceId = c.req.param("workspaceId")!;
98
- await requireAccessGrant(c, deps, workspaceId, "variable-sets:use");
108
+ const grant = await requireAccessGrant(c, deps, workspaceId);
109
+ requirePermission(grant, "variable-sets:read");
110
+ requirePermission(grant, "secrets:list");
99
111
  return c.json(await requireVariableSetForApi(db, workspaceId, c.req.param("variableSetId")!));
100
112
  });
101
113
 
114
+ if (prefix.endsWith("/variable-sets")) {
115
+ app.get(`${prefix}/:variableSetId/variables/:name`, async (c) => {
116
+ const workspaceId = c.req.param("workspaceId")!;
117
+ const grant = await requireAccessGrant(c, deps, workspaceId);
118
+ requirePermission(grant, "variable-sets:read");
119
+ requireLiteralPermission(grant, "secrets:read");
120
+ const key = requireVariableSetEncryption(settings);
121
+ const name = parseVariableName(c.req.param("name")!);
122
+ const secret = await readVariableSetSecretAtomically(db, {
123
+ accountId: grant.accountId,
124
+ workspaceId,
125
+ subjectId: grant.subjectId,
126
+ variableSetId: c.req.param("variableSetId")!,
127
+ name,
128
+ actor: { kind: "subject" },
129
+ decrypt: (valueEncrypted) => decryptVariableSetValue(key, valueEncrypted),
130
+ });
131
+ if (!secret) {
132
+ throw new HTTPException(404, {
133
+ message: "variable set variable not found",
134
+ });
135
+ }
136
+ return c.json(secret);
137
+ });
138
+ }
139
+
102
140
  app.patch(`${prefix}/:variableSetId`, async (c) => {
103
141
  const workspaceId = c.req.param("workspaceId")!;
104
- const grant = await requireAccessGrant(c, deps, workspaceId, "variable-sets:manage");
142
+ const grant = await requireAccessGrant(c, deps, workspaceId);
143
+ requirePermission(grant, "variable-sets:write");
105
144
  const variableSet = await requireVariableSetForApi(
106
145
  db,
107
146
  workspaceId,
@@ -112,7 +151,9 @@ export function registerVariableSetRoutes(app: Hono, deps: ApiRouteDeps): void {
112
151
  if (name !== undefined && name !== variableSet.name) {
113
152
  const existing = await getVariableSetByName(db, workspaceId, name);
114
153
  if (existing && existing.id !== variableSet.id) {
115
- throw new HTTPException(409, { message: `variable set name is already in use: ${name}` });
154
+ throw new HTTPException(409, {
155
+ message: `variable set name is already in use: ${name}`,
156
+ });
116
157
  }
117
158
  }
118
159
  const updated = await updateVariableSet(db, workspaceId, variableSet.id, {
@@ -129,7 +170,9 @@ export function registerVariableSetRoutes(app: Hono, deps: ApiRouteDeps): void {
129
170
 
130
171
  app.delete(`${prefix}/:variableSetId`, async (c) => {
131
172
  const workspaceId = c.req.param("workspaceId")!;
132
- const grant = await requireAccessGrant(c, deps, workspaceId, "variable-sets:manage");
173
+ const grant = await requireAccessGrant(c, deps, workspaceId);
174
+ requirePermission(grant, "variable-sets:write");
175
+ requirePermission(grant, "secrets:write");
133
176
  const variableSet = await requireVariableSetForApi(
134
177
  db,
135
178
  workspaceId,
@@ -166,7 +209,9 @@ export function registerVariableSetRoutes(app: Hono, deps: ApiRouteDeps): void {
166
209
 
167
210
  app.put(`${prefix}/:variableSetId/variables/:name`, async (c) => {
168
211
  const workspaceId = c.req.param("workspaceId")!;
169
- const grant = await requireAccessGrant(c, deps, workspaceId, "variable-sets:manage");
212
+ const grant = await requireAccessGrant(c, deps, workspaceId);
213
+ requirePermission(grant, "variable-sets:write");
214
+ requirePermission(grant, "secrets:write");
170
215
  const key = requireVariableSetEncryption(settings);
171
216
  const name = parseVariableName(c.req.param("name")!);
172
217
  const variableSet = await requireVariableSetForApi(
@@ -199,7 +244,9 @@ export function registerVariableSetRoutes(app: Hono, deps: ApiRouteDeps): void {
199
244
 
200
245
  app.delete(`${prefix}/:variableSetId/variables/:name`, async (c) => {
201
246
  const workspaceId = c.req.param("workspaceId")!;
202
- const grant = await requireAccessGrant(c, deps, workspaceId, "variable-sets:manage");
247
+ const grant = await requireAccessGrant(c, deps, workspaceId);
248
+ requirePermission(grant, "variable-sets:write");
249
+ requirePermission(grant, "secrets:write");
203
250
  const name = parseVariableName(c.req.param("name")!);
204
251
  const variableSet = await requireVariableSetForApi(
205
252
  db,
@@ -208,7 +255,9 @@ export function registerVariableSetRoutes(app: Hono, deps: ApiRouteDeps): void {
208
255
  );
209
256
  const deleted = await deleteVariableSetVariable(db, workspaceId, variableSet.id, name);
210
257
  if (!deleted) {
211
- throw new HTTPException(404, { message: "variable set variable not found" });
258
+ throw new HTTPException(404, {
259
+ message: "variable set variable not found",
260
+ });
212
261
  }
213
262
  await recordVariableSetAuditEvent(db, {
214
263
  grant,