@opengeni/api-router 0.23.1 → 0.30.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 (152) hide show
  1. package/dist/app.d.ts +3 -2
  2. package/dist/app.js +5 -3
  3. package/dist/auth/canonical-human-session-admission.d.ts +27 -0
  4. package/dist/browser-auth-broker.d.ts +20 -0
  5. package/dist/browser-controller-authority.d.ts +61 -0
  6. package/dist/browser-network-route.d.ts +8 -0
  7. package/dist/browser-state-authority.d.ts +27 -0
  8. package/dist/{chunk-T4T2PGU4.js → chunk-2PQMSRCB.js} +37891 -17463
  9. package/dist/chunk-2PQMSRCB.js.map +1 -0
  10. package/dist/codemode.d.ts +23 -0
  11. package/dist/controller-data-plane.d.ts +12 -0
  12. package/dist/editable-artifact-live-hints.d.ts +11 -0
  13. package/dist/editable-artifact-native-kernel.d.ts +37 -0
  14. package/dist/editable-artifact-office-import.d.ts +22 -0
  15. package/dist/editable-artifact-production.d.ts +29 -0
  16. package/dist/editable-artifact-websocket.d.ts +49 -0
  17. package/dist/editable-artifact-workspace-files.d.ts +23 -0
  18. package/dist/github-browser-flow.d.ts +6 -0
  19. package/dist/http/auth.d.ts +1 -1
  20. package/dist/http/cors.d.ts +1 -0
  21. package/dist/http/sse.d.ts +18 -1
  22. package/dist/index.d.ts +3 -2
  23. package/dist/index.js +1796 -22
  24. package/dist/index.js.map +1 -1
  25. package/dist/integrations/api-integrations.d.ts +24 -0
  26. package/dist/integrations/atlassian.d.ts +176 -0
  27. package/dist/integrations/fiken.d.ts +233 -0
  28. package/dist/integrations/github-skill-source.d.ts +5 -0
  29. package/dist/integrations/google-drive.d.ts +85 -0
  30. package/dist/integrations/oauth-client.d.ts +22 -1
  31. package/dist/integrations/provider-oauth.d.ts +17 -0
  32. package/dist/integrations/slack-bot.d.ts +175 -1
  33. package/dist/integrations/slack-interactions.d.ts +24 -6
  34. package/dist/integrations/social-api.d.ts +2 -1
  35. package/dist/interaction-holder-heartbeat.d.ts +17 -0
  36. package/dist/interaction-metrics.d.ts +11 -0
  37. package/dist/mcp/editable-artifact-query-schema.d.ts +4 -0
  38. package/dist/mcp/editable-artifacts.d.ts +13 -0
  39. package/dist/mcp/scheduled-task-view.d.ts +168 -0
  40. package/dist/mcp/server.d.ts +16 -4
  41. package/dist/memory-slack-delivery.d.ts +9 -0
  42. package/dist/model-catalog.d.ts +1 -0
  43. package/dist/routes/api-integrations.d.ts +8 -0
  44. package/dist/routes/browser-identities.d.ts +5 -0
  45. package/dist/routes/browser-sessions.d.ts +6 -0
  46. package/dist/routes/canonical-human-identities.d.ts +3 -0
  47. package/dist/routes/channels.d.ts +3 -0
  48. package/dist/routes/company-profile.d.ts +3 -0
  49. package/dist/routes/computer-sessions.d.ts +6 -0
  50. package/dist/routes/editable-artifacts.d.ts +44 -0
  51. package/dist/routes/integration-facets.d.ts +3 -0
  52. package/dist/routes/interaction-resources.d.ts +5 -0
  53. package/dist/routes/memory-slack-publications.d.ts +6 -0
  54. package/dist/routes/organization-memberships.d.ts +3 -0
  55. package/dist/routes/plugins.d.ts +8 -0
  56. package/dist/routes/sessions.d.ts +18 -2
  57. package/dist/routes/skills.d.ts +6 -0
  58. package/dist/routes/slack-task-policy.d.ts +3 -0
  59. package/dist/routes/supergrok.d.ts +3 -0
  60. package/dist/routes/video-generation.d.ts +3 -0
  61. package/dist/sandbox/channel-a.d.ts +62 -2
  62. package/dist/sandbox/machines.d.ts +2 -2
  63. package/dist/sandbox/metrics-ingestion.d.ts +6 -1
  64. package/dist/sandbox/viewer.d.ts +7 -3
  65. package/dist/slack-reaction-files.d.ts +27 -0
  66. package/dist/temporal-schedule-cleanup.d.ts +26 -0
  67. package/dist/transcription/providers/xai-subscription.d.ts +8 -0
  68. package/dist/xai-realtime.d.ts +26 -0
  69. package/dist/xai-subscription-auth.d.ts +23 -0
  70. package/package.json +20 -14
  71. package/src/app.ts +604 -64
  72. package/src/auth/canonical-human-session-admission.ts +116 -0
  73. package/src/auth/managed-auth.ts +101 -0
  74. package/src/browser-auth-broker.ts +155 -0
  75. package/src/browser-controller-authority.ts +186 -0
  76. package/src/browser-network-route.ts +34 -0
  77. package/src/browser-state-authority.ts +236 -0
  78. package/src/codemode.ts +186 -0
  79. package/src/controller-data-plane.ts +35 -0
  80. package/src/editable-artifact-live-hints.ts +64 -0
  81. package/src/editable-artifact-native-kernel.ts +659 -0
  82. package/src/editable-artifact-office-import.ts +230 -0
  83. package/src/editable-artifact-production.ts +419 -0
  84. package/src/editable-artifact-websocket.ts +311 -0
  85. package/src/editable-artifact-workspace-files.ts +186 -0
  86. package/src/github-browser-flow.ts +35 -6
  87. package/src/http/auth.ts +29 -9
  88. package/src/http/cors.ts +3 -0
  89. package/src/http/sse.ts +229 -5
  90. package/src/index.ts +119 -20
  91. package/src/integrations/api-integrations.ts +365 -0
  92. package/src/integrations/atlassian.ts +1621 -0
  93. package/src/integrations/fiken.ts +1230 -0
  94. package/src/integrations/github-skill-source.ts +142 -0
  95. package/src/integrations/google-drive.ts +1005 -64
  96. package/src/integrations/oauth-client.ts +123 -33
  97. package/src/integrations/provider-oauth.ts +756 -0
  98. package/src/integrations/slack-bot.ts +669 -11
  99. package/src/integrations/slack-interactions.ts +2063 -93
  100. package/src/integrations/social-api.ts +11 -0
  101. package/src/interaction-holder-heartbeat.ts +95 -0
  102. package/src/interaction-metrics.ts +159 -0
  103. package/src/mcp/documents.ts +146 -6
  104. package/src/mcp/editable-artifact-query-schema.ts +236 -0
  105. package/src/mcp/editable-artifacts.ts +448 -0
  106. package/src/mcp/scheduled-task-view.ts +34 -0
  107. package/src/mcp/server.ts +1756 -355
  108. package/src/memory-slack-delivery.ts +209 -0
  109. package/src/model-catalog.ts +19 -6
  110. package/src/routes/api-integrations.ts +410 -0
  111. package/src/routes/browser-identities.ts +160 -0
  112. package/src/routes/browser-sessions.ts +4482 -0
  113. package/src/routes/canonical-human-identities.ts +156 -0
  114. package/src/routes/channels.ts +90 -0
  115. package/src/routes/company-profile.ts +255 -0
  116. package/src/routes/computer-sessions.ts +1602 -0
  117. package/src/routes/connections.ts +421 -99
  118. package/src/routes/documents.ts +22 -3
  119. package/src/routes/editable-artifacts.ts +1159 -0
  120. package/src/routes/files.ts +106 -4
  121. package/src/routes/github.ts +18 -2
  122. package/src/routes/install.ts +7 -1
  123. package/src/routes/integration-facets.ts +258 -0
  124. package/src/routes/interaction-resources.ts +595 -0
  125. package/src/routes/memory-slack-publications.ts +216 -0
  126. package/src/routes/organization-memberships.ts +53 -0
  127. package/src/routes/packs.ts +437 -7
  128. package/src/routes/plugins.ts +752 -0
  129. package/src/routes/rigs.ts +77 -20
  130. package/src/routes/scheduled-tasks.ts +67 -40
  131. package/src/routes/sessions.ts +830 -278
  132. package/src/routes/skills.ts +256 -0
  133. package/src/routes/slack-task-policy.ts +115 -0
  134. package/src/routes/supergrok.ts +717 -0
  135. package/src/routes/transcription-recordings.ts +9 -2
  136. package/src/routes/transcriptions.ts +2 -1
  137. package/src/routes/video-generation.ts +162 -0
  138. package/src/routes/workspaces.ts +68 -28
  139. package/src/sandbox/channel-a.ts +750 -116
  140. package/src/sandbox/machines.ts +5 -5
  141. package/src/sandbox/metrics-ingestion.ts +121 -3
  142. package/src/sandbox/rematerialize.ts +35 -47
  143. package/src/sandbox/viewer.ts +39 -12
  144. package/src/slack-reaction-files.ts +181 -0
  145. package/src/temporal-schedule-cleanup.ts +135 -0
  146. package/src/transcription/providers/xai-subscription.ts +110 -0
  147. package/src/transcription/service.ts +17 -2
  148. package/src/xai-realtime.ts +216 -0
  149. package/src/xai-subscription-auth.ts +132 -0
  150. package/dist/chunk-T4T2PGU4.js.map +0 -1
  151. package/dist/mcp/toolspace.d.ts +0 -71
  152. package/src/mcp/toolspace.ts +0 -1190
