@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
package/dist/index.js CHANGED
@@ -1,7 +1,10 @@
1
1
  import {
2
+ SlackBotProviderError,
2
3
  createAppComposition,
3
- startSlackInteractionPump
4
- } from "./chunk-HWXJW5C7.js";
4
+ createOpenGeniSlackBotInteractionClient,
5
+ startSlackInteractionPump,
6
+ startTemporalScheduleCleanupPump
7
+ } from "./chunk-JIKNR5YL.js";
5
8
 
6
9
  // src/index.ts
7
10
  import {
@@ -21,6 +24,8 @@ import {
21
24
  } from "@opengeni/db";
22
25
  import { createNatsEventBus } from "@opengeni/events";
23
26
  import { createObservability, logStartupDependencyRetry } from "@opengeni/observability";
27
+ import { createObjectStorage } from "@opengeni/storage";
28
+ import { isArtifactRuntimeConfigured } from "@opengeni/artifact-tool/runtime/development";
24
29
  import { SESSION_WORKFLOW_WAKE_DISPATCHER_SCHEDULE_ID } from "@opengeni/core";
25
30
  import {
26
31
  Connection,
@@ -41,11 +46,11 @@ function eventAttributes(attributes) {
41
46
  if (!attributes) {
42
47
  return void 0;
43
48
  }
44
- const sanitized = {};
49
+ const projected = {};
45
50
  for (const [key, value] of Object.entries(attributes)) {
46
- sanitized[key] = eventAttributeValue(value);
51
+ projected[key] = eventAttributeValue(value);
47
52
  }
48
- return sanitized;
53
+ return projected;
49
54
  }
50
55
  function eventAttributeValue(value) {
51
56
  if (value === null || value === void 0 || typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
@@ -72,6 +77,7 @@ import {
72
77
  workspaceAgentPermissions
73
78
  } from "@opengeni/events";
74
79
  var AUTH_CALLOUT_SUBJECT = "$SYS.REQ.USER.AUTH";
80
+ var NATS_USER_JWT_TTL_SECONDS = 5 * 60;
75
81
  async function handleAuthorizationRequest(deps, requestBytes) {
76
82
  const requestJwt = Buffer.from(requestBytes).toString("utf8");
77
83
  const decoded = decodeAuthRequest(requestJwt);
@@ -109,10 +115,14 @@ async function handleAuthorizationRequest(deps, requestBytes) {
109
115
  });
110
116
  return deny("enrollment is not active");
111
117
  }
112
- if (enrollment.id !== claims.enrollmentId) {
118
+ if (enrollment.workspaceId !== claims.workspaceId || enrollment.id !== claims.enrollmentId || enrollment.id !== claims.agentId || claims.agentId !== claims.enrollmentId || claims.subjectPrefix !== `agent.${claims.workspaceId}.${claims.agentId}`) {
113
119
  return deny("enrollment identity mismatch");
114
120
  }
121
+ if (enrollment.credentialGeneration !== claims.credentialGeneration) {
122
+ return deny("enrollment credential generation mismatch");
123
+ }
115
124
  const permissions = workspaceAgentPermissions(claims.workspaceId);
125
+ const nowSeconds = Math.floor(Date.now() / 1e3);
116
126
  const userJwt = mintUserJwt({
117
127
  userPublicKey: decoded.userNkey,
118
128
  accountSeed: deps.callout.accountSeed,
@@ -125,7 +135,7 @@ async function handleAuthorizationRequest(deps, requestBytes) {
125
135
  audienceAccount: deps.callout.accountName,
126
136
  // Tie the credential's life to the bearer's remaining life: a revoked/expired
127
137
  // enrollment cannot outlive its bearer at the NATS layer either.
128
- expiresAtSeconds: claims.exp
138
+ expiresAtSeconds: Math.min(claims.exp, nowSeconds + NATS_USER_JWT_TTL_SECONDS)
129
139
  });
130
140
  const response = mintAuthResponse({
131
141
  userPublicKey: decoded.userNkey,
@@ -159,19 +169,26 @@ async function startAuthCalloutResponder(deps, natsUrl) {
159
169
  // src/sandbox/metrics-ingestion.ts
160
170
  import {
161
171
  clearEnrollmentWentOffline,
172
+ disconnectAttachedBrowserDevices,
162
173
  getEnrollment as getEnrollment2,
163
174
  ingestMachineMetricsSample,
175
+ reconcileAttachedBrowserInventory,
164
176
  sessionsWithActiveOpOnEnrollment,
165
177
  setEnrollmentDisplayState,
166
178
  setEnrollmentOpStreamState,
167
179
  setEnrollmentWentOffline,
168
180
  touchEnrollmentLastSeen
169
181
  } from "@opengeni/db";
182
+ import {
183
+ AttachedBrowserInventorySnapshot as AttachedBrowserInventoryContract
184
+ } from "@opengeni/contracts";
170
185
  import { appendAndPublishEvents } from "@opengeni/events";
171
186
  import {
172
187
  AgentEvent,
188
+ Arch,
173
189
  GoingOfflineReason,
174
190
  Hello,
191
+ Os,
175
192
  goingOfflineReasonToJSON
176
193
  } from "@opengeni/agent-proto";
177
194
  var AGENT_EVENTS_SUBJECT = "agent.*.*.events";
@@ -210,6 +227,73 @@ function wireSampleToDbSample(wire) {
210
227
  sampledAt: wire.sampledAtMs && Number(wire.sampledAtMs) > 0 ? new Date(Number(wire.sampledAtMs)) : /* @__PURE__ */ new Date()
211
228
  };
212
229
  }
230
+ function wireAttachedBrowserInventoryToContract(wire) {
231
+ return AttachedBrowserInventoryContract.parse({
232
+ bridgeGeneration: wire.bridgeGeneration,
233
+ revision: safeWireInteger(wire.revision, "browser inventory revision"),
234
+ devices: wire.devices.map((device) => ({
235
+ id: device.id,
236
+ name: device.name,
237
+ profileLabel: device.profileLabel.trim() || null,
238
+ browserName: device.browserName,
239
+ browserVersion: device.browserVersion,
240
+ extensionVersion: device.extensionVersion,
241
+ platform: wireBrowserPlatform(device.platform),
242
+ architecture: wireBrowserArchitecture(device.arch),
243
+ connectionGeneration: device.connectionGeneration,
244
+ inventoryRevision: safeWireInteger(
245
+ device.inventoryRevision,
246
+ "attached browser inventory revision"
247
+ ),
248
+ tabCount: safeWireInteger(device.tabCount, "attached browser tab count"),
249
+ capabilities: device.capabilities
250
+ }))
251
+ });
252
+ }
253
+ function safeWireInteger(value, label) {
254
+ let parsed;
255
+ try {
256
+ parsed = BigInt(value);
257
+ } catch {
258
+ throw new Error(`${label} is not an integer`);
259
+ }
260
+ if (parsed < 0n || parsed > BigInt(Number.MAX_SAFE_INTEGER)) {
261
+ throw new Error(`${label} is outside the safe integer range`);
262
+ }
263
+ return Number(parsed);
264
+ }
265
+ function wireBrowserPlatform(platform) {
266
+ switch (platform) {
267
+ case Os.OS_LINUX:
268
+ return "linux";
269
+ case Os.OS_MACOS:
270
+ return "macos";
271
+ case Os.OS_WINDOWS:
272
+ return "windows";
273
+ default:
274
+ throw new Error("attached browser platform is unspecified or unsupported");
275
+ }
276
+ }
277
+ function wireBrowserArchitecture(architecture) {
278
+ switch (architecture) {
279
+ case Arch.ARCH_X86_64:
280
+ return "x64";
281
+ case Arch.ARCH_AARCH64:
282
+ return "arm64";
283
+ default:
284
+ throw new Error("attached browser architecture is unspecified or unsupported");
285
+ }
286
+ }
287
+ async function ingestAttachedBrowserInventory(db, input) {
288
+ const enrollment = await getEnrollment2(db, input.workspaceId, input.agentId);
289
+ if (!enrollment) return;
290
+ await reconcileAttachedBrowserInventory(db, {
291
+ accountId: enrollment.accountId,
292
+ workspaceId: input.workspaceId,
293
+ enrollmentId: input.agentId,
294
+ snapshot: wireAttachedBrowserInventoryToContract(input.inventory)
295
+ });
296
+ }
213
297
  async function ingestHeartbeat(db, input) {
214
298
  const enrollment = await getEnrollment2(db, input.workspaceId, input.agentId);
215
299
  if (!enrollment) {
@@ -280,6 +364,18 @@ async function handleAgentEventPayload(db, observability, payload, subject, bus)
280
364
  enrollmentId: ids.agentId,
281
365
  reason
282
366
  });
367
+ try {
368
+ await disconnectAttachedBrowserDevices(db, {
369
+ accountId: enrollment.accountId,
370
+ workspaceId: ids.workspaceId,
371
+ enrollmentId: ids.agentId
372
+ });
373
+ } catch (error) {
374
+ observability?.warn?.("Failed to disconnect attached browsers with their machine", {
375
+ subject,
376
+ error: error instanceof Error ? error.message : String(error)
377
+ });
378
+ }
283
379
  if (bus) {
284
380
  const isSelfUpdate = event.event.goingOffline.reason === GoingOfflineReason.GOING_OFFLINE_REASON_UPDATE;
285
381
  await fanOutMachineLinkEvents(
@@ -315,10 +411,23 @@ async function handleAgentEventPayload(db, observability, payload, subject, bus)
315
411
  if (event.event?.$case !== "heartbeat") {
316
412
  return;
317
413
  }
318
- const metrics = event.event.heartbeat.metrics;
319
- if (!metrics) {
320
- return;
414
+ const heartbeat = event.event.heartbeat;
415
+ if (heartbeat.attachedBrowserInventory) {
416
+ try {
417
+ await ingestAttachedBrowserInventory(db, {
418
+ workspaceId: ids.workspaceId,
419
+ agentId: ids.agentId,
420
+ inventory: heartbeat.attachedBrowserInventory
421
+ });
422
+ } catch (error) {
423
+ observability?.warn?.("Failed to ingest an attached-browser inventory", {
424
+ subject,
425
+ error: error instanceof Error ? error.message : String(error)
426
+ });
427
+ }
321
428
  }
429
+ const metrics = heartbeat.metrics;
430
+ if (!metrics) return;
322
431
  try {
323
432
  await ingestHeartbeat(db, {
324
433
  workspaceId: ids.workspaceId,
@@ -453,6 +562,1592 @@ function startHelloIngestion(deps) {
453
562
  );
454
563
  }
455
564
 
565
+ // src/memory-slack-delivery.ts
566
+ import {
567
+ claimMemorySlackPublication,
568
+ completeMemorySlackPublication,
569
+ currentMemorySlackConfigurationMatches,
570
+ deferMemorySlackPublication,
571
+ failMemorySlackPublication
572
+ } from "@opengeni/db";
573
+ import { sanitizeSlackPublicationText } from "@opengeni/core";
574
+ var DELIVERY_LEASE_MS = 3e4;
575
+ var MAX_DELIVERY_ATTEMPTS = 8;
576
+ var MAX_DELIVERY_RETRY_MS = 5 * 6e4;
577
+ var MAX_SLACK_MESSAGE_CHARS = 3500;
578
+ async function drainMemorySlackPublicationsOnce(deps) {
579
+ const claimHolderId = crypto.randomUUID();
580
+ const publication = await claimMemorySlackPublication(deps.db, claimHolderId, DELIVERY_LEASE_MS);
581
+ if (!publication) return false;
582
+ try {
583
+ if (!await currentMemorySlackConfigurationMatches(deps.db, publication)) {
584
+ await failMemorySlackPublication(deps.db, {
585
+ publication,
586
+ claimHolderId,
587
+ errorCode: "configuration_changed",
588
+ cancelled: true
589
+ });
590
+ return true;
591
+ }
592
+ const client = await createOpenGeniSlackBotInteractionClient(deps, {
593
+ accountId: publication.accountId,
594
+ workspaceId: publication.workspaceId,
595
+ connectionId: publication.connectionId,
596
+ subjectId: publication.initiatorSubjectId,
597
+ sessionId: publication.sessionId
598
+ });
599
+ const result = await client.postMessage({
600
+ operationId: publication.operationId,
601
+ channelId: publication.slackChannelId,
602
+ text: formatMemorySlackPublicationMessage(deps, publication),
603
+ requireActiveNonSharedChannel: true
604
+ });
605
+ const completed = await completeMemorySlackPublication(deps.db, {
606
+ publication,
607
+ claimHolderId,
608
+ slackChannelId: result.channelId,
609
+ slackMessageTimestamp: result.timestamp
610
+ });
611
+ if (!completed) throw new Error("Memory Slack publication lost its durable claim");
612
+ } catch (error) {
613
+ const errorCode = deliveryErrorCode(error);
614
+ if (memorySlackDestinationDrift(error)) {
615
+ await failMemorySlackPublication(deps.db, {
616
+ publication,
617
+ claimHolderId,
618
+ errorCode,
619
+ cancelled: true
620
+ }).catch(() => void 0);
621
+ } else if (publication.attemptCount >= MAX_DELIVERY_ATTEMPTS || permanentDeliveryError(error)) {
622
+ await failMemorySlackPublication(deps.db, {
623
+ publication,
624
+ claimHolderId,
625
+ errorCode
626
+ }).catch(() => void 0);
627
+ } else {
628
+ await deferMemorySlackPublication(deps.db, {
629
+ publication,
630
+ claimHolderId,
631
+ errorCode,
632
+ retryAt: new Date(Date.now() + deliveryRetryMs(error, publication.attemptCount))
633
+ }).catch(() => void 0);
634
+ }
635
+ }
636
+ return true;
637
+ }
638
+ function startMemorySlackPublicationPump(deps, options = {}) {
639
+ let stopped = false;
640
+ let running;
641
+ const intervalMs = Math.max(250, options.intervalMs ?? 1e3);
642
+ const maxPerTick = Math.max(1, Math.min(50, options.maxPerTick ?? 10));
643
+ const tick = () => {
644
+ if (stopped || running) return;
645
+ running = (async () => {
646
+ for (let index = 0; index < maxPerTick; index += 1) {
647
+ if (!await drainMemorySlackPublicationsOnce(deps)) break;
648
+ }
649
+ })().catch((error) => {
650
+ deps.observability?.error("Memory Slack publication claim failed", {
651
+ errorClass: error instanceof Error ? error.name : "MemorySlackPublicationPumpError",
652
+ errorCode: deliveryErrorCode(error),
653
+ origin: "api"
654
+ });
655
+ }).finally(() => {
656
+ running = void 0;
657
+ });
658
+ };
659
+ const timer = setInterval(tick, intervalMs);
660
+ queueMicrotask(tick);
661
+ return async () => {
662
+ stopped = true;
663
+ clearInterval(timer);
664
+ await running;
665
+ };
666
+ }
667
+ function formatMemorySlackPublicationMessage(deps, publication) {
668
+ const projection = publication.projection;
669
+ const summary = boundedProjectionString(projection["summary"], 512) || "Important change";
670
+ const owner = boundedProjectionString(projection["ownerLabel"], 96);
671
+ const occurredAt = boundedProjectionString(projection["occurredAt"], 64);
672
+ const changeKind = boundedProjectionString(projection["changeKind"], 32);
673
+ const destination = boundedProjectionString(projection["destination"], 64);
674
+ const outcome = boundedProjectionString(projection["outcome"], 64);
675
+ const heading = publication.sourceType === "workspace_memory" ? `Workspace Memory ${changeKind || "change"}` : `Governed learning ${outcome || "change"}`;
676
+ const details = [
677
+ `Importance: ${publication.importance}`,
678
+ ...destination ? [`Surface: ${destination}`] : [],
679
+ ...owner ? [`Owner: ${owner}`] : [],
680
+ ...occurredAt ? [`Recorded: ${occurredAt}`] : []
681
+ ];
682
+ const path = publication.sourceType === "workspace_memory" ? `/workspaces/${publication.workspaceId}/memory?memoryId=${encodeURIComponent(publication.sourceId)}` : `/workspaces/${publication.workspaceId}/workspace-state`;
683
+ const base = deps.settings.webBaseUrl ?? deps.settings.publicBaseUrl;
684
+ const link = base ? new URL(path, base).toString() : null;
685
+ const text = `*${heading}*
686
+ ${summary}
687
+
688
+ ${details.join(" \xB7 ")}${link ? `
689
+ ${link}` : ""}`;
690
+ return text.length <= MAX_SLACK_MESSAGE_CHARS ? text : `${text.slice(0, MAX_SLACK_MESSAGE_CHARS - 20)}
691
+ \u2026 message truncated`;
692
+ }
693
+ var PERMANENT_DELIVERY_CODES = /* @__PURE__ */ new Set([
694
+ "account_inactive",
695
+ "channel_not_found",
696
+ "invalid_auth",
697
+ "is_archived",
698
+ "not_authed",
699
+ "not_in_channel",
700
+ "token_expired",
701
+ "token_revoked"
702
+ ]);
703
+ var MEMORY_SLACK_DESTINATION_DRIFT_CODES = /* @__PURE__ */ new Set([
704
+ "channel_not_found",
705
+ "is_archived",
706
+ "not_in_channel",
707
+ "slack_connect_unsupported"
708
+ ]);
709
+ function memorySlackDestinationDrift(error) {
710
+ return error instanceof SlackBotProviderError && MEMORY_SLACK_DESTINATION_DRIFT_CODES.has(error.code);
711
+ }
712
+ function permanentDeliveryError(error) {
713
+ if (!(error instanceof SlackBotProviderError)) return false;
714
+ if (PERMANENT_DELIVERY_CODES.has(error.code)) return true;
715
+ const status = /^http_(\d{3})$/.exec(error.code)?.[1];
716
+ return status ? Number(status) >= 400 && Number(status) < 500 && status !== "408" && status !== "429" : false;
717
+ }
718
+ function deliveryRetryMs(error, attemptCount) {
719
+ if (error instanceof SlackBotProviderError && error.retryAfterMs) {
720
+ return Math.min(Math.max(error.retryAfterMs, 1e3), MAX_DELIVERY_RETRY_MS);
721
+ }
722
+ return Math.min(1e3 * 2 ** Math.max(0, attemptCount - 1), MAX_DELIVERY_RETRY_MS);
723
+ }
724
+ function deliveryErrorCode(error) {
725
+ if (error instanceof SlackBotProviderError) return error.code.slice(0, 128);
726
+ const raw = error instanceof Error ? error.name : "memory_slack_delivery_error";
727
+ return raw.toLowerCase().replace(/[^a-z0-9_-]/g, "_").slice(0, 128) || "error";
728
+ }
729
+ function boundedProjectionString(value, maxChars) {
730
+ return typeof value === "string" ? escapeSlackText(sanitizeSlackPublicationText(value).slice(0, maxChars)) : "";
731
+ }
732
+ function escapeSlackText(value) {
733
+ return value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;");
734
+ }
735
+
736
+ // src/editable-artifact-websocket.ts
737
+ import {
738
+ EditableArtifactDomainError,
739
+ EditableArtifactLiveError,
740
+ decodeEditableArtifactLiveClientWireFrame,
741
+ encodeEditableArtifactLiveServerWireFrame
742
+ } from "@opengeni/core";
743
+ var EDITABLE_ARTIFACT_LIVE_WEBSOCKET_PATH = "/v1/editable-artifacts/live";
744
+ var EDITABLE_ARTIFACT_LIVE_WEBSOCKET_PROTOCOL = "opengeni-artifact-v1";
745
+ var EDITABLE_ARTIFACT_LIVE_WEBSOCKET_MAX_MESSAGE_BYTES = 8 * 1024 * 1024 + 64 * 1024;
746
+ var MAX_QUEUED_MESSAGES = 64;
747
+ var MAX_QUEUED_BYTES = 12 * 1024 * 1024;
748
+ var EditableArtifactWebSocketTransport = class {
749
+ constructor(application) {
750
+ this.application = application;
751
+ this.websocket = Object.freeze({
752
+ open: (socket) => socket.data.attach(socket),
753
+ message: (socket, message) => socket.data.receive(message),
754
+ close: (socket) => socket.data.transportClosed()
755
+ });
756
+ }
757
+ websocket;
758
+ handles(request) {
759
+ return new URL(request.url).pathname === EDITABLE_ARTIFACT_LIVE_WEBSOCKET_PATH;
760
+ }
761
+ upgrade(request, server) {
762
+ const url = new URL(request.url);
763
+ if (url.pathname !== EDITABLE_ARTIFACT_LIVE_WEBSOCKET_PATH) return void 0;
764
+ if (request.method !== "GET") {
765
+ return new Response("Method Not Allowed", {
766
+ status: 405,
767
+ headers: { allow: "GET" }
768
+ });
769
+ }
770
+ if (!this.application) return new Response("Service Unavailable", { status: 503 });
771
+ if (!offeredProtocol(request.headers.get("sec-websocket-protocol"))) {
772
+ return new Response("WebSocket protocol required", { status: 426 });
773
+ }
774
+ const connection = new EditableArtifactWebSocketConnection(this.application);
775
+ const upgraded = server.upgrade(request, {
776
+ data: connection,
777
+ headers: {
778
+ "sec-websocket-protocol": EDITABLE_ARTIFACT_LIVE_WEBSOCKET_PROTOCOL
779
+ }
780
+ });
781
+ return upgraded ? void 0 : new Response("Bad Request", { status: 400 });
782
+ }
783
+ };
784
+ var EditableArtifactWebSocketConnection = class {
785
+ constructor(application) {
786
+ this.application = application;
787
+ }
788
+ socket = null;
789
+ session = null;
790
+ work = Promise.resolve();
791
+ queuedMessages = 0;
792
+ queuedBytes = 0;
793
+ terminal = false;
794
+ attach(socket) {
795
+ if (this.socket || this.terminal) {
796
+ socket.close(4400, "invalid connection state");
797
+ return;
798
+ }
799
+ this.socket = socket;
800
+ }
801
+ receive(message) {
802
+ if (this.terminal) return;
803
+ if (typeof message === "string") {
804
+ void this.fail("invalid_frame");
805
+ return;
806
+ }
807
+ const byteLength = message.byteLength;
808
+ if (byteLength === 0 || byteLength > EDITABLE_ARTIFACT_LIVE_WEBSOCKET_MAX_MESSAGE_BYTES || this.queuedMessages + 1 > MAX_QUEUED_MESSAGES || this.queuedBytes + byteLength > MAX_QUEUED_BYTES) {
809
+ void this.fail(
810
+ byteLength > EDITABLE_ARTIFACT_LIVE_WEBSOCKET_MAX_MESSAGE_BYTES ? "oversized_frame" : "slow_client"
811
+ );
812
+ return;
813
+ }
814
+ const bytes = message instanceof Uint8Array ? Uint8Array.from(message) : new Uint8Array(message).slice();
815
+ this.queuedMessages += 1;
816
+ this.queuedBytes += bytes.byteLength;
817
+ const run = this.work.then(async () => {
818
+ try {
819
+ await this.process(bytes);
820
+ } finally {
821
+ this.queuedMessages -= 1;
822
+ this.queuedBytes -= bytes.byteLength;
823
+ }
824
+ });
825
+ this.work = run.catch(async (error) => {
826
+ await this.handleFailure(error);
827
+ });
828
+ }
829
+ async send(frame) {
830
+ if (this.terminal || !this.socket) {
831
+ throw new EditableArtifactLiveError("closed", "Editable artifact socket is closed");
832
+ }
833
+ const bytes = encodeEditableArtifactLiveServerWireFrame(frame);
834
+ if (bytes.byteLength > EDITABLE_ARTIFACT_LIVE_WEBSOCKET_MAX_MESSAGE_BYTES) {
835
+ throw new EditableArtifactLiveError(
836
+ "oversized_frame",
837
+ "Editable artifact server frame exceeds socket limit",
838
+ { requiresSnapshot: true }
839
+ );
840
+ }
841
+ const accepted = this.socket.send(bytes, false);
842
+ if (!Number.isSafeInteger(accepted) || accepted <= 0) {
843
+ throw new EditableArtifactLiveError("closed", "Editable artifact socket rejected frame");
844
+ }
845
+ }
846
+ bufferedBytes() {
847
+ const buffered = this.socket?.getBufferedAmount?.() ?? this.socket?.bufferedAmount ?? 0;
848
+ return Number.isSafeInteger(buffered) && buffered >= 0 ? buffered : Number.MAX_SAFE_INTEGER;
849
+ }
850
+ close(close) {
851
+ if (this.terminal) return;
852
+ this.terminal = true;
853
+ this.socket?.close(closeCode(close.reason), close.reason);
854
+ }
855
+ transportClosed() {
856
+ if (this.terminal) return;
857
+ this.terminal = true;
858
+ void this.session?.close("transport_error");
859
+ }
860
+ async process(bytes) {
861
+ if (this.terminal) return;
862
+ const frame = decodeEditableArtifactLiveClientWireFrame(bytes);
863
+ if (!this.session) {
864
+ if (frame.type !== "open") {
865
+ throw new EditableArtifactLiveError("invalid_frame", "First socket frame must be open");
866
+ }
867
+ const session = await this.application.openLive({
868
+ token: frame.token,
869
+ artifactId: frame.artifactId,
870
+ protocolVersion: frame.protocolVersion,
871
+ resume: frame.resume,
872
+ sink: this
873
+ });
874
+ if (this.terminal) {
875
+ await session.close("transport_error");
876
+ return;
877
+ }
878
+ this.session = session;
879
+ void session.closed.then(() => {
880
+ if (!this.terminal)
881
+ this.close({
882
+ reason: "closed",
883
+ retryable: false,
884
+ requiresSnapshot: false
885
+ });
886
+ });
887
+ return;
888
+ }
889
+ if (frame.type === "open") {
890
+ throw new EditableArtifactLiveError("invalid_frame", "Socket is already open");
891
+ }
892
+ if (frame.artifactId !== this.session.artifactId || frame.streamEpoch !== this.session.streamEpoch) {
893
+ throw new EditableArtifactLiveError("stale_epoch", "Socket frame belongs to another stream");
894
+ }
895
+ if (frame.type === "applied") {
896
+ await this.session.acknowledge(frame);
897
+ return;
898
+ }
899
+ let receipt;
900
+ try {
901
+ receipt = await this.session.submitIntent(frame);
902
+ } catch (error) {
903
+ await this.send({
904
+ type: "mutationRejected",
905
+ protocolVersion: frame.protocolVersion,
906
+ artifactId: frame.artifactId,
907
+ streamEpoch: frame.streamEpoch,
908
+ requestHash: frame.requestHash,
909
+ ...mutationFailure(error)
910
+ });
911
+ return;
912
+ }
913
+ await this.send({
914
+ type: "transaction",
915
+ protocolVersion: frame.protocolVersion,
916
+ artifactId: frame.artifactId,
917
+ streamEpoch: frame.streamEpoch,
918
+ transaction: receipt.transaction
919
+ });
920
+ await this.send({
921
+ type: "mutationAccepted",
922
+ protocolVersion: frame.protocolVersion,
923
+ artifactId: frame.artifactId,
924
+ streamEpoch: frame.streamEpoch,
925
+ requestHash: receipt.requestHash,
926
+ clientTransactionId: receipt.clientTransactionId,
927
+ transactionId: receipt.transaction.transactionId,
928
+ startSequence: receipt.transaction.startSequence,
929
+ endSequence: receipt.transaction.endSequence,
930
+ stateHash: receipt.transaction.stateHash
931
+ });
932
+ }
933
+ async handleFailure(error) {
934
+ if (this.terminal) return;
935
+ if (error instanceof EditableArtifactLiveError) {
936
+ await this.fail(error.code === "invalid_ticket" ? "transport_error" : error.code);
937
+ return;
938
+ }
939
+ await this.fail("transport_error");
940
+ }
941
+ async fail(reason) {
942
+ if (this.terminal) return;
943
+ this.terminal = true;
944
+ await this.session?.close(reason).catch(() => void 0);
945
+ this.socket?.close(closeCode(reason ?? "transport_error"), reason ?? "transport_error");
946
+ }
947
+ };
948
+ function mutationFailure(error) {
949
+ if (error instanceof EditableArtifactDomainError) {
950
+ if (error.code === "forbidden") return { code: "forbidden", retryable: false };
951
+ if (["invalid_request", "request_hash_mismatch", "invalid_undo_target"].includes(error.code)) {
952
+ return { code: "invalid_request", retryable: false };
953
+ }
954
+ if (error.code === "kernel_contract_violation") {
955
+ return { code: "unsupported", retryable: false };
956
+ }
957
+ if (["retryable_conflict", "outbox_lease_conflict"].includes(error.code)) {
958
+ return { code: "conflict", retryable: true };
959
+ }
960
+ return { code: "conflict", retryable: false };
961
+ }
962
+ if (error instanceof EditableArtifactLiveError && error.code === "permission_changed") {
963
+ return { code: "forbidden", retryable: false };
964
+ }
965
+ return { code: "unavailable", retryable: true };
966
+ }
967
+ function offeredProtocol(value) {
968
+ return value?.split(",").map((part) => part.trim()).includes(EDITABLE_ARTIFACT_LIVE_WEBSOCKET_PROTOCOL) ?? false;
969
+ }
970
+ function closeCode(reason) {
971
+ if (reason === "permission_changed") return 4403;
972
+ if (reason === "ticket_expired" || reason === "ticket_replayed") return 4401;
973
+ if (reason === "protocol_mismatch") return 4406;
974
+ if (reason === "slow_client" || reason === "oversized_frame") return 4409;
975
+ if (reason === "closed") return 1e3;
976
+ return 4400;
977
+ }
978
+
979
+ // src/editable-artifact-production.ts
980
+ import { createHash as createHash4, randomUUID } from "crypto";
981
+ import {
982
+ editableArtifactMaterializerCapabilitiesForRuntime
983
+ } from "@opengeni/artifact-tool/runtime/materializer";
984
+ import {
985
+ EditableArtifactDurableExportService,
986
+ EditableArtifactAgentApplication,
987
+ EDITABLE_ARTIFACT_EXPORT_MAX_DOWNLOAD_BYTES,
988
+ EditableArtifactCompactionPipeline,
989
+ EditableArtifactGenesisPipeline,
990
+ EditableArtifactService,
991
+ MAX_EDITABLE_ARTIFACT_SNAPSHOT_BYTES as MAX_EDITABLE_ARTIFACT_SNAPSHOT_BYTES3,
992
+ ProductionEditableArtifactSnapshotVerifier,
993
+ SystemEditableArtifactClock,
994
+ editableArtifactLiveReadPortFromPostgres,
995
+ editableArtifactLiveTicketStorePortFromPostgres,
996
+ editableArtifactStorePortFromPostgres,
997
+ editableArtifactDurableExportStorePortFromPostgres,
998
+ ogatxEditableArtifactMutationIntentCodec,
999
+ editableArtifactId
1000
+ } from "@opengeni/core/editable-artifacts";
1001
+ import {
1002
+ EDITABLE_ARTIFACT_LIVE_PROTOCOL_VERSION,
1003
+ EditableArtifactApplication,
1004
+ EditableArtifactCompatibilityError,
1005
+ EditableArtifactLiveServer,
1006
+ SystemEditableArtifactLiveClock,
1007
+ SystemEditableArtifactLiveScheduler,
1008
+ WebCryptoEditableArtifactLiveTokens
1009
+ } from "@opengeni/core/editable-artifact-live";
1010
+ import {
1011
+ PostgresEditableArtifactLiveReadStore,
1012
+ PostgresEditableArtifactLiveTicketStore,
1013
+ PostgresEditableArtifactStore,
1014
+ listEditableArtifactIdsForSession,
1015
+ touchEditableArtifactSessionLink,
1016
+ dbSql,
1017
+ withRlsContext
1018
+ } from "@opengeni/db";
1019
+ import { PostgresEditableArtifactDurableExportStore } from "@opengeni/db/editable-artifact-durable-export";
1020
+ import {
1021
+ MAX_BOUNDED_OBJECT_CHUNK_BYTES as MAX_BOUNDED_OBJECT_CHUNK_BYTES2,
1022
+ createObjectStorageBoundedPorts
1023
+ } from "@opengeni/storage";
1024
+
1025
+ // src/editable-artifact-live-hints.ts
1026
+ import {
1027
+ decodeEditableArtifactBrokerHint,
1028
+ editableArtifactLiveHintSubject
1029
+ } from "@opengeni/core";
1030
+ var EventBusEditableArtifactLiveHints = class {
1031
+ constructor(bus) {
1032
+ this.bus = bus;
1033
+ }
1034
+ async subscribe(input) {
1035
+ const connection = this.bus.getOpStreamConnection?.();
1036
+ if (!connection) throw new Error("Event bus has no artifact hint subscription transport");
1037
+ const subject = editableArtifactLiveHintSubject(input.scope, input.artifactId);
1038
+ const subscription = connection.subscribe(subject);
1039
+ let active = true;
1040
+ try {
1041
+ await requireFlushBarrier(connection);
1042
+ } catch (error) {
1043
+ subscription.unsubscribe();
1044
+ throw error;
1045
+ }
1046
+ void (async () => {
1047
+ try {
1048
+ for await (const message of subscription) {
1049
+ if (!active) return;
1050
+ try {
1051
+ const hint = decodeEditableArtifactBrokerHint(message.data, {
1052
+ scope: input.scope,
1053
+ artifactId: input.artifactId
1054
+ });
1055
+ input.onHint({
1056
+ artifactId: input.artifactId,
1057
+ headSequence: hint.headSequence
1058
+ });
1059
+ } catch {
1060
+ }
1061
+ }
1062
+ } catch {
1063
+ if (active) input.onReconnect();
1064
+ }
1065
+ })();
1066
+ return () => {
1067
+ if (!active) return;
1068
+ active = false;
1069
+ subscription.unsubscribe();
1070
+ };
1071
+ }
1072
+ };
1073
+ async function requireFlushBarrier(connection) {
1074
+ if (!connection.flush) {
1075
+ throw new Error("Artifact hint subscription transport lacks a flush barrier");
1076
+ }
1077
+ await connection.flush();
1078
+ }
1079
+
1080
+ // src/editable-artifact-native-kernel.ts
1081
+ import { createHash } from "crypto";
1082
+ import { pathToFileURL } from "url";
1083
+ import {
1084
+ createNativeArtifactSession,
1085
+ openNativeArtifactSession
1086
+ } from "@opengeni/artifact-tool/native";
1087
+ import {
1088
+ doctorConfiguredArtifactRuntime
1089
+ } from "@opengeni/artifact-tool/runtime/development";
1090
+ import {
1091
+ decodeEditableArtifactCausalFrontier,
1092
+ encodeEditableArtifactCausalFrontier
1093
+ } from "@opengeni/contracts/editable-artifact-causal-frontier";
1094
+ import { decodeEditableArtifactSerializedCommit } from "@opengeni/contracts/editable-artifact-serialized-commit";
1095
+ import { decodeEditableArtifactMutationIntent } from "@opengeni/contracts/editable-artifacts";
1096
+ import {
1097
+ MAX_EDITABLE_ARTIFACT_SNAPSHOT_BYTES,
1098
+ causalFrontiersEqual,
1099
+ editableArtifactCausalFrontier,
1100
+ editableArtifactContentHash,
1101
+ editableArtifactReplicaId,
1102
+ editableArtifactStateHash
1103
+ } from "@opengeni/core";
1104
+ var MODEL_SCHEMA_VERSION = 1;
1105
+ var OPERATION_PROTOCOL_VERSION = 1;
1106
+ var CRDT_STATE_VERSION = 1;
1107
+ var SNAPSHOT_CHUNK_BYTES = 1024 * 1024;
1108
+ async function loadVerifiedNativeArtifactRuntimeBinding() {
1109
+ const location = await doctorConfiguredArtifactRuntime();
1110
+ const module = await import(
1111
+ /* @vite-ignore */
1112
+ pathToFileURL(location.skillFacadeEntrypoint).href
1113
+ );
1114
+ const candidate = module.getConfiguredArtifactRuntime?.();
1115
+ assertRuntime(candidate, location);
1116
+ return Object.freeze({ runtime: candidate, location, facade: module });
1117
+ }
1118
+ var NativeEditableArtifactKernelAdapter = class {
1119
+ constructor(runtime, objects) {
1120
+ this.runtime = runtime;
1121
+ this.objects = objects;
1122
+ if (runtime.kind !== "native" || runtime.target === "wasm-web") {
1123
+ throw new Error("Editable artifact authority requires a native kernel");
1124
+ }
1125
+ }
1126
+ async applyTransaction(request) {
1127
+ const session = await this.openCurrentState(request.state);
1128
+ try {
1129
+ if (request.modality === "spreadsheet") {
1130
+ if (session.modality !== "spreadsheet") throw new Error("Native modality mismatch");
1131
+ const committedTransactionBytes = session.authorTransaction(
1132
+ request.intentBytes,
1133
+ encodeEditableArtifactCausalFrontier(request.resolvedCausalBase)
1134
+ );
1135
+ return Object.freeze({
1136
+ modality: "spreadsheet",
1137
+ committedTransactionBytes,
1138
+ kernelVersion: this.runtime.buildIdentity,
1139
+ modelSchemaVersion: MODEL_SCHEMA_VERSION
1140
+ });
1141
+ }
1142
+ if (session.modality !== request.modality) {
1143
+ throw new Error("Native modality mismatch");
1144
+ }
1145
+ const nativeReceiptBytes = session.applyCommands(request.intent.commandBytes);
1146
+ return Object.freeze({
1147
+ modality: request.modality,
1148
+ nativeReceiptBytes,
1149
+ resultingStateHash: editableArtifactStateHash(session.stateHash()),
1150
+ kernelVersion: this.runtime.buildIdentity,
1151
+ modelSchemaVersion: MODEL_SCHEMA_VERSION
1152
+ });
1153
+ } finally {
1154
+ session.dispose();
1155
+ }
1156
+ }
1157
+ async query(input) {
1158
+ const session = await this.openCurrentState(input.state);
1159
+ try {
1160
+ return Uint8Array.from(session.query(input.queryBytes));
1161
+ } finally {
1162
+ session.dispose();
1163
+ }
1164
+ }
1165
+ async openCurrentState(state) {
1166
+ const snapshot = state.snapshot;
1167
+ if (!snapshot) throw new Error("Editable artifact has no verified replay snapshot");
1168
+ const snapshotBytes = await readVerifiedSnapshot(this.objects, snapshot);
1169
+ const session = openNativeArtifactSession(this.runtime, {
1170
+ modality: state.modality,
1171
+ snapshot: snapshotBytes
1172
+ });
1173
+ try {
1174
+ verifySnapshotBoundary(session, state);
1175
+ replayKernelTail(session, state);
1176
+ verifyDurableHead(session, state);
1177
+ return session;
1178
+ } catch (error) {
1179
+ session.dispose();
1180
+ throw error;
1181
+ }
1182
+ }
1183
+ async createCanonicalEmptySnapshot(input) {
1184
+ throwIfAborted(input.signal);
1185
+ const session = createNativeArtifactSession(this.runtime, {
1186
+ modality: input.modality,
1187
+ replicaNamespace: namespaceForArtifact(input.artifactId)
1188
+ });
1189
+ try {
1190
+ const snapshot = session.snapshot();
1191
+ throwIfAborted(input.signal);
1192
+ const revision = safeRevision(session.revision());
1193
+ if (revision !== 0) throw new Error("Native genesis revision is not zero");
1194
+ const common = {
1195
+ scope: input.scope,
1196
+ artifactId: input.artifactId,
1197
+ modality: input.modality,
1198
+ canonicalChunks: fixedChunks(snapshot, input.signal),
1199
+ canonicalByteSize: snapshot.byteLength,
1200
+ canonicalContentHash: editableArtifactContentHash(
1201
+ `sha256:${createHash("sha256").update(snapshot).digest("hex")}`
1202
+ ),
1203
+ stateHash: editableArtifactStateHash(session.stateHash()),
1204
+ coveredHeadSequence: 0,
1205
+ modelSchemaVersion: MODEL_SCHEMA_VERSION,
1206
+ kernelVersion: this.runtime.buildIdentity
1207
+ };
1208
+ if (session.modality === "spreadsheet") {
1209
+ return Object.freeze({
1210
+ ...common,
1211
+ modality: "spreadsheet",
1212
+ coveredCausalFrontier: domainFrontier(session.frontier()),
1213
+ operationProtocolVersion: OPERATION_PROTOCOL_VERSION,
1214
+ crdtStateVersion: CRDT_STATE_VERSION
1215
+ });
1216
+ }
1217
+ return Object.freeze({
1218
+ ...common,
1219
+ modality: session.modality,
1220
+ nativeRevision: 0
1221
+ });
1222
+ } finally {
1223
+ session.dispose();
1224
+ }
1225
+ }
1226
+ async createCanonicalSnapshot(input) {
1227
+ throwIfAborted(input.signal);
1228
+ const snapshot = input.state.snapshot;
1229
+ if (!snapshot) throw new Error("Editable artifact has no verified replay snapshot");
1230
+ const snapshotBytes = await readVerifiedSnapshot(this.objects, snapshot, input.signal);
1231
+ const session = openNativeArtifactSession(this.runtime, {
1232
+ modality: input.state.modality,
1233
+ snapshot: snapshotBytes
1234
+ });
1235
+ try {
1236
+ verifySnapshotBoundary(session, input.state);
1237
+ replayKernelTail(session, input.state);
1238
+ verifyDurableHead(session, input.state);
1239
+ throwIfAborted(input.signal);
1240
+ const canonical = session.snapshot();
1241
+ const common = {
1242
+ scope: input.state.artifact.scope,
1243
+ artifactId: input.state.artifact.id,
1244
+ modality: input.state.modality,
1245
+ canonicalChunks: fixedChunks(canonical, input.signal),
1246
+ canonicalByteSize: canonical.byteLength,
1247
+ canonicalContentHash: editableArtifactContentHash(
1248
+ `sha256:${createHash("sha256").update(canonical).digest("hex")}`
1249
+ ),
1250
+ stateHash: editableArtifactStateHash(session.stateHash()),
1251
+ coveredHeadSequence: input.state.artifact.headSequence,
1252
+ modelSchemaVersion: MODEL_SCHEMA_VERSION,
1253
+ kernelVersion: this.runtime.buildIdentity
1254
+ };
1255
+ if (session.modality === "spreadsheet") {
1256
+ if (input.state.modality !== "spreadsheet") throw new Error("Native modality mismatch");
1257
+ return Object.freeze({
1258
+ ...common,
1259
+ modality: session.modality,
1260
+ coveredCausalFrontier: domainFrontier(session.frontier()),
1261
+ operationProtocolVersion: OPERATION_PROTOCOL_VERSION,
1262
+ crdtStateVersion: CRDT_STATE_VERSION
1263
+ });
1264
+ }
1265
+ if (session.modality !== input.state.modality) throw new Error("Native modality mismatch");
1266
+ return Object.freeze({
1267
+ ...common,
1268
+ modality: session.modality,
1269
+ nativeRevision: safeRevision(session.revision())
1270
+ });
1271
+ } finally {
1272
+ session.dispose();
1273
+ }
1274
+ }
1275
+ async verifyAndReconstruct(input) {
1276
+ throwIfAborted(input.signal);
1277
+ const bytes = await collectExact(
1278
+ input.snapshotChunks,
1279
+ input.expectedSnapshotByteSize,
1280
+ input.signal
1281
+ );
1282
+ const session = openNativeArtifactSession(this.runtime, {
1283
+ modality: input.modality,
1284
+ snapshot: bytes
1285
+ });
1286
+ try {
1287
+ const canonical = session.snapshot();
1288
+ if (!equalBytes(canonical, bytes)) throw new Error("Native snapshot is not canonical");
1289
+ const expected = input.expectedSnapshot;
1290
+ if (expected.modality !== session.modality) throw new Error("Snapshot modality mismatch");
1291
+ const coveredHeadSequence = expected.coveredHeadSequence;
1292
+ const stateHash = editableArtifactStateHash(session.stateHash());
1293
+ if (stateHash !== expected.stateHash) throw new Error("Snapshot state hash mismatch");
1294
+ const canonicalContentHash = editableArtifactContentHash(
1295
+ `sha256:${createHash("sha256").update(canonical).digest("hex")}`
1296
+ );
1297
+ if (session.modality === "spreadsheet") {
1298
+ const coveredCausalFrontier = domainFrontier(session.frontier());
1299
+ if (expected.modality !== "spreadsheet" || !causalFrontiersEqual(coveredCausalFrontier, expected.coveredCausalFrontier)) {
1300
+ throw new Error("Snapshot causal coverage mismatch");
1301
+ }
1302
+ if (input.replayPlan && input.replayPlan.modality !== "spreadsheet") {
1303
+ throw new Error("Replay modality mismatch");
1304
+ }
1305
+ const replayedTarget2 = input.replayPlan ? await replaySpreadsheetPlan(session, input.replayPlan, input.signal) : void 0;
1306
+ return Object.freeze({
1307
+ modality: "spreadsheet",
1308
+ canonicalReencodeVerified: true,
1309
+ scope: input.scope,
1310
+ artifactId: input.artifactId,
1311
+ canonicalByteSize: canonical.byteLength,
1312
+ canonicalContentHash,
1313
+ coveredHeadSequence,
1314
+ stateHash,
1315
+ modelSchemaVersion: MODEL_SCHEMA_VERSION,
1316
+ kernelVersion: this.runtime.buildIdentity,
1317
+ fullCrdtStateVerified: true,
1318
+ coveredCausalFrontier,
1319
+ operationProtocolVersion: OPERATION_PROTOCOL_VERSION,
1320
+ crdtStateVersion: CRDT_STATE_VERSION,
1321
+ ...replayedTarget2 ? { replayedTarget: replayedTarget2 } : {}
1322
+ });
1323
+ }
1324
+ if (expected.modality === "spreadsheet") throw new Error("Snapshot modality mismatch");
1325
+ const nativeRevision = safeRevision(session.revision());
1326
+ if (nativeRevision !== expected.nativeRevision) {
1327
+ throw new Error("Snapshot native revision mismatch");
1328
+ }
1329
+ if (input.replayPlan && input.replayPlan.modality === "spreadsheet") {
1330
+ throw new Error("Replay modality mismatch");
1331
+ }
1332
+ const replayedTarget = input.replayPlan ? await replaySerializedPlan(session, input.replayPlan, input.signal) : void 0;
1333
+ return Object.freeze({
1334
+ modality: session.modality,
1335
+ canonicalReencodeVerified: true,
1336
+ scope: input.scope,
1337
+ artifactId: input.artifactId,
1338
+ canonicalByteSize: canonical.byteLength,
1339
+ canonicalContentHash,
1340
+ coveredHeadSequence,
1341
+ stateHash,
1342
+ modelSchemaVersion: MODEL_SCHEMA_VERSION,
1343
+ kernelVersion: this.runtime.buildIdentity,
1344
+ nativeRevision,
1345
+ ...replayedTarget ? { replayedTarget } : {}
1346
+ });
1347
+ } finally {
1348
+ session.dispose();
1349
+ }
1350
+ }
1351
+ };
1352
+ function assertRuntime(candidate, location) {
1353
+ if (typeof candidate !== "object" || candidate === null) {
1354
+ throw new Error("Verified artifact facade returned no runtime");
1355
+ }
1356
+ const runtime = candidate;
1357
+ if (runtime.kind !== "native" || runtime.target === "wasm-web" || runtime.target !== location.target || runtime.buildIdentity !== location.kernel.buildIdentity || typeof runtime.createCollaborationSession !== "function" || typeof runtime.openCollaborationSession !== "function" || typeof runtime.createDocumentSession !== "function" || typeof runtime.openDocumentSession !== "function" || typeof runtime.createPresentationSession !== "function" || typeof runtime.openPresentationSession !== "function") {
1358
+ throw new Error("Verified artifact facade returned an incompatible runtime");
1359
+ }
1360
+ }
1361
+ function verifySnapshotBoundary(session, state) {
1362
+ const snapshot = state.snapshot;
1363
+ if (!snapshot || session.modality !== state.modality)
1364
+ throw new Error("Snapshot modality mismatch");
1365
+ if (session.stateHash() !== snapshot.stateHash) {
1366
+ throw new Error("Native snapshot boundary does not match durable metadata");
1367
+ }
1368
+ if (state.modality === "spreadsheet") {
1369
+ if (session.modality !== "spreadsheet" || snapshot.modality !== "spreadsheet" || !causalFrontiersEqual(domainFrontier(session.frontier()), snapshot.coveredCausalFrontier)) {
1370
+ throw new Error("Native snapshot frontier does not match durable metadata");
1371
+ }
1372
+ } else if (session.modality === "spreadsheet" || snapshot.modality === "spreadsheet" || safeRevision(session.revision()) !== snapshot.nativeRevision || state.baseNativeRevision !== snapshot.nativeRevision) {
1373
+ throw new Error("Native snapshot revision does not match durable metadata");
1374
+ }
1375
+ }
1376
+ function replayKernelTail(session, state) {
1377
+ if (state.modality === "spreadsheet") {
1378
+ if (session.modality !== "spreadsheet") throw new Error("Native modality mismatch");
1379
+ for (const transaction of state.committedTransactionTail) {
1380
+ const priorNativeRevision = safeRevision(session.revision());
1381
+ if (session.stateHash() !== transaction.priorStateHash) {
1382
+ throw new Error("Spreadsheet replay prior state mismatch");
1383
+ }
1384
+ session.applyCommitted(transaction.committedTransactionBytes);
1385
+ if (safeRevision(session.revision()) !== priorNativeRevision + 1 || session.stateHash() !== transaction.stateHash || !causalFrontiersEqual(
1386
+ domainFrontier(session.frontier()),
1387
+ transaction.resultingCausalFrontier
1388
+ )) {
1389
+ throw new Error("Spreadsheet replay result mismatch");
1390
+ }
1391
+ }
1392
+ return;
1393
+ }
1394
+ if (session.modality !== state.modality) {
1395
+ throw new Error("Native modality mismatch");
1396
+ }
1397
+ for (const transaction of state.committedTransactionTail) {
1398
+ if (session.stateHash() !== transaction.priorStateHash || safeRevision(session.revision()) !== transaction.priorNativeRevision) {
1399
+ throw new Error("Serialized replay prior state mismatch");
1400
+ }
1401
+ const commit = decodeEditableArtifactSerializedCommit(
1402
+ transaction.committedTransactionBytes,
1403
+ state.modality
1404
+ );
1405
+ const intent = decodeEditableArtifactMutationIntent(commit.intentBytes);
1406
+ const receipt = session.applyCommands(intent.commandBytes);
1407
+ if (!equalBytes(receipt, commit.nativeReceiptBytes) || !equalBytes(receipt, transaction.nativeReceiptBytes) || safeRevision(session.revision()) !== transaction.nativeRevision || session.stateHash() !== transaction.stateHash) {
1408
+ throw new Error("Serialized replay result mismatch");
1409
+ }
1410
+ }
1411
+ }
1412
+ function verifyDurableHead(session, state) {
1413
+ const snapshot = state.snapshot;
1414
+ if (!snapshot) throw new Error("Editable artifact has no replay snapshot");
1415
+ let durableSequence = snapshot.coveredHeadSequence;
1416
+ for (const transaction of state.committedTransactionTail) {
1417
+ if (transaction.sequenceStart !== durableSequence + 1) {
1418
+ throw new Error("Durable transaction tail contains a sequence gap");
1419
+ }
1420
+ durableSequence = transaction.sequenceEnd;
1421
+ }
1422
+ if (session.modality !== state.modality || durableSequence !== state.artifact.headSequence || session.stateHash() !== state.artifact.stateHash) {
1423
+ throw new Error("Reconstructed native state does not match the durable head");
1424
+ }
1425
+ if (state.modality === "spreadsheet" && (session.modality !== "spreadsheet" || !causalFrontiersEqual(domainFrontier(session.frontier()), state.artifact.causalFrontier))) {
1426
+ throw new Error("Reconstructed native frontier does not match the durable head");
1427
+ }
1428
+ if (state.modality !== "spreadsheet") {
1429
+ const expectedNativeRevision = state.committedTransactionTail.at(-1)?.nativeRevision ?? state.baseNativeRevision;
1430
+ if (safeRevision(session.revision()) !== expectedNativeRevision) {
1431
+ throw new Error("Reconstructed native revision does not match the serialized tail");
1432
+ }
1433
+ }
1434
+ }
1435
+ async function replaySpreadsheetPlan(session, plan, signal) {
1436
+ let sequence = plan.baseHeadSequence;
1437
+ for await (const segment of plan.segments) {
1438
+ throwIfAborted(signal);
1439
+ if (segment.sequenceStart !== sequence + 1) throw new Error("Replay sequence gap");
1440
+ if (segment.modality !== "spreadsheet") throw new Error("Replay segment modality mismatch");
1441
+ session.applyCommitted(segment.bytes);
1442
+ sequence = segment.sequenceEnd;
1443
+ }
1444
+ if (sequence !== plan.targetHeadSequence || session.stateHash() !== plan.targetStateHash) {
1445
+ throw new Error("Replay target mismatch");
1446
+ }
1447
+ const causalFrontier = domainFrontier(session.frontier());
1448
+ if (!causalFrontiersEqual(causalFrontier, plan.targetCausalFrontier)) {
1449
+ throw new Error("Replay frontier mismatch");
1450
+ }
1451
+ return Object.freeze({
1452
+ headSequence: sequence,
1453
+ causalFrontier,
1454
+ stateHash: editableArtifactStateHash(session.stateHash())
1455
+ });
1456
+ }
1457
+ async function replaySerializedPlan(session, plan, signal) {
1458
+ if (session.modality !== plan.modality) throw new Error("Replay modality mismatch");
1459
+ let sequence = plan.baseHeadSequence;
1460
+ for await (const segment of plan.segments) {
1461
+ throwIfAborted(signal);
1462
+ if (segment.sequenceStart !== sequence + 1 || segment.modality !== session.modality) {
1463
+ throw new Error("Replay sequence or modality mismatch");
1464
+ }
1465
+ const commit = decodeEditableArtifactSerializedCommit(segment.bytes, session.modality);
1466
+ const intent = decodeEditableArtifactMutationIntent(commit.intentBytes);
1467
+ const receipt = session.applyCommands(intent.commandBytes);
1468
+ if (!equalBytes(receipt, commit.nativeReceiptBytes)) throw new Error("Replay receipt mismatch");
1469
+ sequence = segment.sequenceEnd;
1470
+ }
1471
+ if (sequence !== plan.targetHeadSequence || session.stateHash() !== plan.targetStateHash) {
1472
+ throw new Error("Replay target mismatch");
1473
+ }
1474
+ if (safeRevision(session.revision()) !== plan.targetNativeRevision) {
1475
+ throw new Error("Replay native revision mismatch");
1476
+ }
1477
+ return Object.freeze({
1478
+ headSequence: sequence,
1479
+ nativeRevision: plan.targetNativeRevision,
1480
+ stateHash: editableArtifactStateHash(session.stateHash())
1481
+ });
1482
+ }
1483
+ function domainFrontier(bytes) {
1484
+ return editableArtifactCausalFrontier(
1485
+ decodeEditableArtifactCausalFrontier(bytes).map((entry) => ({
1486
+ replicaId: editableArtifactReplicaId(entry.replicaId),
1487
+ counter: entry.counter
1488
+ }))
1489
+ );
1490
+ }
1491
+ async function readVerifiedSnapshot(objects, snapshot, signal) {
1492
+ const object = await objects.open({
1493
+ opaqueReference: snapshot.blobReference,
1494
+ maxBytes: MAX_EDITABLE_ARTIFACT_SNAPSHOT_BYTES,
1495
+ expectedByteSize: snapshot.byteSize,
1496
+ ...signal ? { signal } : {}
1497
+ });
1498
+ try {
1499
+ if (object.contentType !== void 0 && object.contentType !== snapshot.mimeType) {
1500
+ throw new Error("Snapshot content type mismatch");
1501
+ }
1502
+ const bytes = await collectExact(
1503
+ object.chunks(signal ? { signal } : {}),
1504
+ snapshot.byteSize,
1505
+ signal
1506
+ );
1507
+ const contentHash = `sha256:${createHash("sha256").update(bytes).digest("hex")}`;
1508
+ if (contentHash !== snapshot.contentHash) throw new Error("Snapshot content hash mismatch");
1509
+ await object.assertUnchanged(signal);
1510
+ return bytes;
1511
+ } finally {
1512
+ await object.close();
1513
+ }
1514
+ }
1515
+ async function collectExact(chunks, expectedBytes, signal) {
1516
+ if (!Number.isSafeInteger(expectedBytes) || expectedBytes <= 0) {
1517
+ throw new Error("Snapshot byte size is invalid");
1518
+ }
1519
+ const result = new Uint8Array(expectedBytes);
1520
+ let offset = 0;
1521
+ for await (const chunk of chunks) {
1522
+ throwIfAborted(signal);
1523
+ if (!(chunk instanceof Uint8Array) || chunk.byteLength === 0) {
1524
+ throw new Error("Snapshot stream is truncated");
1525
+ }
1526
+ if (offset + chunk.byteLength > result.byteLength) {
1527
+ throw new Error("Snapshot stream exceeds its declared size");
1528
+ }
1529
+ result.set(chunk, offset);
1530
+ offset += chunk.byteLength;
1531
+ }
1532
+ throwIfAborted(signal);
1533
+ if (offset !== result.byteLength) throw new Error("Snapshot stream is truncated");
1534
+ return result;
1535
+ }
1536
+ async function* fixedChunks(bytes, signal) {
1537
+ for (let offset = 0; offset < bytes.byteLength; offset += SNAPSHOT_CHUNK_BYTES) {
1538
+ throwIfAborted(signal);
1539
+ yield bytes.subarray(offset, Math.min(bytes.byteLength, offset + SNAPSHOT_CHUNK_BYTES));
1540
+ }
1541
+ }
1542
+ function namespaceForArtifact(artifactId) {
1543
+ const digest = createHash("sha256").update(artifactId).digest();
1544
+ let value = 0n;
1545
+ for (let index = 0; index < 8; index += 1) value = value << 8n | BigInt(digest[index]);
1546
+ return value === 0n ? 1n : value;
1547
+ }
1548
+ function safeRevision(value) {
1549
+ if (value < 0n || value > BigInt(Number.MAX_SAFE_INTEGER)) {
1550
+ throw new Error("Native revision exceeds the safe integer range");
1551
+ }
1552
+ return Number(value);
1553
+ }
1554
+ function equalBytes(left, right) {
1555
+ if (left.byteLength !== right.byteLength) return false;
1556
+ let difference = 0;
1557
+ for (let index = 0; index < left.byteLength; index += 1) {
1558
+ difference |= left[index] ^ right[index];
1559
+ }
1560
+ return difference === 0;
1561
+ }
1562
+ function throwIfAborted(signal) {
1563
+ if (signal?.aborted) throw new Error("Editable artifact native operation was cancelled");
1564
+ }
1565
+
1566
+ // src/editable-artifact-office-import.ts
1567
+ import { createHash as createHash2 } from "crypto";
1568
+ import {
1569
+ ArtifactOfficeSourceUnsupportedError,
1570
+ prepareArtifactOfficeImport
1571
+ } from "@opengeni/artifact-tool/office-import";
1572
+ import {
1573
+ MAX_EDITABLE_ARTIFACT_SNAPSHOT_BYTES as MAX_EDITABLE_ARTIFACT_SNAPSHOT_BYTES2,
1574
+ EDITABLE_ARTIFACT_ORIGINAL_IMPORT_MAX_BYTES,
1575
+ editableArtifactCausalFrontier as editableArtifactCausalFrontier2,
1576
+ editableArtifactContentHash as editableArtifactContentHash2,
1577
+ editableArtifactReplicaId as editableArtifactReplicaId2,
1578
+ editableArtifactStateHash as editableArtifactStateHash2,
1579
+ EditableArtifactOfficeImportError
1580
+ } from "@opengeni/core/editable-artifacts";
1581
+ import { getFile } from "@opengeni/db";
1582
+ import {
1583
+ MAX_BOUNDED_OBJECT_CHUNK_BYTES
1584
+ } from "@opengeni/storage";
1585
+ var SNAPSHOT_MIME = "application/vnd.opengeni.editable-artifact-snapshot";
1586
+ var EditableArtifactOfficeImportAdapter = class {
1587
+ constructor(dependencies) {
1588
+ this.dependencies = dependencies;
1589
+ this.readFile = dependencies.readFile ?? getFile;
1590
+ }
1591
+ readFile;
1592
+ async prepare(input) {
1593
+ throwIfAborted2(input.signal);
1594
+ const file = await this.readFile(this.dependencies.db, input.scope.workspaceId, input.fileId);
1595
+ if (!file || file.status !== "ready") {
1596
+ throw new EditableArtifactOfficeImportError("invalid_source");
1597
+ }
1598
+ const expectedMimeType = officeMimeType(input.modality);
1599
+ if (file.contentType !== expectedMimeType || !officeFilenameMatches(file.filename, input.modality) || !Number.isSafeInteger(file.sizeBytes) || file.sizeBytes < 1 || file.sizeBytes > EDITABLE_ARTIFACT_ORIGINAL_IMPORT_MAX_BYTES || typeof file.sha256 !== "string" || !/^[0-9a-f]{64}$/u.test(file.sha256)) {
1600
+ throw new EditableArtifactOfficeImportError("invalid_source");
1601
+ }
1602
+ const sourceBytes = await readVerifiedWorkspaceFile(
1603
+ this.dependencies.objectStorage,
1604
+ file,
1605
+ input.signal
1606
+ );
1607
+ const prepared = await prepareOfficeImport({
1608
+ runtime: this.dependencies.runtime,
1609
+ modality: input.modality,
1610
+ filename: file.filename,
1611
+ mimeType: expectedMimeType,
1612
+ bytes: sourceBytes,
1613
+ prepare: this.dependencies.prepareOffice ?? prepareArtifactOfficeImport
1614
+ });
1615
+ const retainedSource = await this.dependencies.sourceObjects.write({
1616
+ chunks: byteChunks(sourceBytes),
1617
+ contentType: expectedMimeType,
1618
+ maxBytes: EDITABLE_ARTIFACT_ORIGINAL_IMPORT_MAX_BYTES,
1619
+ expectedByteSize: prepared.source.byteSize,
1620
+ expectedContentHash: prepared.source.contentHash,
1621
+ ...input.signal ? { signal: input.signal } : {}
1622
+ });
1623
+ const retainedSnapshot = await this.dependencies.snapshotObjects.write({
1624
+ chunks: byteChunks(prepared.snapshot.bytes),
1625
+ contentType: SNAPSHOT_MIME,
1626
+ maxBytes: MAX_EDITABLE_ARTIFACT_SNAPSHOT_BYTES2,
1627
+ expectedByteSize: prepared.snapshot.byteSize,
1628
+ expectedContentHash: prepared.snapshot.contentHash,
1629
+ ...input.signal ? { signal: input.signal } : {}
1630
+ });
1631
+ const common = {
1632
+ modality: prepared.snapshot.modality,
1633
+ blobReference: retainedSnapshot.opaqueReference,
1634
+ byteSize: prepared.snapshot.byteSize,
1635
+ contentHash: editableArtifactContentHash2(prepared.snapshot.contentHash),
1636
+ mimeType: SNAPSHOT_MIME,
1637
+ coveredHeadSequence: 0,
1638
+ stateHash: editableArtifactStateHash2(prepared.snapshot.stateHash),
1639
+ modelSchemaVersion: prepared.snapshot.modelSchemaVersion,
1640
+ kernelVersion: prepared.snapshot.kernelVersion
1641
+ };
1642
+ return Object.freeze({
1643
+ originalImport: Object.freeze({
1644
+ fileId: file.id,
1645
+ blobReference: retainedSource.opaqueReference,
1646
+ byteSize: prepared.source.byteSize,
1647
+ contentHash: editableArtifactContentHash2(prepared.source.contentHash),
1648
+ mimeType: expectedMimeType
1649
+ }),
1650
+ snapshot: prepared.snapshot.modality === "spreadsheet" ? Object.freeze({
1651
+ ...common,
1652
+ modality: "spreadsheet",
1653
+ coveredCausalFrontier: editableArtifactCausalFrontier2(
1654
+ prepared.snapshot.coveredCausalFrontier.map((entry) => ({
1655
+ replicaId: editableArtifactReplicaId2(entry.replicaId),
1656
+ counter: entry.counter
1657
+ }))
1658
+ ),
1659
+ operationProtocolVersion: prepared.snapshot.operationProtocolVersion,
1660
+ crdtStateVersion: prepared.snapshot.crdtStateVersion
1661
+ }) : Object.freeze({
1662
+ ...common,
1663
+ modality: prepared.snapshot.modality,
1664
+ nativeRevision: prepared.snapshot.nativeRevision
1665
+ })
1666
+ });
1667
+ }
1668
+ };
1669
+ async function prepareOfficeImport(input) {
1670
+ try {
1671
+ return await input.prepare({
1672
+ facade: input.runtime.facade,
1673
+ modality: input.modality,
1674
+ filename: input.filename,
1675
+ mimeType: input.mimeType,
1676
+ bytes: input.bytes,
1677
+ expectedRuntimeTarget: input.runtime.location.target,
1678
+ expectedKernelVersion: input.runtime.runtime.buildIdentity
1679
+ });
1680
+ } catch (error) {
1681
+ if (error instanceof ArtifactOfficeSourceUnsupportedError) {
1682
+ throw new EditableArtifactOfficeImportError("unsupported_content");
1683
+ }
1684
+ throw error;
1685
+ }
1686
+ }
1687
+ function officeMimeType(modality) {
1688
+ if (modality === "spreadsheet") {
1689
+ return "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet";
1690
+ }
1691
+ if (modality === "document") {
1692
+ return "application/vnd.openxmlformats-officedocument.wordprocessingml.document";
1693
+ }
1694
+ return "application/vnd.openxmlformats-officedocument.presentationml.presentation";
1695
+ }
1696
+ function officeFilenameMatches(filename, modality) {
1697
+ const extension = modality === "spreadsheet" ? ".xlsx" : modality === "document" ? ".docx" : ".pptx";
1698
+ return filename.toLowerCase().endsWith(extension);
1699
+ }
1700
+ async function readVerifiedWorkspaceFile(storage, file, signal) {
1701
+ throwIfAborted2(signal);
1702
+ const before = await storage.headFile(file);
1703
+ if (before.ContentLength !== file.sizeBytes || before.ContentType !== file.contentType || before.Metadata?.sha256 !== file.sha256 || typeof before.VersionToken !== "string" || before.VersionToken.length < 1) {
1704
+ throw new EditableArtifactOfficeImportError("source_changed");
1705
+ }
1706
+ const bytes = new Uint8Array(file.sizeBytes);
1707
+ const digest = createHash2("sha256");
1708
+ for (let start = 0; start < file.sizeBytes; start += MAX_BOUNDED_OBJECT_CHUNK_BYTES) {
1709
+ throwIfAborted2(signal);
1710
+ const end = Math.min(file.sizeBytes, start + MAX_BOUNDED_OBJECT_CHUNK_BYTES) - 1;
1711
+ const chunk = await storage.getFileRange(file, { start, end });
1712
+ if (!chunk || chunk.byteLength !== end - start + 1) {
1713
+ throw new EditableArtifactOfficeImportError("source_changed");
1714
+ }
1715
+ bytes.set(chunk, start);
1716
+ digest.update(chunk);
1717
+ }
1718
+ const after = await storage.headFile(file);
1719
+ const sha256 = digest.digest("hex");
1720
+ if (after.ContentLength !== before.ContentLength || after.ContentType !== before.ContentType || after.Metadata?.sha256 !== before.Metadata?.sha256 || after.VersionToken !== before.VersionToken || sha256 !== file.sha256) {
1721
+ throw new EditableArtifactOfficeImportError("source_changed");
1722
+ }
1723
+ return bytes;
1724
+ }
1725
+ async function* byteChunks(bytes) {
1726
+ for (let offset = 0; offset < bytes.byteLength; offset += MAX_BOUNDED_OBJECT_CHUNK_BYTES) {
1727
+ yield bytes.subarray(offset, offset + MAX_BOUNDED_OBJECT_CHUNK_BYTES);
1728
+ }
1729
+ }
1730
+ function throwIfAborted2(signal) {
1731
+ if (signal?.aborted) throw signal.reason ?? new Error("Artifact import aborted");
1732
+ }
1733
+
1734
+ // src/editable-artifact-workspace-files.ts
1735
+ import { createHash as createHash3 } from "crypto";
1736
+ import { completeFileUpload, prepareGeneratedWorkspaceFile } from "@opengeni/db";
1737
+ var UPLOAD_INTENT_TTL_MS = 60 * 6e4;
1738
+ var UUID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/u;
1739
+ var EditableArtifactWorkspaceFileAdapter = class {
1740
+ constructor(dependencies) {
1741
+ this.dependencies = dependencies;
1742
+ this.now = dependencies.now ?? (() => /* @__PURE__ */ new Date());
1743
+ this.prepareFile = dependencies.prepareFile ?? prepareGeneratedWorkspaceFile;
1744
+ this.completeFile = dependencies.completeFile ?? completeFileUpload;
1745
+ }
1746
+ now;
1747
+ prepareFile;
1748
+ completeFile;
1749
+ async ensureMaterializationFile(input) {
1750
+ throwIfAborted3(input.signal);
1751
+ const storage = this.dependencies.objectStorage;
1752
+ if (!storage.headObject || !storage.putObjectStreamIfAbsent) {
1753
+ throw new Error("Object storage lacks immutable streaming upload support");
1754
+ }
1755
+ const download = await this.dependencies.durableExports.openMaterializationDownload({
1756
+ scope: input.scope,
1757
+ actor: input.actor,
1758
+ artifactId: input.artifact.id,
1759
+ jobId: input.jobId,
1760
+ ...input.signal ? { signal: input.signal } : {}
1761
+ });
1762
+ try {
1763
+ const sha256 = plainSha256(download.contentHash);
1764
+ const filename = exportFilename(input.filename, download.format);
1765
+ const fileId = deterministicUuid(
1766
+ `editable-artifact-export:file:${input.scope.workspaceId}:${input.artifact.id}:${input.versionId}:${input.jobId}`
1767
+ );
1768
+ const uploadId = deterministicUuid(
1769
+ `editable-artifact-export:upload:${input.scope.workspaceId}:${input.artifact.id}:${input.versionId}:${input.jobId}`
1770
+ );
1771
+ const safeFilename = `artifact-${input.artifact.id}-${input.jobId}.${download.format}`;
1772
+ const objectKey = `workspaces/${input.scope.workspaceId}/files/${fileId}/artifact-exports/${safeFilename}`;
1773
+ const prepared = await this.prepareFile(this.dependencies.db, {
1774
+ accountId: input.scope.accountId,
1775
+ workspaceId: input.scope.workspaceId,
1776
+ fileId,
1777
+ uploadId,
1778
+ filename,
1779
+ safeFilename,
1780
+ contentType: download.mimeType,
1781
+ sizeBytes: download.byteSize,
1782
+ sha256,
1783
+ bucket: storage.bucket,
1784
+ objectKey,
1785
+ expiresAt: new Date(this.now().getTime() + UPLOAD_INTENT_TTL_MS)
1786
+ });
1787
+ let file = prepared.file;
1788
+ if (file.status !== "ready") {
1789
+ const existing = await storage.headObject(objectKey);
1790
+ if (existing) {
1791
+ assertStoredExport(existing, download.byteSize, download.mimeType, sha256);
1792
+ } else {
1793
+ await storage.putObjectStreamIfAbsent({
1794
+ key: objectKey,
1795
+ contentType: download.mimeType,
1796
+ chunks: download.chunks(input.signal ? { signal: input.signal } : {}),
1797
+ byteSize: download.byteSize,
1798
+ sha256,
1799
+ ...input.signal ? { signal: input.signal } : {}
1800
+ });
1801
+ const stored = await storage.headObject(objectKey);
1802
+ assertStoredExport(stored, download.byteSize, download.mimeType, sha256);
1803
+ }
1804
+ await download.assertUnchanged(input.signal);
1805
+ throwIfAborted3(input.signal);
1806
+ file = await this.completeFile(this.dependencies.db, input.scope.workspaceId, uploadId);
1807
+ } else {
1808
+ assertStoredExport(
1809
+ await storage.headObject(objectKey),
1810
+ download.byteSize,
1811
+ download.mimeType,
1812
+ sha256
1813
+ );
1814
+ await download.assertUnchanged(input.signal);
1815
+ }
1816
+ if (file.id !== fileId || file.status !== "ready" || file.filename !== filename || file.contentType !== download.mimeType || file.sizeBytes !== download.byteSize || file.sha256 !== sha256) {
1817
+ throw new Error("Generated workspace file differs from its immutable export");
1818
+ }
1819
+ return Object.freeze({
1820
+ fileId: file.id,
1821
+ filename: file.filename,
1822
+ contentType: file.contentType,
1823
+ sizeBytes: file.sizeBytes,
1824
+ sha256,
1825
+ artifactId: input.artifact.id,
1826
+ versionId: input.versionId,
1827
+ materializationJobId: input.jobId,
1828
+ sourceHeadSequence: input.sourceHeadSequence,
1829
+ sourceStateHash: input.sourceStateHash
1830
+ });
1831
+ } finally {
1832
+ await download.close();
1833
+ }
1834
+ }
1835
+ };
1836
+ function assertStoredExport(head, byteSize, contentType, sha256) {
1837
+ if (!head || head.ContentLength !== byteSize || head.ContentType !== contentType || head.Metadata?.sha256 !== sha256 || typeof head.VersionToken !== "string" || head.VersionToken.length < 1) {
1838
+ throw new Error("Workspace export object differs from its durable materialization");
1839
+ }
1840
+ }
1841
+ function exportFilename(value, format) {
1842
+ const expectedExtension = `.${format}`;
1843
+ const normalized = value.normalize("NFKC").replace(/[\\/:*?"<>|\u0000-\u001f\u007f]+/gu, "-").replace(/\s+/gu, " ").trim().slice(0, 200);
1844
+ const stem = normalized.toLowerCase().endsWith(expectedExtension) ? normalized.slice(0, -expectedExtension.length) : normalized;
1845
+ return `${stem.replace(/[. ]+$/u, "").slice(0, 190) || "artifact"}${expectedExtension}`;
1846
+ }
1847
+ function plainSha256(value) {
1848
+ const match = /^sha256:([0-9a-f]{64})$/u.exec(value);
1849
+ if (!match) throw new Error("Materialized artifact content hash is invalid");
1850
+ return match[1];
1851
+ }
1852
+ function deterministicUuid(seed) {
1853
+ const bytes = createHash3("sha256").update(seed, "utf8").digest().subarray(0, 16);
1854
+ bytes[6] = (bytes[6] ?? 0) & 15 | 80;
1855
+ bytes[8] = (bytes[8] ?? 0) & 63 | 128;
1856
+ const hex = bytes.toString("hex");
1857
+ const value = `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;
1858
+ if (!UUID_PATTERN.test(value)) throw new Error("Generated workspace file identity is invalid");
1859
+ return value;
1860
+ }
1861
+ function throwIfAborted3(signal) {
1862
+ if (signal?.aborted) throw signal.reason ?? new Error("Artifact export aborted");
1863
+ }
1864
+
1865
+ // src/editable-artifact-production.ts
1866
+ var INVALIDATION_POLL_MS = 1e3;
1867
+ var TICKET_CLEANUP_MS = 3e4;
1868
+ async function createStandaloneEditableArtifactApplication(input) {
1869
+ const boundedObjects = createObjectStorageBoundedPorts(input.objectStorage);
1870
+ const materializationObjects = createObjectStorageBoundedPorts(input.objectStorage, {
1871
+ keyPrefix: "editable-artifacts/materializations/v1/sha256/"
1872
+ });
1873
+ const importSourceObjects = createObjectStorageBoundedPorts(input.objectStorage, {
1874
+ keyPrefix: "editable-artifacts/imports/v1/sha256/"
1875
+ });
1876
+ const runtimeBinding = await loadVerifiedNativeArtifactRuntimeBinding();
1877
+ const runtime = runtimeBinding.runtime;
1878
+ const kernel = new NativeEditableArtifactKernelAdapter(runtime, boundedObjects.read);
1879
+ const persistedStore = new PostgresEditableArtifactStore(input.db);
1880
+ const store = editableArtifactStorePortFromPostgres(persistedStore);
1881
+ const authorization = new PostgresEditableArtifactAuthorization(input.db);
1882
+ const ids = new RandomEditableArtifactStableIds();
1883
+ const clock = new SystemEditableArtifactClock();
1884
+ const snapshotVerifier = new ProductionEditableArtifactSnapshotVerifier({
1885
+ objects: boundedObjects.read,
1886
+ kernel
1887
+ });
1888
+ const genesis = new EditableArtifactGenesisPipeline({
1889
+ kernel,
1890
+ objects: boundedObjects.write,
1891
+ now: () => clock.now()
1892
+ });
1893
+ const compaction = new EditableArtifactCompactionPipeline({
1894
+ kernel,
1895
+ objects: boundedObjects.write,
1896
+ now: () => clock.now()
1897
+ });
1898
+ const domain = new EditableArtifactService({
1899
+ authorization,
1900
+ kernel,
1901
+ store,
1902
+ intentCodec: ogatxEditableArtifactMutationIntentCodec,
1903
+ clock,
1904
+ ids,
1905
+ snapshotVerifier,
1906
+ genesis,
1907
+ compaction
1908
+ });
1909
+ const materializerCapabilities = editableArtifactMaterializerCapabilitiesForRuntime(
1910
+ runtime,
1911
+ runtimeBinding.location.artifactTool.packageVersion
1912
+ );
1913
+ const durableExports = new EditableArtifactDurableExportService({
1914
+ authorization,
1915
+ exactSnapshots: Object.freeze({
1916
+ ensure: async (request) => await domain.compactCurrentHead(request)
1917
+ }),
1918
+ store: editableArtifactDurableExportStorePortFromPostgres(
1919
+ new PostgresEditableArtifactDurableExportStore(input.db)
1920
+ ),
1921
+ ids: new RandomEditableArtifactDurableExportIds(),
1922
+ profiles: materializationProfiles(materializerCapabilities),
1923
+ materializationObjects: materializationObjects.read
1924
+ });
1925
+ const officeImports = new EditableArtifactOfficeImportAdapter({
1926
+ db: input.db,
1927
+ objectStorage: input.objectStorage,
1928
+ runtime: runtimeBinding,
1929
+ sourceObjects: importSourceObjects.write,
1930
+ snapshotObjects: boundedObjects.write
1931
+ });
1932
+ const agent = new EditableArtifactAgentApplication({
1933
+ domain,
1934
+ exports: durableExports,
1935
+ associations: Object.freeze({
1936
+ listArtifactIds: async ({ scope, sessionId, limit }) => (await listEditableArtifactIdsForSession(input.db, scope, sessionId, limit)).map(
1937
+ editableArtifactId
1938
+ ),
1939
+ touch: async ({ scope, sessionId, artifactId }) => await touchEditableArtifactSessionLink(input.db, scope, sessionId, artifactId)
1940
+ }),
1941
+ inspector: kernel,
1942
+ officeImports,
1943
+ workspaceFiles: new EditableArtifactWorkspaceFileAdapter({
1944
+ db: input.db,
1945
+ objectStorage: input.objectStorage,
1946
+ durableExports
1947
+ })
1948
+ });
1949
+ const ticketStore = new PostgresEditableArtifactLiveTicketStore(input.db);
1950
+ const liveRead = new PostgresEditableArtifactLiveReadStore(input.db, {
1951
+ snapshotBytes: snapshotBytesPort(boundedObjects.read)
1952
+ });
1953
+ const invalidations = new PollingEditableArtifactAuthorizationInvalidations(authorization);
1954
+ const live = new EditableArtifactLiveServer({
1955
+ authorization,
1956
+ domain,
1957
+ tickets: editableArtifactLiveTicketStorePortFromPostgres(ticketStore),
1958
+ tokens: new WebCryptoEditableArtifactLiveTokens(),
1959
+ clock: new SystemEditableArtifactLiveClock(),
1960
+ scheduler: new SystemEditableArtifactLiveScheduler(),
1961
+ read: editableArtifactLiveReadPortFromPostgres(liveRead),
1962
+ hints: new EventBusEditableArtifactLiveHints(input.bus),
1963
+ invalidations
1964
+ });
1965
+ const compatibility = Object.freeze({
1966
+ assertCompatible(request) {
1967
+ if (request.protocolVersion !== EDITABLE_ARTIFACT_LIVE_PROTOCOL_VERSION || request.modelSchemaVersion !== 1 || request.kernelVersion !== runtime.buildIdentity) {
1968
+ throw new EditableArtifactCompatibilityError();
1969
+ }
1970
+ }
1971
+ });
1972
+ const application = new EditableArtifactApplication({
1973
+ domain,
1974
+ live,
1975
+ compatibility
1976
+ });
1977
+ const cleanup = setInterval(() => {
1978
+ void ticketStore.cleanupExpired().catch(() => void 0);
1979
+ }, TICKET_CLEANUP_MS);
1980
+ cleanup.unref?.();
1981
+ return Object.freeze({
1982
+ application,
1983
+ durableExports,
1984
+ agent,
1985
+ officeImports,
1986
+ kernelVersion: runtime.buildIdentity,
1987
+ close() {
1988
+ clearInterval(cleanup);
1989
+ invalidations.close();
1990
+ }
1991
+ });
1992
+ }
1993
+ var PostgresEditableArtifactAuthorization = class {
1994
+ constructor(db) {
1995
+ this.db = db;
1996
+ }
1997
+ async authorize(request) {
1998
+ const actor = request.actor;
1999
+ const rows = await withRlsContext(
2000
+ this.db,
2001
+ request.scope,
2002
+ async (tx) => rawRows(
2003
+ await tx.execute(dbSql`select *
2004
+ from opengeni_private.authorize_editable_artifact_actor(
2005
+ ${request.scope.accountId}::uuid,
2006
+ ${request.scope.workspaceId}::uuid,
2007
+ ${request.artifactId},
2008
+ ${actor.kind},
2009
+ ${actor.subjectId},
2010
+ ${actor.kind === "agent" ? actor.sessionId : null},
2011
+ ${actor.kind === "agent" ? actor.turnId : null},
2012
+ ${actor.kind === "agent" ? actor.attemptId : null},
2013
+ ${actor.kind === "agent" ? actor.generation : null},
2014
+ ${actor.kind === "service" ? actor.service : null},
2015
+ ${request.permission},
2016
+ current_schema()
2017
+ )`)
2018
+ )
2019
+ );
2020
+ if (rows.length !== 1) throw new Error("Editable artifact authorization returned no decision");
2021
+ const revision = Number(rows[0].authorization_revision);
2022
+ if (!Number.isSafeInteger(revision) || revision < 1) {
2023
+ throw new Error("Editable artifact authorization returned an invalid revision");
2024
+ }
2025
+ return Object.freeze({ allowed: rows[0].allowed === true, revision });
2026
+ }
2027
+ };
2028
+ var PollingEditableArtifactAuthorizationInvalidations = class {
2029
+ constructor(authorization) {
2030
+ this.authorization = authorization;
2031
+ }
2032
+ stops = /* @__PURE__ */ new Set();
2033
+ async subscribe(input) {
2034
+ let active = true;
2035
+ let polling = false;
2036
+ let baseline = await this.read(input);
2037
+ const timer = setInterval(() => {
2038
+ if (!active || polling) return;
2039
+ polling = true;
2040
+ void this.read(input).then((next) => {
2041
+ if (!active) return;
2042
+ if (next !== baseline) {
2043
+ baseline = next;
2044
+ input.onInvalidated();
2045
+ }
2046
+ }).catch(() => {
2047
+ if (active) input.onInvalidated();
2048
+ }).finally(() => {
2049
+ polling = false;
2050
+ });
2051
+ }, INVALIDATION_POLL_MS);
2052
+ timer.unref?.();
2053
+ const stop = () => {
2054
+ if (!active) return;
2055
+ active = false;
2056
+ clearInterval(timer);
2057
+ this.stops.delete(stop);
2058
+ };
2059
+ this.stops.add(stop);
2060
+ return stop;
2061
+ }
2062
+ close() {
2063
+ for (const stop of [...this.stops]) stop();
2064
+ }
2065
+ async read(input) {
2066
+ const decision = await this.authorization.authorize({
2067
+ scope: input.scope,
2068
+ artifactId: input.artifactId,
2069
+ actor: input.actor,
2070
+ permission: "read"
2071
+ });
2072
+ return `${decision.allowed ? 1 : 0}:${decision.revision}`;
2073
+ }
2074
+ };
2075
+ var RandomEditableArtifactStableIds = class {
2076
+ next(_kind) {
2077
+ return randomUUID().replaceAll("-", "");
2078
+ }
2079
+ };
2080
+ var RandomEditableArtifactDurableExportIds = class {
2081
+ next(_kind) {
2082
+ return randomUUID().replaceAll("-", "");
2083
+ }
2084
+ };
2085
+ function materializationProfiles(capabilities) {
2086
+ if (capabilities.protocol !== "OGAMC001" || capabilities.runtimeKind !== "native" || capabilities.maxOutputBytes !== EDITABLE_ARTIFACT_EXPORT_MAX_DOWNLOAD_BYTES || capabilities.codecVersions["opengeni.xlsx"] === void 0 || !capabilities.supportedModelSchemaVersions.includes(1) || !capabilities.supportedOperationProtocolVersions.includes(1) || !capabilities.supportedSnapshotProtocolVersions.includes(1)) {
2087
+ throw new Error("Verified artifact materializer profile is incompatible");
2088
+ }
2089
+ const codecVersion = capabilities.codecVersions["opengeni.xlsx"];
2090
+ return Object.freeze({
2091
+ async resolve(input) {
2092
+ if (input.modality !== "spreadsheet" || input.format !== "xlsx" || Object.keys(input.options).length !== 0) {
2093
+ return null;
2094
+ }
2095
+ return Object.freeze({
2096
+ modality: "spreadsheet",
2097
+ format: "xlsx",
2098
+ codecId: "opengeni.xlsx",
2099
+ codecVersion,
2100
+ kernelVersion: capabilities.kernelVersion,
2101
+ fontRegistryHash: capabilities.fontRegistryHash,
2102
+ policyHash: capabilities.policyHash,
2103
+ normalizedOptions: "{}"
2104
+ });
2105
+ }
2106
+ });
2107
+ }
2108
+ function snapshotBytesPort(objects) {
2109
+ return Object.freeze({
2110
+ async readSnapshotBytes(snapshot) {
2111
+ const object = await objects.open({
2112
+ opaqueReference: snapshot.blobReference,
2113
+ maxBytes: MAX_EDITABLE_ARTIFACT_SNAPSHOT_BYTES3,
2114
+ expectedByteSize: snapshot.byteSize
2115
+ });
2116
+ try {
2117
+ if (object.contentType !== void 0 && object.contentType !== snapshot.mimeType) {
2118
+ throw new Error("Editable artifact snapshot content type mismatch");
2119
+ }
2120
+ const bytes = new Uint8Array(snapshot.byteSize);
2121
+ const digest = createHash4("sha256");
2122
+ let offset = 0;
2123
+ for await (const chunk of object.chunks({
2124
+ chunkBytes: MAX_BOUNDED_OBJECT_CHUNK_BYTES2
2125
+ })) {
2126
+ if (offset + chunk.byteLength > bytes.byteLength) {
2127
+ throw new Error("Editable artifact snapshot exceeds its declared size");
2128
+ }
2129
+ bytes.set(chunk, offset);
2130
+ digest.update(chunk);
2131
+ offset += chunk.byteLength;
2132
+ }
2133
+ if (offset !== bytes.byteLength || `sha256:${digest.digest("hex")}` !== snapshot.contentHash) {
2134
+ throw new Error("Editable artifact snapshot digest or size mismatch");
2135
+ }
2136
+ await object.assertUnchanged();
2137
+ return bytes;
2138
+ } finally {
2139
+ await object.close();
2140
+ }
2141
+ }
2142
+ });
2143
+ }
2144
+ function rawRows(result) {
2145
+ if (Array.isArray(result)) return result;
2146
+ const rows = result?.rows;
2147
+ if (Array.isArray(rows)) return rows;
2148
+ throw new Error("Unsupported database execute result shape");
2149
+ }
2150
+
456
2151
  // src/index.ts
457
2152
  function isWorkflowAlreadyStarted(error) {
458
2153
  return error instanceof WorkflowExecutionAlreadyStartedError;
@@ -557,6 +2252,14 @@ async function createTemporalWorkflowClient(settings, db) {
557
2252
  },
558
2253
  syncScheduledTask: async ({ task }) => {
559
2254
  const schedule = temporal.schedule.getHandle(task.temporalScheduleId);
2255
+ if (task.schedule.type === "manual") {
2256
+ try {
2257
+ await schedule.delete();
2258
+ } catch (error) {
2259
+ if (!(error instanceof ScheduleNotFoundError)) throw error;
2260
+ }
2261
+ return;
2262
+ }
560
2263
  const options = temporalScheduleOptions(task, settings.temporalTaskQueue);
561
2264
  try {
562
2265
  await schedule.update(() => temporalScheduleUpdateOptions(options));
@@ -568,13 +2271,21 @@ async function createTemporalWorkflowClient(settings, db) {
568
2271
  }
569
2272
  },
570
2273
  deleteScheduledTaskSchedule: async ({ temporalScheduleId }) => {
571
- await temporal.schedule.getHandle(temporalScheduleId).delete().catch(() => void 0);
2274
+ try {
2275
+ await temporal.withDeadline(Date.now() + 5e3, async () => {
2276
+ await temporal.schedule.getHandle(temporalScheduleId).delete();
2277
+ });
2278
+ } catch (error) {
2279
+ if (error instanceof ScheduleNotFoundError) return;
2280
+ throw error;
2281
+ }
572
2282
  },
573
2283
  triggerScheduledTask: async ({
574
2284
  task,
575
2285
  agentRunUsageIdempotencyKey,
576
2286
  triggerWorkflowId,
577
- initiator
2287
+ initiator,
2288
+ triggerType = "manual"
578
2289
  }) => {
579
2290
  const workflowId = triggerWorkflowId;
580
2291
  try {
@@ -587,7 +2298,7 @@ async function createTemporalWorkflowClient(settings, db) {
587
2298
  accountId: task.accountId,
588
2299
  workspaceId: task.workspaceId,
589
2300
  taskId: task.id,
590
- triggerType: "manual",
2301
+ triggerType,
591
2302
  agentRunUsageIdempotencyKey,
592
2303
  initiator
593
2304
  }
@@ -605,18 +2316,23 @@ async function createTemporalWorkflowClient(settings, db) {
605
2316
  if (!targetId) {
606
2317
  throw new Error("rig verification requires changeId or versionId");
607
2318
  }
608
- await temporal.workflow.start("rigVerificationWorkflow", {
609
- taskQueue: settings.temporalTaskQueue,
610
- workflowId: workflowId ?? `rig-verification-${targetId}-${crypto.randomUUID()}`,
611
- workflowIdReusePolicy: "ALLOW_DUPLICATE",
612
- args: [
613
- {
614
- workspaceId,
615
- ...changeId ? { changeId } : {},
616
- ...versionId ? { versionId } : {}
617
- }
618
- ]
619
- });
2319
+ try {
2320
+ await temporal.workflow.start("rigVerificationWorkflow", {
2321
+ taskQueue: settings.temporalTaskQueue,
2322
+ workflowId: workflowId ?? `rig-verification-${targetId}-${crypto.randomUUID()}`,
2323
+ workflowIdReusePolicy: "REJECT_DUPLICATE",
2324
+ args: [
2325
+ {
2326
+ workspaceId,
2327
+ ...changeId ? { changeId } : {},
2328
+ ...versionId ? { versionId } : {}
2329
+ }
2330
+ ]
2331
+ });
2332
+ } catch (error) {
2333
+ if (isWorkflowAlreadyStarted(error)) return;
2334
+ throw error;
2335
+ }
620
2336
  },
621
2337
  check: async () => {
622
2338
  await connection.workflowService.getSystemInfo({});
@@ -693,24 +2409,73 @@ async function startApi() {
693
2409
  await dbClient.close();
694
2410
  throw new Error("OpenGeni API startup dependencies were not initialized");
695
2411
  }
2412
+ const objectStorage = createObjectStorage(settings);
2413
+ let editableArtifactComposition;
2414
+ if (objectStorage && isArtifactRuntimeConfigured()) {
2415
+ try {
2416
+ editableArtifactComposition = await createStandaloneEditableArtifactApplication({
2417
+ db: dbClient.db,
2418
+ bus,
2419
+ objectStorage
2420
+ });
2421
+ } catch (error) {
2422
+ await Promise.allSettled([bus.close(), workflowClient.close(), dbClient.close()]);
2423
+ throw error;
2424
+ }
2425
+ }
696
2426
  const { app, routeDeps } = createAppComposition({
697
2427
  settings,
698
2428
  db: dbClient.db,
699
2429
  bus,
700
2430
  workflowClient: workflowClient.client,
701
2431
  documentIndexer: workflowClient.documentIndexer,
2432
+ objectStorage,
2433
+ ...editableArtifactComposition ? {
2434
+ editableArtifacts: editableArtifactComposition.application,
2435
+ editableArtifactExports: editableArtifactComposition.durableExports,
2436
+ editableArtifactAgent: editableArtifactComposition.agent,
2437
+ editableArtifactOfficeImports: editableArtifactComposition.officeImports
2438
+ } : {},
702
2439
  observability,
703
2440
  readinessChecks: {
704
2441
  db: runtimeDatabaseReadyCheck(dbClient.db, databasePosture)
705
2442
  }
706
2443
  });
2444
+ if (!routeDeps.editableArtifacts) {
2445
+ observability.warn("editable artifact engine is not composed", {
2446
+ subsystem: "editable_artifacts",
2447
+ behavior: "http_and_websocket_fail_closed"
2448
+ });
2449
+ }
2450
+ const artifactWebSockets = new EditableArtifactWebSocketTransport(routeDeps.editableArtifacts);
707
2451
  const server = Bun.serve({
708
2452
  hostname: settings.apiHost,
709
2453
  port: settings.apiPort,
710
2454
  idleTimeout: 255,
711
- fetch: app.fetch
2455
+ fetch: (request, bunServer) => {
2456
+ if (artifactWebSockets.handles(request)) {
2457
+ return artifactWebSockets.upgrade(request, bunServer);
2458
+ }
2459
+ return app.fetch(request);
2460
+ },
2461
+ websocket: {
2462
+ maxPayloadLength: EDITABLE_ARTIFACT_LIVE_WEBSOCKET_MAX_MESSAGE_BYTES,
2463
+ backpressureLimit: 16 * 1024 * 1024,
2464
+ closeOnBackpressureLimit: true,
2465
+ open: (socket) => artifactWebSockets.websocket.open(socket),
2466
+ message: (socket, message) => artifactWebSockets.websocket.message(socket, message),
2467
+ close: (socket) => artifactWebSockets.websocket.close(socket)
2468
+ }
712
2469
  });
713
2470
  const stopSlackInteractionPump = settings.slackSigningSecret ? startSlackInteractionPump(routeDeps) : void 0;
2471
+ const stopMemorySlackPublicationPump = startMemorySlackPublicationPump(routeDeps);
2472
+ const stopTemporalScheduleCleanupPump = startTemporalScheduleCleanupPump({
2473
+ db: dbClient.db,
2474
+ deleteSchedule: async (temporalScheduleId) => {
2475
+ await workflowClient.client.deleteScheduledTaskSchedule({ temporalScheduleId });
2476
+ },
2477
+ observability
2478
+ });
714
2479
  let stopMetricsIngestion;
715
2480
  let stopHelloIngestion;
716
2481
  let authCalloutResponder;
@@ -733,9 +2498,11 @@ async function startApi() {
733
2498
  { db: dbClient.db, settings, callout, observability },
734
2499
  settings.natsUrl
735
2500
  );
736
- } catch (error) {
2501
+ } catch {
737
2502
  observability.error("OpenGeni NATS auth-callout responder failed to start", {
738
- error: error instanceof Error ? error.message : String(error)
2503
+ errorClass: "NatsAuthCalloutOperationError",
2504
+ errorCode: "nats_auth_callout_start_failed",
2505
+ origin: "api"
739
2506
  });
740
2507
  }
741
2508
  } else {
@@ -754,9 +2521,12 @@ async function startApi() {
754
2521
  close: async () => {
755
2522
  server.stop(true);
756
2523
  stopSlackInteractionPump?.();
2524
+ await stopMemorySlackPublicationPump();
757
2525
  stopMetricsIngestion?.();
758
2526
  stopHelloIngestion?.();
2527
+ await stopTemporalScheduleCleanupPump();
759
2528
  await Promise.allSettled([
2529
+ Promise.resolve(editableArtifactComposition?.close()),
760
2530
  authCalloutResponder?.close(),
761
2531
  bus.close(),
762
2532
  workflowClient.close(),
@@ -781,9 +2551,12 @@ function shouldCreateScheduleAfterUpdateError(error) {
781
2551
  return error instanceof ScheduleNotFoundError;
782
2552
  }
783
2553
  function temporalScheduleSpec(schedule) {
2554
+ if (schedule.type === "manual") {
2555
+ throw new Error("manual scheduled tasks do not have a Temporal Schedule spec");
2556
+ }
784
2557
  if (schedule.type === "interval") {
785
2558
  return {
786
- intervals: [{ every: `${schedule.everySeconds}s` }],
2559
+ intervals: [temporalIntervalSpec(schedule)],
787
2560
  ...schedule.startAt ? { startAt: new Date(schedule.startAt) } : {},
788
2561
  ...schedule.endAt ? { endAt: new Date(schedule.endAt) } : {}
789
2562
  };
@@ -816,6 +2589,19 @@ function temporalScheduleSpec(schedule) {
816
2589
  timezone: "UTC"
817
2590
  };
818
2591
  }
2592
+ function temporalIntervalSpec(schedule) {
2593
+ const every = `${schedule.everySeconds}s`;
2594
+ if (!schedule.startAt) {
2595
+ return { every };
2596
+ }
2597
+ const everyMilliseconds = BigInt(schedule.everySeconds) * 1000n;
2598
+ const startMilliseconds = BigInt(new Date(schedule.startAt).getTime());
2599
+ const offsetMilliseconds = (startMilliseconds % everyMilliseconds + everyMilliseconds) % everyMilliseconds;
2600
+ return {
2601
+ every,
2602
+ ...offsetMilliseconds === 0n ? {} : { offset: `${offsetMilliseconds}ms` }
2603
+ };
2604
+ }
819
2605
  function temporalMonth(monthIndex) {
820
2606
  return TEMPORAL_MONTHS[monthIndex];
821
2607
  }
@@ -842,7 +2628,7 @@ function temporalScheduleOptions(task, taskQueue) {
842
2628
  pauseOnFailure: false
843
2629
  },
844
2630
  state: {
845
- paused: task.status === "paused",
2631
+ paused: scheduledTaskEffectivelyPaused(task),
846
2632
  ...task.schedule.type === "once" ? { remainingActions: 1 } : {}
847
2633
  },
848
2634
  memo: {
@@ -853,6 +2639,14 @@ function temporalScheduleOptions(task, taskQueue) {
853
2639
  }
854
2640
  };
855
2641
  }
2642
+ function scheduledTaskEffectivelyPaused(task) {
2643
+ if (task.status === "paused") return true;
2644
+ if (task.action.kind !== "knowledge_source_sync") return false;
2645
+ const control = task.metadata.knowledgeSourceSync;
2646
+ if (!control || typeof control !== "object") return false;
2647
+ const record = control;
2648
+ return record.sourceEnabled === false || record.connectionPaused === true;
2649
+ }
856
2650
  function temporalScheduleUpdateOptions(options) {
857
2651
  return {
858
2652
  spec: options.spec,