@@ -1,20 +1,50 @@
1
+ import { createHash } from "node:crypto";
2
+
1
3
  import {
2
4
  EnablePackRequest,
5
+ InstallPackRequest,
3
6
  MarketingDailyAnalysisTaskRequest,
7
+ PackInstallation,
8
+ PackInstallationPreview,
9
+ PackUninstallPreview,
10
+ PreviewPackInstallationRequest,
4
11
  RegisterCapabilityPackRequest,
12
+ UninstallPackRequest,
13
+ UninstallPackResult,
14
+ stableJson,
5
15
  type SocialConnection,
6
16
  } from "@opengeni/contracts";
7
17
  import {
18
+ adoptPackComponentReferences,
19
+ CapabilityComponentVersionConflictError,
20
+ deferPackInstallationOperation,
8
21
  deleteWorkspacePack,
9
22
  disableCapabilityInstallation,
10
23
  enablePackInstallation,
24
+ finalizePackComponentOwnership,
25
+ finalizePackInstallationOperation,
26
+ finalizePackUninstallOperation,
11
27
  getCapabilityInstallation,
12
28
  getPackInstallation,
13
29
  getWorkspacePack,
14
30
  getSocialConnection,
31
+ installPortableSkill,
15
32
  listPackInstallations,
16
33
  listSocialConnections,
34
+ PackManifestChangedError,
35
+ PackComponentResolutionError,
36
+ PackInstallationVersionConflictError,
37
+ PackInstallationVersionRequiredError,
38
+ PackOperationClaimLostError,
39
+ PackOperationIdempotencyError,
40
+ PackOperationInProgressError,
41
+ preparePackInstallationOperation,
42
+ preparePackUninstallOperation,
43
+ previewPackComponentRelease,
44
+ recordPackInlineSkillComponent,
17
45
  registerWorkspacePack,
46
+ releasePackComponents,
47
+ touchPackInstallationOperation,
18
48
  updatePackInstallationStatus,
19
49
  } from "@opengeni/db";
20
50
  import { getDocumentBase } from "@opengeni/documents";
@@ -27,9 +57,12 @@ import { validateVariableSetAttachment } from "@opengeni/core";
27
57
  import {
28
58
  assertPackSandboxImageCompatible,
29
59
  buildMarketingDailyAnalysisAgentConfig,
60
+ capabilityPackRequiresInstallationPlan,
61
+ inlinePackSkillInstall,
30
62
  isBuiltInCapabilityPack,
31
63
  listWorkspaceCapabilityPacks,
32
64
  MARKETING_SOCIAL_PACK_ID,
65
+ previewCapabilityPackInstallation,
33
66
  resolveCapabilityPack,
34
67
  } from "@opengeni/core";
35
68
  import { createValidatedScheduledTask, syncCreatedScheduledTask } from "@opengeni/core";
@@ -70,16 +103,27 @@ export function registerPackRoutes(app: Hono, deps: ApiRouteDeps): void {
70
103
  await requireAccessGrant(c, deps, workspaceId, "workspace:admin");
71
104
  const packId = c.req.param("packId");
72
105
  if (isBuiltInCapabilityPack(packId)) {
73
- throw new HTTPException(409, { message: "built-in packs cannot be unregistered" });
106
+ throw new HTTPException(409, {
107
+ message: "built-in packs cannot be unregistered",
108
+ });
74
109
  }
75
110
  if (!(await getWorkspacePack(db, workspaceId, packId))) {
76
111
  throw new HTTPException(404, { message: "pack not found" });
77
112
  }
78
- // Disable installations before deleting the registration so a crash in
79
- // between can never orphan an active installation whose manifest is
80
- // gone; the capability installation row for pack:{packId} would
81
- // otherwise keep a future re-registration looking enabled.
113
+ // V2 Packs own explicit components. Unregistering before the ownership
114
+ // ledger is safely released would orphan those components, so require the
115
+ // dedicated uninstall flow first. Legacy installations retain the old
116
+ // disable-before-delete behavior for compatibility.
82
117
  const installation = await getPackInstallation(db, workspaceId, packId);
118
+ if (
119
+ installation &&
120
+ installation.status !== "disabled" &&
121
+ (installation.manifestDigest !== null || installation.manifestSnapshot !== null)
122
+ ) {
123
+ throw new HTTPException(409, {
124
+ message: "uninstall this Pack before unregistering it",
125
+ });
126
+ }
83
127
  if (installation && installation.status === "active") {
84
128
  await updatePackInstallationStatus(db, workspaceId, packId, "disabled");
85
129
  }
@@ -111,10 +155,342 @@ export function registerPackRoutes(app: Hono, deps: ApiRouteDeps): void {
111
155
  });
112
156
  });
113
157
 
158
+ app.post("/v1/workspaces/:workspaceId/packs/:packId/installation-preview", async (c) => {
159
+ const workspaceId = c.req.param("workspaceId");
160
+ await requireAccessGrant(c, deps, workspaceId, "workspace:read");
161
+ const pack = await requirePack(db, workspaceId, c.req.param("packId"));
162
+ const payload = PreviewPackInstallationRequest.parse(await c.req.json());
163
+ return c.json(
164
+ PackInstallationPreview.parse(
165
+ await previewCapabilityPackInstallation(db, workspaceId, pack, {
166
+ ...(payload.rigId ? { rigId: payload.rigId } : {}),
167
+ ...(payload.variableSetId ? { variableSetId: payload.variableSetId } : {}),
168
+ }),
169
+ ),
170
+ );
171
+ });
172
+
173
+ app.post("/v1/workspaces/:workspaceId/packs/:packId/install", async (c) => {
174
+ const workspaceId = c.req.param("workspaceId");
175
+ const grant = await requireAccessGrant(c, deps, workspaceId, "workspace:admin");
176
+ const pack = await requirePack(db, workspaceId, c.req.param("packId"));
177
+ const payload = InstallPackRequest.parse(await c.req.json());
178
+ const preview = PackInstallationPreview.parse(
179
+ await previewCapabilityPackInstallation(db, workspaceId, pack, {
180
+ ...(payload.rigId ? { rigId: payload.rigId } : {}),
181
+ ...(payload.variableSetId ? { variableSetId: payload.variableSetId } : {}),
182
+ }),
183
+ );
184
+ if (preview.manifestDigest !== payload.expectedManifestDigest) {
185
+ throw new HTTPException(409, {
186
+ message: "The Pack manifest changed after preview. Review the updated installation plan.",
187
+ });
188
+ }
189
+ if (!preview.ready) {
190
+ throw new HTTPException(422, {
191
+ message: `The Pack is not ready to install: ${preview.blockers.join("; ")}`,
192
+ });
193
+ }
194
+ if (preview.variableSetId) {
195
+ await validateVariableSetAttachment(
196
+ { settings, db },
197
+ grant,
198
+ workspaceId,
199
+ preview.variableSetId,
200
+ { preauthorized: payload.variableSetId === undefined },
201
+ );
202
+ }
203
+ const metadata = {
204
+ ...payload.metadata,
205
+ platformVersion: 2,
206
+ packVersion: pack.version,
207
+ ...(preview.variableSetId ? { variableSetId: preview.variableSetId } : {}),
208
+ };
209
+ const requestDigest = sha256(
210
+ stableJson({
211
+ packId: pack.id,
212
+ manifestDigest: preview.manifestDigest,
213
+ expectedInstallationVersion: payload.expectedInstallationVersion ?? null,
214
+ selectedRigId: preview.rig.rigId,
215
+ variableSetId: preview.variableSetId,
216
+ metadata,
217
+ }),
218
+ );
219
+ let prepared: Awaited<ReturnType<typeof preparePackInstallationOperation>>;
220
+ try {
221
+ prepared = await preparePackInstallationOperation(db, {
222
+ accountId: grant.accountId,
223
+ workspaceId,
224
+ subjectId: grant.subjectId,
225
+ pack,
226
+ manifestDigest: preview.manifestDigest,
227
+ selectedRigId: preview.rig.rigId,
228
+ metadata,
229
+ idempotencyKey: payload.idempotencyKey,
230
+ requestDigest,
231
+ ...(!isBuiltInCapabilityPack(pack.id)
232
+ ? { registeredManifestDigest: preview.manifestDigest }
233
+ : {}),
234
+ ...(payload.expectedInstallationVersion !== undefined
235
+ ? { expectedInstallationVersion: payload.expectedInstallationVersion }
236
+ : {}),
237
+ });
238
+ } catch (error) {
239
+ throw packMutationHttpError(error);
240
+ }
241
+ if (prepared.replayResult) {
242
+ const replayed = await getPackInstallation(db, workspaceId, pack.id);
243
+ if (!replayed) {
244
+ throw new HTTPException(500, {
245
+ message: "The completed Pack operation lost its installation record",
246
+ });
247
+ }
248
+ return c.json(PackInstallation.parse(replayed), 200);
249
+ }
250
+
251
+ const retainedComponentKeys: string[] = [];
252
+ const retainedFacetInstallationIds: string[] = [];
253
+ const retainedBindingIds: string[] = [];
254
+ let activeComponentKey = "manifest";
255
+ const heartbeat = async (): Promise<void> =>
256
+ await touchPackInstallationOperation(db, {
257
+ accountId: grant.accountId,
258
+ workspaceId,
259
+ operationId: prepared.operationId,
260
+ operationVersion: prepared.operationVersion,
261
+ });
262
+ try {
263
+ for (const skill of pack.skills) {
264
+ await heartbeat();
265
+ const inline = inlinePackSkillInstall(pack, skill);
266
+ activeComponentKey = inline.componentKey;
267
+ const installed = await installPortableSkill(db, {
268
+ accountId: grant.accountId,
269
+ workspaceId,
270
+ subjectId: grant.subjectId,
271
+ capabilityId: inline.capabilityId,
272
+ pluginKey: inline.pluginKey,
273
+ source: "pack",
274
+ sourceUrl: inline.sourceUrl,
275
+ repositoryUrl: inline.repositoryUrl,
276
+ sourceCommit: inline.sourceCommit,
277
+ sourcePath: inline.sourcePath,
278
+ name: inline.name,
279
+ description: inline.description,
280
+ contentSha256: inline.contentSha256,
281
+ totalBytes: inline.totalBytes,
282
+ files: inline.files,
283
+ owner: {
284
+ kind: "pack",
285
+ id: prepared.installation.id,
286
+ removable: true,
287
+ },
288
+ });
289
+ await heartbeat();
290
+ await recordPackInlineSkillComponent(db, {
291
+ accountId: grant.accountId,
292
+ workspaceId,
293
+ packInstallationId: prepared.installation.id,
294
+ componentKey: inline.componentKey,
295
+ capabilityId: inline.capabilityId,
296
+ facetInstallationId: installed.facetInstallationId,
297
+ contentSha256: inline.contentSha256,
298
+ name: inline.name,
299
+ });
300
+ retainedComponentKeys.push(inline.componentKey);
301
+ retainedFacetInstallationIds.push(installed.facetInstallationId);
302
+ }
303
+
304
+ activeComponentKey = "pinned-components";
305
+ await heartbeat();
306
+ const adopted = await adoptPackComponentReferences(db, {
307
+ accountId: grant.accountId,
308
+ workspaceId,
309
+ packInstallationId: prepared.installation.id,
310
+ references: pack.components,
311
+ });
312
+ retainedComponentKeys.push(...adopted.components.map((component) => component.key));
313
+ retainedFacetInstallationIds.push(...adopted.retainedFacetInstallationIds);
314
+ retainedBindingIds.push(...adopted.retainedBindingIds);
315
+
316
+ activeComponentKey = "ownership-finalize";
317
+ await heartbeat();
318
+ await finalizePackComponentOwnership(db, {
319
+ accountId: grant.accountId,
320
+ workspaceId,
321
+ packInstallationId: prepared.installation.id,
322
+ retainedComponentKeys,
323
+ retainedFacetInstallationIds,
324
+ retainedBindingIds,
325
+ });
326
+ await heartbeat();
327
+ const finalized = await finalizePackInstallationOperation(db, {
328
+ accountId: grant.accountId,
329
+ workspaceId,
330
+ operationId: prepared.operationId,
331
+ operationVersion: prepared.operationVersion,
332
+ packInstallationId: prepared.installation.id,
333
+ packId: pack.id,
334
+ result: {
335
+ status: "installed",
336
+ packId: pack.id,
337
+ manifestDigest: preview.manifestDigest,
338
+ componentCount: retainedComponentKeys.length,
339
+ },
340
+ });
341
+ return c.json(
342
+ PackInstallation.parse(finalized),
343
+ preview.installationVersion === null ? 201 : 200,
344
+ );
345
+ } catch (error) {
346
+ await deferPackInstallationOperation(db, {
347
+ accountId: grant.accountId,
348
+ workspaceId,
349
+ operationId: prepared.operationId,
350
+ operationVersion: prepared.operationVersion,
351
+ packInstallationId: prepared.installation.id,
352
+ phase: `component_failed:${activeComponentKey}`,
353
+ errorCode: packFailureCode(error),
354
+ }).catch(() => undefined);
355
+ if (error instanceof PackComponentResolutionError) {
356
+ throw new HTTPException(409, {
357
+ message: `Pack component ${error.componentKey} changed after preview. Review the installation plan and retry with the same idempotency key.`,
358
+ });
359
+ }
360
+ if (error instanceof CapabilityComponentVersionConflictError) {
361
+ throw new HTTPException(409, {
362
+ message: `Pack component ${activeComponentKey} is pinned by another installed owner. Resolve that version conflict and retry with the same idempotency key.`,
363
+ });
364
+ }
365
+ if (error instanceof PackOperationClaimLostError) {
366
+ throw packMutationHttpError(error);
367
+ }
368
+ if (error instanceof HTTPException) throw error;
369
+ throw new HTTPException(422, {
370
+ message: `Pack component ${activeComponentKey} could not be installed. No Pack-owned component is active until the operation completes; retry with the same idempotency key.`,
371
+ });
372
+ }
373
+ });
374
+
375
+ app.get("/v1/workspaces/:workspaceId/packs/:packId/uninstall-preview", async (c) => {
376
+ const workspaceId = c.req.param("workspaceId");
377
+ await requireAccessGrant(c, deps, workspaceId, "workspace:read");
378
+ const pack = await requirePack(db, workspaceId, c.req.param("packId"));
379
+ const installation = await getPackInstallation(db, workspaceId, pack.id);
380
+ const installed = Boolean(installation && installation.status !== "disabled");
381
+ const components = installed
382
+ ? await previewPackComponentRelease(db, workspaceId, installation!.id)
383
+ : [];
384
+ return c.json(
385
+ PackUninstallPreview.parse({
386
+ packId: pack.id,
387
+ installed,
388
+ installationVersion: installation?.version ?? null,
389
+ components,
390
+ }),
391
+ );
392
+ });
393
+
394
+ app.delete("/v1/workspaces/:workspaceId/packs/:packId/installation", async (c) => {
395
+ const workspaceId = c.req.param("workspaceId");
396
+ const grant = await requireAccessGrant(c, deps, workspaceId, "workspace:admin");
397
+ const pack = await requirePack(db, workspaceId, c.req.param("packId"));
398
+ const payload = UninstallPackRequest.parse(await c.req.json());
399
+ const requestDigest = sha256(
400
+ stableJson({
401
+ packId: pack.id,
402
+ expectedInstallationVersion: payload.expectedInstallationVersion,
403
+ }),
404
+ );
405
+ let prepared: Awaited<ReturnType<typeof preparePackUninstallOperation>>;
406
+ try {
407
+ prepared = await preparePackUninstallOperation(db, {
408
+ accountId: grant.accountId,
409
+ workspaceId,
410
+ subjectId: grant.subjectId,
411
+ packId: pack.id,
412
+ expectedInstallationVersion: payload.expectedInstallationVersion,
413
+ idempotencyKey: payload.idempotencyKey,
414
+ requestDigest,
415
+ });
416
+ } catch (error) {
417
+ throw packMutationHttpError(error);
418
+ }
419
+ if (!("installation" in prepared)) {
420
+ return c.json(
421
+ UninstallPackResult.parse({
422
+ packId: pack.id,
423
+ status:
424
+ prepared.replayResult.status === "not_installed" ? "not_installed" : "uninstalled",
425
+ retainedComponents: Array.isArray(prepared.replayResult.retainedComponents)
426
+ ? prepared.replayResult.retainedComponents
427
+ : [],
428
+ }),
429
+ );
430
+ }
431
+ try {
432
+ await touchPackInstallationOperation(db, {
433
+ accountId: grant.accountId,
434
+ workspaceId,
435
+ operationId: prepared.operationId,
436
+ operationVersion: prepared.operationVersion,
437
+ });
438
+ const released = await releasePackComponents(db, {
439
+ accountId: grant.accountId,
440
+ workspaceId,
441
+ packInstallationId: prepared.installation.id,
442
+ });
443
+ await touchPackInstallationOperation(db, {
444
+ accountId: grant.accountId,
445
+ workspaceId,
446
+ operationId: prepared.operationId,
447
+ operationVersion: prepared.operationVersion,
448
+ });
449
+ const result = UninstallPackResult.parse({
450
+ packId: pack.id,
451
+ status: "uninstalled",
452
+ retainedComponents: [...new Set(released.retainedComponents)],
453
+ });
454
+ await finalizePackUninstallOperation(db, {
455
+ accountId: grant.accountId,
456
+ workspaceId,
457
+ operationId: prepared.operationId,
458
+ operationVersion: prepared.operationVersion,
459
+ packInstallationId: prepared.installation.id,
460
+ packId: pack.id,
461
+ result,
462
+ });
463
+ return c.json(result);
464
+ } catch (error) {
465
+ await deferPackInstallationOperation(db, {
466
+ accountId: grant.accountId,
467
+ workspaceId,
468
+ operationId: prepared.operationId,
469
+ operationVersion: prepared.operationVersion,
470
+ packInstallationId: prepared.installation.id,
471
+ phase: "uninstall_failed",
472
+ errorCode: packFailureCode(error),
473
+ }).catch(() => undefined);
474
+ if (error instanceof PackOperationClaimLostError) {
475
+ throw packMutationHttpError(error);
476
+ }
477
+ if (error instanceof HTTPException) throw error;
478
+ throw new HTTPException(422, {
479
+ message: "The Pack could not be safely uninstalled. Retry with the same idempotency key.",
480
+ });
481
+ }
482
+ });
483
+
114
484
  app.post("/v1/workspaces/:workspaceId/packs/:packId/enable", async (c) => {
115
485
  const workspaceId = c.req.param("workspaceId");
116
486
  const grant = await requireAccessGrant(c, deps, workspaceId, "workspace:admin");
117
487
  const pack = await requirePack(db, workspaceId, c.req.param("packId"));
488
+ if (capabilityPackRequiresInstallationPlan(pack)) {
489
+ throw new HTTPException(409, {
490
+ message:
491
+ "This Pack uses component or Rig requirements. Preview and install it through the Pack installation flow.",
492
+ });
493
+ }
118
494
  await assertPackSandboxImageCompatible(db, workspaceId, pack);
119
495
  const existing = await getPackInstallation(db, workspaceId, pack.id);
120
496
  const payload = EnablePackRequest.parse(await c.req.json());
@@ -222,6 +598,7 @@ export function registerPackRoutes(app: Hono, deps: ApiRouteDeps): void {
222
598
  payload: {
223
599
  name: payload.name ?? "Daily social media analysis",
224
600
  status: payload.status,
601
+ action: { kind: "agent_turn" },
225
602
  schedule: {
226
603
  type: "calendar",
227
604
  timeZone: payload.timeZone,
@@ -232,6 +609,7 @@ export function registerPackRoutes(app: Hono, deps: ApiRouteDeps): void {
232
609
  overlapPolicy: payload.overlapPolicy,
233
610
  agentConfig,
234
611
  ...(installationVariableSetId ? { variableSetId: installationVariableSetId } : {}),
612
+ ...(installation.selectedRigId ? { rigId: installation.selectedRigId } : {}),
235
613
  metadata: {
236
614
  packId: pack.id,
237
615
  packVersion: pack.version,
@@ -247,6 +625,54 @@ export function registerPackRoutes(app: Hono, deps: ApiRouteDeps): void {
247
625
  );
248
626
  }
249
627
 
628
+ function packMutationHttpError(error: unknown): HTTPException {
629
+ if (error instanceof PackManifestChangedError) {
630
+ return new HTTPException(409, {
631
+ message: "The Pack manifest changed after preview. Review the updated installation plan.",
632
+ });
633
+ }
634
+ if (error instanceof PackOperationClaimLostError) {
635
+ return new HTTPException(409, {
636
+ message: "This Pack operation was recovered by another request; review the current state",
637
+ });
638
+ }
639
+ if (error instanceof PackOperationIdempotencyError) {
640
+ return new HTTPException(409, {
641
+ message: "Pack idempotency key was already used",
642
+ });
643
+ }
644
+ if (error instanceof PackOperationInProgressError) {
645
+ return new HTTPException(409, {
646
+ message: "Another Pack operation is still running; wait for it to finish, then review again",
647
+ });
648
+ }
649
+ if (error instanceof PackInstallationVersionConflictError) {
650
+ return new HTTPException(409, {
651
+ message: "Pack installation changed after preview",
652
+ });
653
+ }
654
+ if (error instanceof PackInstallationVersionRequiredError) {
655
+ return new HTTPException(400, {
656
+ message: "Updating or repairing a Pack requires the previewed installation version",
657
+ });
658
+ }
659
+ if (error instanceof HTTPException) return error;
660
+ return new HTTPException(422, {
661
+ message: error instanceof Error ? error.message : "Pack mutation failed",
662
+ });
663
+ }
664
+
665
+ function packFailureCode(error: unknown): string {
666
+ if (error instanceof HTTPException) return `http_${error.status}`;
667
+ if (error instanceof PackComponentResolutionError) return `component_${error.status}`;
668
+ if (error instanceof CapabilityComponentVersionConflictError) return "component_version_conflict";
669
+ return "component_install_failed";
670
+ }
671
+
672
+ function sha256(value: string): string {
673
+ return createHash("sha256").update(value).digest("hex");
674
+ }
675
+
250
676
  async function requirePack(db: ApiRouteDeps["db"], workspaceId: string, packId: string) {
251
677
  const pack = await resolveCapabilityPack(db, workspaceId, packId);
252
678
  if (!pack) {
@@ -268,7 +694,9 @@ async function resolveSocialConnections(
268
694
  ids.map(async (id) => {
269
695
  const connection = await getSocialConnection(db, workspaceId, id, subjectId);
270
696
  if (!connection) {
271
- throw new HTTPException(422, { message: `unknown social connection: ${id}` });
697
+ throw new HTTPException(422, {
698
+ message: `unknown social connection: ${id}`,
699
+ });
272
700
  }
273
701
  return connection;
274
702
  }),
@@ -293,7 +721,9 @@ async function validateDocumentBaseIds(
293
721
  for (const baseId of [...new Set(documentBaseIds)]) {
294
722
  const base = await getDocumentBase(db, workspaceId, baseId);
295
723
  if (!base) {
296
- throw new HTTPException(422, { message: `unknown document base: ${baseId}` });
724
+ throw new HTTPException(422, {
725
+ message: `unknown document base: ${baseId}`,
726
+ });
297
727
  }
298
728
  }
299
729
  }