@opengeni/core 0.20.16 → 0.21.10

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.
package/dist/index.js CHANGED
@@ -3,7 +3,28 @@ var SESSION_WORKFLOW_WAKE_DISPATCHER_SCHEDULE_ID = "opengeni-session-workflow-wa
3
3
  var SESSION_WORKFLOW_WAKE_DISPATCHER_WORKFLOW_TYPE = "sessionWorkflowWakeDispatcherWorkflow";
4
4
  var SESSION_WORKFLOW_WAKE_DISPATCHER_PERIOD_MS = 1e4;
5
5
 
6
+ // src/managed-session.ts
7
+ async function getManagedSession(c, auth) {
8
+ const result = await auth.api.getSession({
9
+ headers: c.req.raw.headers,
10
+ returnHeaders: true
11
+ });
12
+ for (const cookie of setCookieHeaders(result.headers)) {
13
+ c.header("set-cookie", cookie, { append: true });
14
+ }
15
+ return result.response;
16
+ }
17
+ function setCookieHeaders(headers) {
18
+ const getSetCookie = headers.getSetCookie;
19
+ if (getSetCookie) {
20
+ return getSetCookie.call(headers);
21
+ }
22
+ const cookie = headers.get("set-cookie");
23
+ return cookie ? [cookie] : [];
24
+ }
25
+
6
26
  // src/transcription.ts
27
+ var TRANSCRIPTION_PROVIDER_REQUEST_TIMEOUT_MILLISECONDS = 10 * 60 * 1e3;
7
28
  var TranscriptionServiceError = class extends Error {
8
29
  code;
9
30
  status;
@@ -78,7 +99,7 @@ function filenameForMimeType(mimeType) {
78
99
 
79
100
  // src/sandbox/fleet.ts
80
101
  import {
81
- getEnrollment,
102
+ getEnrollment as getEnrollment2,
82
103
  getSandbox as getSandbox2,
83
104
  listSandboxes,
84
105
  readActiveSandbox as readActiveSandbox2,
@@ -88,6 +109,7 @@ import {
88
109
  } from "@opengeni/db";
89
110
  import {
90
111
  NatsControlRpc as NatsControlRpc2,
112
+ NatsOpStreamTransport as NatsOpStreamTransport2,
91
113
  selfhostedLiveness,
92
114
  SelfhostedSession,
93
115
  swapTargetEstablishability
@@ -99,6 +121,7 @@ import { sandboxArchiveCaptureTimeoutMs } from "@opengeni/config";
99
121
  import {
100
122
  advanceWorkspaceGenerationForDirectRequest,
101
123
  advanceWorkspaceGenerationForRetainedProcess,
124
+ getEnrollment,
102
125
  getRetainedProcess,
103
126
  getSandbox,
104
127
  markWarmLeaseInstanceLost,
@@ -114,6 +137,7 @@ import {
114
137
  isProviderSandboxGoneDuringRoutedOperation,
115
138
  makeActiveBackendResolver,
116
139
  NatsControlRpc,
140
+ NatsOpStreamTransport,
117
141
  RoutingSandboxSession,
118
142
  resolveModalCheckpointProviderBindingForSession
119
143
  } from "@opengeni/runtime/sandbox";
@@ -153,6 +177,18 @@ function controlRpcFactory(bus) {
153
177
  return bus.getRequestConnection();
154
178
  });
155
179
  }
180
+ async function resolveSelfhostedOpStream(services, workspaceId, sandbox) {
181
+ if (services.settings.agentOpStreamEnabled !== true || !services.bus?.getOpStreamConnection || !sandbox.enrollmentId) {
182
+ return void 0;
183
+ }
184
+ const enrollment = await getEnrollment(services.db, workspaceId, sandbox.enrollmentId);
185
+ if (enrollment?.opStream !== true) return void 0;
186
+ return {
187
+ transport: new NatsOpStreamTransport(
188
+ async () => services.bus?.getOpStreamConnection?.() ?? null
189
+ )
190
+ };
191
+ }
156
192
  function routingEnabled(settings) {
157
193
  return settings.sandboxSelfhostedEnabled === true;
158
194
  }
@@ -316,6 +352,7 @@ function wrapChannelABoxWithRouting(services, ids, established) {
316
352
  } : null;
317
353
  },
318
354
  controlRpcFactory: controlRpcFactory(bus),
355
+ resolveSelfhostedOpStream: (sandbox) => resolveSelfhostedOpStream(services, ids.workspaceId, sandbox),
319
356
  relay: relayConfigFromSettings(settings),
320
357
  selfhostedTimeoutMs: settings.sandboxSelfhostedControlTimeoutMs,
321
358
  selfhostedExecTimeoutMs: settings.sandboxSelfhostedExecTimeoutMs,
@@ -462,6 +499,10 @@ async function listFleet(services, ctx) {
462
499
  const groupRecovering = Boolean(
463
500
  groupLease && (groupLease.liveness === "warming" || groupLease.recovery.restore.status === "pending" || groupLease.recovery.restore.status === "restoring" || groupLease.recovery.restore.status === "verifying")
464
501
  );
502
+ const groupRecoveryUnavailable = Boolean(
503
+ groupLease && (groupLease.recovery.restore.status === "degraded" || groupLease.recovery.restore.status === "unrecoverable" || groupLease.recovery.workspace.status === "degraded" || groupLease.recovery.workspace.status === "unrecoverable")
504
+ );
505
+ const groupOperationAvailability = groupOnline ? "ready" : groupRecoveryUnavailable ? "unavailable" : groupRecovering ? "recovering" : ctx.sessionBackend === "selfhosted" ? "unavailable" : "wakeable";
465
506
  entries.push({
466
507
  id: ctx.sessionGroupId,
467
508
  kind: ctx.sessionBackend === "selfhosted" ? "selfhosted" : "modal",
@@ -471,6 +512,7 @@ async function listFleet(services, ctx) {
471
512
  isSessionGroup: true,
472
513
  enrollmentId: null,
473
514
  attachable: groupOnline,
515
+ operationAvailability: groupOperationAvailability,
474
516
  providerStatus: groupLease?.recovery.provider.status ?? "not_created",
475
517
  leaseLiveness: groupLease?.liveness ?? null,
476
518
  routeStatus: groupActive ? "attached" : "detached",
@@ -488,7 +530,10 @@ async function listFleet(services, ctx) {
488
530
  if (sandbox.kind !== "selfhosted" || !sandbox.enrollmentId) {
489
531
  continue;
490
532
  }
491
- const enrollment = await getEnrollment(db, ctx.workspaceId, sandbox.enrollmentId);
533
+ const enrollment = await getEnrollment2(db, ctx.workspaceId, sandbox.enrollmentId);
534
+ if (!enrollment || enrollment.status !== "active") {
535
+ continue;
536
+ }
492
537
  const probe = enrollment ? await probeEnrollment(services, ctx.workspaceId, enrollment) : { liveness: "offline", consented: false, hasDisplay: false };
493
538
  entries.push({
494
539
  id: sandbox.id,
@@ -499,6 +544,7 @@ async function listFleet(services, ctx) {
499
544
  isSessionGroup: false,
500
545
  enrollmentId: sandbox.enrollmentId,
501
546
  attachable: probe.liveness === "online",
547
+ operationAvailability: probe.liveness === "online" ? "ready" : probe.liveness === "reconnecting" ? "recovering" : "unavailable",
502
548
  consented: probe.consented,
503
549
  hasDisplay: probe.hasDisplay,
504
550
  lastSeenAt: enrollment?.lastSeenAt ?? null,
@@ -548,7 +594,7 @@ async function resolveTarget(services, ctx, target) {
548
594
  code: "offline_enrollment"
549
595
  };
550
596
  }
551
- const enrollment = await getEnrollment(services.db, ctx.workspaceId, sandbox.enrollmentId);
597
+ const enrollment = await getEnrollment2(services.db, ctx.workspaceId, sandbox.enrollmentId);
552
598
  if (!enrollment) {
553
599
  return {
554
600
  ok: false,
@@ -647,7 +693,8 @@ async function executeRunOnSelfhostedMachine(machine, target, op) {
647
693
  controlRpc: machine.controlRpc,
648
694
  relay: machine.relay,
649
695
  timeoutMs: machine.controlTimeoutMs,
650
- execTimeoutMs: machine.execTimeoutMs
696
+ execTimeoutMs: machine.execTimeoutMs,
697
+ ...machine.opStream !== void 0 ? { opStream: machine.opStream } : {}
651
698
  });
652
699
  try {
653
700
  if (op.kind === "exec") {
@@ -690,6 +737,8 @@ async function executeRunOnSelfhostedMachine(machine, target, op) {
690
737
  // so leave `timedOut` absent while still reporting the enforced deadline.
691
738
  ...op.kind === "exec" ? { deadlineMs: session.effectiveExecDeadlineMs } : {}
692
739
  };
740
+ } finally {
741
+ await session.finalizeOpStreamOps().catch(() => void 0);
693
742
  }
694
743
  }
695
744
  async function runOnSandbox(services, ctx, target, op) {
@@ -711,7 +760,7 @@ async function runOnSandbox(services, ctx, target, op) {
711
760
  reason: `run_on routes one-off ops to enrolled selfhosted machines; ${sandbox.kind} targets are reached via the active sandbox (swap to it first)`
712
761
  };
713
762
  }
714
- const enrollment = await getEnrollment(services.db, ctx.workspaceId, sandbox.enrollmentId);
763
+ const enrollment = await getEnrollment2(services.db, ctx.workspaceId, sandbox.enrollmentId);
715
764
  if (!enrollment || enrollment.status !== "active") {
716
765
  return {
717
766
  target,
@@ -728,7 +777,14 @@ async function runOnSandbox(services, ctx, target, op) {
728
777
  controlRpc: controlRpc(services.bus),
729
778
  relay: relayConfigFromSettings(services.settings),
730
779
  controlTimeoutMs: services.settings.sandboxSelfhostedControlTimeoutMs,
731
- execTimeoutMs: services.settings.sandboxSelfhostedExecTimeoutMs
780
+ execTimeoutMs: services.settings.sandboxSelfhostedExecTimeoutMs,
781
+ ...services.settings.agentOpStreamEnabled === true && enrollment.opStream === true && services.bus?.getOpStreamConnection ? {
782
+ opStream: {
783
+ transport: new NatsOpStreamTransport2(
784
+ async () => services.bus?.getOpStreamConnection?.() ?? null
785
+ )
786
+ }
787
+ } : {}
732
788
  },
733
789
  target,
734
790
  op
@@ -740,7 +796,7 @@ async function provisionSandbox(services, ctx, input) {
740
796
  const base = (services.settings.publicBaseUrl ?? "https://get.opengeni.ai").replace(/\/+$/, "");
741
797
  return {
742
798
  kind: "selfhosted",
743
- instructions: "Share these instructions with a human operator. They install the OpenGeni agent on the machine, run `opengeni-agent enroll`, complete the device-flow at the verification URL (the loud whole-machine + screen-control consent), and the machine then appears here as an attachable selfhosted sandbox.",
799
+ instructions: "Share these instructions with a human operator. They install the OpenGeni agent on the machine, run `opengeni-agent connect`, complete the device-flow at the verification URL (the loud whole-machine + screen-control consent), and the machine then appears here as an attachable selfhosted sandbox. Existing connections to other OpenGeni workspaces or deployments are preserved.",
744
800
  // Install from THIS control plane's origin (not a hardcoded public CDN): the
745
801
  // served install script is rewritten to pull the per-SHA agent baked into
746
802
  // this exact deployment (see apps/api/src/routes/install.ts), so a deployed
@@ -852,10 +908,43 @@ function requirePermission(grant, permission) {
852
908
  });
853
909
  }
854
910
  }
911
+ function requireLiteralPermission(grant, permission) {
912
+ if (!hasLiteralPermission(grant.permissions, permission)) {
913
+ throw new HTTPException2(403, {
914
+ message: `missing literal permission: ${permission}`
915
+ });
916
+ }
917
+ }
918
+ function hasLiteralPermission(permissions, permission) {
919
+ return permissions.includes(permission);
920
+ }
855
921
  function hasPermission(permissions, permission) {
922
+ if (permission === "secrets:read") {
923
+ return permissions.includes("secrets:read");
924
+ }
856
925
  const aliases = {
857
926
  "variable-sets:use": ["environments:use"],
858
- "variable-sets:manage": ["environments:manage"]
927
+ "variable-sets:manage": ["environments:manage"],
928
+ "variable-sets:list": [
929
+ "variable-sets:use",
930
+ "variable-sets:manage",
931
+ "environments:use",
932
+ "environments:manage"
933
+ ],
934
+ "variable-sets:read": [
935
+ "variable-sets:use",
936
+ "variable-sets:manage",
937
+ "environments:use",
938
+ "environments:manage"
939
+ ],
940
+ "variable-sets:write": ["variable-sets:manage", "environments:manage"],
941
+ "secrets:list": [
942
+ "variable-sets:use",
943
+ "variable-sets:manage",
944
+ "environments:use",
945
+ "environments:manage"
946
+ ],
947
+ "secrets:write": ["variable-sets:manage", "environments:manage"]
859
948
  };
860
949
  return permissions.includes(permission) || (aliases[permission]?.some((alias) => permissions.includes(alias)) ?? false) || permissions.includes("workspace:admin");
861
950
  }
@@ -911,9 +1000,7 @@ async function resolveAccessContext(c, deps) {
911
1000
  }
912
1001
  }
913
1002
  if (deps.managedAuth) {
914
- const session = await deps.managedAuth.api.getSession({
915
- headers: c.req.raw.headers
916
- });
1003
+ const session = await getManagedSession(c, deps.managedAuth);
917
1004
  if (session?.user) {
918
1005
  return await ensureManagedAccessForUser(deps.db, {
919
1006
  userId: session.user.id,
@@ -1053,6 +1140,13 @@ var SessionAuthorizationUnavailableError = class extends Error {
1053
1140
  this.name = "SessionAuthorizationUnavailableError";
1054
1141
  }
1055
1142
  };
1143
+ async function requireLiveAgentAttemptAuthorization(db, grant, callerSessionId) {
1144
+ const actor = await resolveSessionAuthorizationActor(db, grant);
1145
+ if (actor.kind !== "agent_attempt" || actor.callerSessionId !== callerSessionId) {
1146
+ throw new SessionAuthorizationDeniedError("caller_stale");
1147
+ }
1148
+ return actor;
1149
+ }
1056
1150
  async function requireSessionAuthorization(deps, grant, input) {
1057
1151
  const port = deps.sessionAuthorization;
1058
1152
  const slackAccess = await getSlackInteractionSessionAccessForSession(deps.db, {
@@ -1748,7 +1842,8 @@ async function buildCapabilityCatalog(input) {
1748
1842
  workspacePacks,
1749
1843
  socialConnections,
1750
1844
  bundledSkills,
1751
- curatedLibrarySkills
1845
+ curatedLibrarySkills,
1846
+ codexAppsCredentialId
1752
1847
  ] = await Promise.all([
1753
1848
  listCapabilityCatalogItems(input.db, input.workspaceId),
1754
1849
  listCapabilityInstallations(input.db, input.workspaceId),
@@ -1756,7 +1851,8 @@ async function buildCapabilityCatalog(input) {
1756
1851
  listWorkspaceCapabilityPacks(input.db, input.workspaceId),
1757
1852
  listSocialConnections(input.db, input.workspaceId, 500, input.subjectId),
1758
1853
  discoverBundledSkills(),
1759
- discoverCuratedSkillLibraryItems()
1854
+ discoverCuratedSkillLibraryItems(),
1855
+ input.settings.codexConnectedAppsEnabled ? resolveCodexAppsCredentialIdForRun(input.db, input.workspaceId) : Promise.resolve(null)
1760
1856
  ]);
1761
1857
  const capabilityInstallationById = new Map(
1762
1858
  capabilityInstallations.map((installation) => [installation.capabilityId, installation])
@@ -1774,7 +1870,14 @@ async function buildCapabilityCatalog(input) {
1774
1870
  ...bundledSkills,
1775
1871
  ...curatedLibrarySkills
1776
1872
  ];
1777
- const items = dedupeCatalogItems([...builtIns, ...persistedItems]).map(
1873
+ const codexApps = input.settings.codexConnectedAppsEnabled ? codexAppsCatalogItem(codexAppsCredentialId !== null) : null;
1874
+ const items = dedupeCatalogItems([
1875
+ ...builtIns,
1876
+ ...persistedItems.filter((item) => !isReservedCodexAppsCatalogItem(item)),
1877
+ // Keep the reserved, server-derived item authoritative over any stale
1878
+ // legacy catalog row with the same id.
1879
+ ...codexApps ? [codexApps] : []
1880
+ ]).map(
1778
1881
  (item) => applyCapabilityEnablement(item, capabilityInstallationById.get(item.id), activePackIds)
1779
1882
  ).sort(compareCatalogItems);
1780
1883
  return {
@@ -1794,7 +1897,7 @@ async function createCatalogItem(input) {
1794
1897
  message: "skill ids are managed by the OpenGeni skill library or runtime adapters"
1795
1898
  });
1796
1899
  }
1797
- if (input.payload.kind === "mcp" && typeof input.payload.metadata.mcpServerId === "string" && input.payload.metadata.mcpServerId.trim() === CODEX_APPS_MCP_SERVER_ID) {
1900
+ if (input.payload.kind === "mcp" && (id === `mcp:${CODEX_APPS_MCP_SERVER_ID}` || typeof input.payload.metadata.mcpServerId === "string" && input.payload.metadata.mcpServerId.trim() === CODEX_APPS_MCP_SERVER_ID)) {
1798
1901
  throw new HTTPException6(422, {
1799
1902
  message: `${CODEX_APPS_MCP_SERVER_ID} is reserved for the canonical Codex Apps service`
1800
1903
  });
@@ -2434,7 +2537,7 @@ function packCatalogItem(pack, source) {
2434
2537
  });
2435
2538
  }
2436
2539
  function configuredMcpCatalogItems(settings) {
2437
- return settings.mcpServers.map(
2540
+ return settings.mcpServers.filter((server) => server.id !== CODEX_APPS_MCP_SERVER_ID).map(
2438
2541
  (server) => CapabilityCatalogItem.parse({
2439
2542
  id: `mcp:${server.id}`,
2440
2543
  kind: "mcp",
@@ -2459,6 +2562,38 @@ function configuredMcpCatalogItems(settings) {
2459
2562
  })
2460
2563
  );
2461
2564
  }
2565
+ function isReservedCodexAppsCatalogItem(item) {
2566
+ return item.id === `mcp:${CODEX_APPS_MCP_SERVER_ID}` || item.kind === "mcp" && (item.runtime.mcpServerId === CODEX_APPS_MCP_SERVER_ID || item.metadata.mcpServerId === CODEX_APPS_MCP_SERVER_ID);
2567
+ }
2568
+ function codexAppsCatalogItem(available) {
2569
+ return CapabilityCatalogItem.parse({
2570
+ id: `mcp:${CODEX_APPS_MCP_SERVER_ID}`,
2571
+ kind: "mcp",
2572
+ source: "built_in",
2573
+ name: "Codex Apps",
2574
+ description: "Use the ChatGPT Apps designated for this workspace. Sessions include this surface by default when it is authorized; explicit policies can opt out.",
2575
+ category: "productivity",
2576
+ tags: ["mcp", "codex", "connected-apps"],
2577
+ providerDomain: "chatgpt.com",
2578
+ surfaceType: "codex_apps",
2579
+ transport: "streamable-http",
2580
+ mcpUrl: CODEX_APPS_MCP_URL,
2581
+ authKind: "none",
2582
+ tools: [{ kind: "mcp", id: CODEX_APPS_MCP_SERVER_ID }],
2583
+ runtime: {
2584
+ available,
2585
+ ...available ? { mcpServerId: CODEX_APPS_MCP_SERVER_ID } : {},
2586
+ transport: "streamable-http",
2587
+ notes: available ? "Available through the active workspace Apps designation." : "Unavailable until an active Codex Apps credential is designated for this workspace."
2588
+ },
2589
+ enabled: available,
2590
+ enabledReason: available ? "designated Apps credential" : "no active Apps designation",
2591
+ metadata: {
2592
+ mcpServerId: CODEX_APPS_MCP_SERVER_ID,
2593
+ authorization: "workspace_designation"
2594
+ }
2595
+ });
2596
+ }
2462
2597
  function platformApiCatalogItems(socialConnections) {
2463
2598
  const xConnection = preferredSocialConnection(socialConnections, "x");
2464
2599
  const xEnabled = xConnection?.status === "connected" || xConnection?.status === "needs_reauth";
@@ -2655,6 +2790,9 @@ function applyCapabilityEnablement(item, installation, activePackIds) {
2655
2790
  if (item.surfaceType === "first_party_social") {
2656
2791
  return { ...item, connectionRef: null };
2657
2792
  }
2793
+ if (item.surfaceType === "codex_apps") {
2794
+ return { ...item, connectionRef: null };
2795
+ }
2658
2796
  if (item.source === "built_in" || item.source === "configured") {
2659
2797
  return {
2660
2798
  ...item,
@@ -3486,6 +3624,7 @@ import {
3486
3624
  mergeResourceRefs as mergeContractResourceRefs,
3487
3625
  mergeToolRefs,
3488
3626
  normalizeRepositorySubpath,
3627
+ normalizeRepositoryTransportUri,
3489
3628
  normalizeResourceMountPath,
3490
3629
  resourceIdentityKey,
3491
3630
  resourceMountPath,
@@ -3557,21 +3696,18 @@ function normalizeResources(resources) {
3557
3696
  mountPath
3558
3697
  };
3559
3698
  } else {
3560
- const url = parseResourceUrl(resource.uri);
3561
- if (url.protocol !== "https:" || !url.hostname) {
3562
- throw new HTTPException8(422, { message: "repository resources must use HTTPS Git URLs" });
3563
- }
3564
- const path = url.pathname.replace(/^\/+|\/+$/g, "").replace(/\.git$/, "");
3565
- const parts = path.split("/").filter(Boolean);
3566
- if (parts.length < 2) {
3567
- throw new HTTPException8(422, { message: "repository URL must include owner and repo" });
3699
+ const credentialProvider = gitCredentialProviderForRepository(resource);
3700
+ let normalizedUri;
3701
+ try {
3702
+ normalizedUri = normalizeRepositoryTransportUri(resource.uri);
3703
+ } catch (error) {
3704
+ throw new HTTPException8(422, {
3705
+ message: error instanceof Error ? error.message : "invalid repository URI"
3706
+ });
3568
3707
  }
3569
- const repo = parts.join("/");
3570
- const normalizedUri = `https://${url.host.toLowerCase()}/${repo}.git`;
3571
3708
  const mountPath = normalizeMountPath(
3572
- resource.mountPath ?? defaultRepositoryMountPath(normalizedUri)
3709
+ resource.mountPath ?? defaultRepositoryMountPath(normalizedUri, credentialProvider)
3573
3710
  );
3574
- const credentialProvider = gitCredentialProviderForRepository(resource);
3575
3711
  const credentialBindingId = gitCredentialBindingIdForRepository(resource, credentialProvider);
3576
3712
  if ((resource.credentialBindingId || resource.connectionId || resource.access) && !credentialProvider) {
3577
3713
  throw new HTTPException8(422, {
@@ -3731,13 +3867,6 @@ function normalizeMountPath(path) {
3731
3867
  throw new HTTPException8(422, { message: `invalid resource mount path: ${path}` });
3732
3868
  }
3733
3869
  }
3734
- function parseResourceUrl(uri) {
3735
- try {
3736
- return new URL(uri);
3737
- } catch {
3738
- throw new HTTPException8(422, { message: "repository resources must use valid URLs" });
3739
- }
3740
- }
3741
3870
  function positiveInteger(value) {
3742
3871
  if (typeof value === "number" && Number.isInteger(value) && value > 0) {
3743
3872
  return value;
@@ -3881,6 +4010,7 @@ import {
3881
4010
  getRig as getRig3,
3882
4011
  getScheduledTask,
3883
4012
  getScheduledTaskPersonalConnectionDelegations,
4013
+ getSession as getSession3,
3884
4014
  requireWorkspace as requireWorkspace2,
3885
4015
  updateScheduledTask
3886
4016
  } from "@opengeni/db";
@@ -3915,7 +4045,7 @@ import {
3915
4045
  createSessionWithIdempotencyKeyResult,
3916
4046
  encryptVariableSetValue as encryptVariableSetValue2,
3917
4047
  getAnySessionInGroup,
3918
- getEnrollment as getEnrollment2,
4048
+ getEnrollment as getEnrollment3,
3919
4049
  getRig as getRig2,
3920
4050
  getWorkspaceDefaultRigId,
3921
4051
  listDistinctVariableSetIdsInGroup,
@@ -4304,7 +4434,7 @@ function validateSessionMcpCredentialUpdates(input) {
4304
4434
  });
4305
4435
  return encryptedUpdates;
4306
4436
  }
4307
- async function createAndStartSession(input) {
4437
+ async function createAndStartSessionWithOutcome(input) {
4308
4438
  const sessionMetadata = {
4309
4439
  ...input.metadata,
4310
4440
  model: input.model,
@@ -4350,12 +4480,24 @@ async function createAndStartSession(input) {
4350
4480
  }
4351
4481
  const { session: keyed, created } = keyedResult;
4352
4482
  if (!created) {
4353
- return await finishStartSession(
4483
+ const finished3 = await finishStartSession(
4354
4484
  keyed.temporalWorkflowId ? { ...input, seedTargetSandbox: null } : input,
4355
4485
  keyed
4356
4486
  );
4487
+ return {
4488
+ session: finished3.session,
4489
+ outcome: finished3.changed ? "repaired" : "replayed",
4490
+ replay: !finished3.changed,
4491
+ changed: finished3.changed
4492
+ };
4357
4493
  }
4358
- return await finishStartSession(input, keyed);
4494
+ const finished2 = await finishStartSession(input, keyed);
4495
+ return {
4496
+ session: finished2.session,
4497
+ outcome: "created",
4498
+ replay: false,
4499
+ changed: true
4500
+ };
4359
4501
  }
4360
4502
  let session;
4361
4503
  try {
@@ -4397,7 +4539,16 @@ async function createAndStartSession(input) {
4397
4539
  }
4398
4540
  throw error;
4399
4541
  }
4400
- return await finishStartSession(input, session);
4542
+ const finished = await finishStartSession(input, session);
4543
+ return {
4544
+ session: finished.session,
4545
+ outcome: "created",
4546
+ replay: false,
4547
+ changed: true
4548
+ };
4549
+ }
4550
+ async function createAndStartSession(input) {
4551
+ return (await createAndStartSessionWithOutcome(input)).session;
4401
4552
  }
4402
4553
  async function finishStartSession(input, session) {
4403
4554
  if (input.seedTargetSandbox) {
@@ -4459,7 +4610,10 @@ async function finishStartSession(input, session) {
4459
4610
  }
4460
4611
  const persisted = await requireSession2(input.db, session.workspaceId, session.id);
4461
4612
  const initialTurnId = started.turn?.id ?? (await listSessionTurns(input.db, session.workspaceId, session.id, 1))[0]?.id ?? null;
4462
- return { ...persisted, initialTurnId };
4613
+ return {
4614
+ session: { ...persisted, initialTurnId },
4615
+ changed: started.changed
4616
+ };
4463
4617
  }
4464
4618
  function workflowIdForSession(sessionId) {
4465
4619
  return `session-${sessionId}`;
@@ -4644,15 +4798,16 @@ async function postUserMessageTurn(input) {
4644
4798
  wakeRevision: result.wakeRevision,
4645
4799
  ...(input.delivery ?? "send") === "steer" || result.interruptionCount > 0 ? { interruptionRequested: true } : {}
4646
4800
  });
4647
- } catch (error) {
4648
- console.warn(
4649
- `[sessions] workflow wake failed for committed prompt ${workspaceId}/${sessionId}; durable outbox will retry`,
4650
- error
4651
- );
4801
+ } catch {
4802
+ console.warn("[sessions] workflow wake failed; durable outbox will retry", {
4803
+ errorClass: "WorkflowWakeOperationError",
4804
+ errorCode: "session_workflow_wake_failed",
4805
+ origin: "core"
4806
+ });
4652
4807
  }
4653
- return { accepted, turn };
4808
+ return { accepted, turn, replay: result.replay };
4654
4809
  }
4655
- async function createSessionForRequest(deps, grant, workspaceId, rawPayload) {
4810
+ async function createSessionForRequestWithOutcome(deps, grant, workspaceId, rawPayload) {
4656
4811
  const { settings, db, bus, workflowClient, objectStorage } = deps;
4657
4812
  const payload = CreateSessionRequest.parse(rawPayload);
4658
4813
  if (hasReservedOpenGeniSlackBotSessionMetadata(payload.metadata)) {
@@ -4923,7 +5078,7 @@ async function createSessionForRequest(deps, grant, workspaceId, rawPayload) {
4923
5078
  if (targetSandbox?.kind === "selfhosted") {
4924
5079
  machineHomeBackend = "selfhosted";
4925
5080
  if (targetSandbox.enrollmentId) {
4926
- const enrollment = await getEnrollment2(db, workspaceId, targetSandbox.enrollmentId);
5081
+ const enrollment = await getEnrollment3(db, workspaceId, targetSandbox.enrollmentId);
4927
5082
  if (enrollment && (enrollment.os === "macos" || enrollment.os === "windows" || enrollment.os === "linux")) {
4928
5083
  machineHomeOs = enrollment.os;
4929
5084
  }
@@ -4940,9 +5095,9 @@ async function createSessionForRequest(deps, grant, workspaceId, rawPayload) {
4940
5095
  });
4941
5096
  }
4942
5097
  const creationInitiator = creationInitiatorForGrant(grant);
4943
- let session;
5098
+ let createOutcome;
4944
5099
  try {
4945
- session = await createAndStartSession({
5100
+ createOutcome = await createAndStartSessionWithOutcome({
4946
5101
  ...payload.requestedSessionId ? { requestedSessionId: payload.requestedSessionId } : {},
4947
5102
  db,
4948
5103
  bus,
@@ -5017,26 +5172,45 @@ async function createSessionForRequest(deps, grant, workspaceId, rawPayload) {
5017
5172
  }
5018
5173
  throw error;
5019
5174
  }
5175
+ let usageRecording = "recorded";
5020
5176
  if (payload.startMode !== "realtime") {
5021
- await recordWorkspaceUsage(deps, {
5022
- accountId: grant.accountId,
5023
- workspaceId,
5024
- subjectId: grant.subjectId,
5025
- eventType: "agent_run.created",
5026
- quantity: 1,
5027
- unit: "run",
5028
- sourceResourceType: "session",
5029
- sourceResourceId: session.id,
5030
- sessionId: session.id,
5031
- initiator: session.createdBy,
5032
- initiatorContext: session.createdByContext,
5033
- origin: creationInitiator.actor ? "system" : "user",
5034
- idempotencyKey: `agent_run.created:${workspaceId}:${session.id}`
5035
- });
5177
+ try {
5178
+ await recordWorkspaceUsage(deps, {
5179
+ accountId: grant.accountId,
5180
+ workspaceId,
5181
+ subjectId: grant.subjectId,
5182
+ eventType: "agent_run.created",
5183
+ quantity: 1,
5184
+ unit: "run",
5185
+ sourceResourceType: "session",
5186
+ sourceResourceId: createOutcome.session.id,
5187
+ sessionId: createOutcome.session.id,
5188
+ initiator: createOutcome.session.createdBy,
5189
+ initiatorContext: createOutcome.session.createdByContext,
5190
+ origin: creationInitiator.actor ? "system" : "user",
5191
+ idempotencyKey: `agent_run.created:${workspaceId}:${createOutcome.session.id}`
5192
+ });
5193
+ } catch (error) {
5194
+ usageRecording = "failed";
5195
+ reportSessionUsageRecordingFailure(error);
5196
+ }
5036
5197
  }
5037
- return session;
5198
+ return { ...createOutcome, usageRecording };
5038
5199
  }
5039
- async function acceptSessionUserMessage(deps, grant, workspaceId, sessionId, input) {
5200
+ function reportSessionUsageRecordingFailure(_error) {
5201
+ console.warn(
5202
+ "[sessions] usage recording failed after committed session create; returning committed outcome",
5203
+ {
5204
+ errorClass: "UsageRecordingError",
5205
+ errorCode: "session_create_usage_recording_failed",
5206
+ origin: "core"
5207
+ }
5208
+ );
5209
+ }
5210
+ async function createSessionForRequest(deps, grant, workspaceId, rawPayload) {
5211
+ return (await createSessionForRequestWithOutcome(deps, grant, workspaceId, rawPayload)).session;
5212
+ }
5213
+ async function acceptSessionUserMessageWithOutcome(deps, grant, workspaceId, sessionId, input) {
5040
5214
  const { settings, db, bus, workflowClient, objectStorage } = deps;
5041
5215
  await requireSessionAuthorization(deps, grant, {
5042
5216
  sessionId,
@@ -5104,7 +5278,7 @@ async function acceptSessionUserMessage(deps, grant, workspaceId, sessionId, inp
5104
5278
  source: personalConnectionDelegationSourceForGrant(grant)
5105
5279
  });
5106
5280
  const delegatedServiceInitiator = serviceInitiatorForGrant(grant);
5107
- const { accepted, turn } = await postUserMessageTurn({
5281
+ const { accepted, turn, replay } = await postUserMessageTurn({
5108
5282
  db,
5109
5283
  bus,
5110
5284
  workflowClient,
@@ -5154,6 +5328,16 @@ async function acceptSessionUserMessage(deps, grant, workspaceId, sessionId, inp
5154
5328
  origin: turn.source,
5155
5329
  idempotencyKey: `agent_run.created:${workspaceId}:${turn.id}`
5156
5330
  });
5331
+ return { accepted, turn, replay };
5332
+ }
5333
+ async function acceptSessionUserMessage(deps, grant, workspaceId, sessionId, input) {
5334
+ const { accepted, turn } = await acceptSessionUserMessageWithOutcome(
5335
+ deps,
5336
+ grant,
5337
+ workspaceId,
5338
+ sessionId,
5339
+ input
5340
+ );
5157
5341
  return { accepted, turn };
5158
5342
  }
5159
5343
  async function updateSessionTitle(deps, grant, sessionId, title, source) {
@@ -5451,6 +5635,17 @@ async function createValidatedScheduledTask(input) {
5451
5635
  });
5452
5636
  const id = crypto.randomUUID();
5453
5637
  validateScheduledTaskSchedule(input.payload.schedule);
5638
+ const target = await validateScheduledTaskTarget({
5639
+ db: input.db,
5640
+ sessionAuthorization: input.sessionAuthorization,
5641
+ authorizationSurface: input.authorizationSurface,
5642
+ grant: input.grant,
5643
+ targetSessionId: input.payload.targetSessionId,
5644
+ runMode: input.payload.runMode,
5645
+ variableSetId: input.payload.variableSetId,
5646
+ rigId: input.payload.rigId,
5647
+ agentConfig
5648
+ });
5454
5649
  if (input.payload.variableSetId) {
5455
5650
  await validateVariableSetAttachment(
5456
5651
  { settings: input.settings, db: input.db },
@@ -5491,11 +5686,97 @@ async function createValidatedScheduledTask(input) {
5491
5686
  ...creationInitiator.context ? { createdByContext: creationInitiator.context } : {},
5492
5687
  createdByActor: creationInitiator.actor ?? null,
5493
5688
  personalConnectionDelegations,
5689
+ targetSessionId: target?.id ?? null,
5494
5690
  variableSetId: input.payload.variableSetId ?? null,
5495
5691
  rigId: input.payload.rigId ?? null,
5496
5692
  metadata: input.payload.metadata
5497
5693
  });
5498
5694
  }
5695
+ async function validateScheduledTaskTarget(input) {
5696
+ if (input.runMode !== "existing_session") {
5697
+ if (input.targetSessionId) {
5698
+ throw new HTTPException11(422, {
5699
+ message: "targetSessionId requires runMode=existing_session"
5700
+ });
5701
+ }
5702
+ return null;
5703
+ }
5704
+ if (!input.targetSessionId) {
5705
+ throw new HTTPException11(input.missingTargetStatus ?? 422, {
5706
+ message: input.missingTargetStatus === 404 ? "target session not found" : "targetSessionId is required when runMode=existing_session"
5707
+ });
5708
+ }
5709
+ requirePermission(input.grant, "sessions:control");
5710
+ if (input.agentConfig.goal) {
5711
+ throw new HTTPException11(422, {
5712
+ message: "agentConfig.goal cannot be used with an existing-session target"
5713
+ });
5714
+ }
5715
+ try {
5716
+ await requireSessionAuthorization(
5717
+ {
5718
+ db: input.db,
5719
+ ...input.sessionAuthorization !== void 0 ? { sessionAuthorization: input.sessionAuthorization } : {}
5720
+ },
5721
+ input.grant,
5722
+ {
5723
+ sessionId: input.targetSessionId,
5724
+ operation: "session.control",
5725
+ surface: input.authorizationSurface ?? "http"
5726
+ }
5727
+ );
5728
+ } catch (error) {
5729
+ if (error instanceof SessionAuthorizationDeniedError) {
5730
+ throw new HTTPException11(404, { message: "target session not found" });
5731
+ }
5732
+ if (error instanceof SessionAuthorizationUnavailableError) {
5733
+ throw new HTTPException11(503, { message: "session authorization is unavailable" });
5734
+ }
5735
+ throw error;
5736
+ }
5737
+ const session = await getSession3(input.db, input.grant.workspaceId, input.targetSessionId);
5738
+ if (!session || session.accountId !== input.grant.accountId) {
5739
+ throw new HTTPException11(404, { message: "target session not found" });
5740
+ }
5741
+ if (session.status === "cancelled") {
5742
+ throw new HTTPException11(409, {
5743
+ message: "target session is cancelled; choose a revivable session"
5744
+ });
5745
+ }
5746
+ if ((session.variableSetId ?? null) !== (input.variableSetId ?? null)) {
5747
+ throw new HTTPException11(422, {
5748
+ message: "target session variableSet attachment does not match the scheduled task"
5749
+ });
5750
+ }
5751
+ if (input.rigId && input.rigId !== session.rigId) {
5752
+ throw new HTTPException11(422, {
5753
+ message: "target session rig does not match the scheduled task"
5754
+ });
5755
+ }
5756
+ if (input.agentConfig.sandboxBackend !== void 0 && input.agentConfig.sandboxBackend !== session.sandboxBackend) {
5757
+ throw new HTTPException11(422, {
5758
+ message: "target session sandbox backend does not match the scheduled task"
5759
+ });
5760
+ }
5761
+ if (scheduledSlackBotConnectionId(session.metadata) !== (input.agentConfig.slackBotConnectionId ?? null)) {
5762
+ throw new HTTPException11(422, {
5763
+ message: "target session OpenGeni Slack bot binding does not match the scheduled task"
5764
+ });
5765
+ }
5766
+ return session;
5767
+ }
5768
+ function scheduledTaskForGrant(task, grant) {
5769
+ if (hasPermission(grant.permissions, "sessions:control") || task.targetSessionId === null) {
5770
+ return task;
5771
+ }
5772
+ return { ...task, targetSessionId: null };
5773
+ }
5774
+ function scheduledTaskRunForGrant(run, grant) {
5775
+ if (hasPermission(grant.permissions, "sessions:control") || run.sessionId === null) {
5776
+ return run;
5777
+ }
5778
+ return { ...run, sessionId: null };
5779
+ }
5499
5780
  async function requireScheduledTaskRig(db, workspaceId, rigId) {
5500
5781
  const rig = await getRig3(db, workspaceId, rigId);
5501
5782
  if (!rig) {
@@ -5504,6 +5785,14 @@ async function requireScheduledTaskRig(db, workspaceId, rigId) {
5504
5785
  }
5505
5786
  async function validatedScheduledTaskUpdate(input) {
5506
5787
  const update = {};
5788
+ const existingTarget = input.existing.targetSessionId;
5789
+ const nextRunMode = input.payload.runMode ?? input.existing.runMode;
5790
+ const nextTargetSessionId = input.payload.targetSessionId !== void 0 ? input.payload.targetSessionId : nextRunMode === "existing_session" ? existingTarget : null;
5791
+ if (input.existing.runMode === "reusable_session" && input.existing.reusableSessionId && nextRunMode === "existing_session") {
5792
+ throw new HTTPException11(409, {
5793
+ message: "cannot target an existing session after this task created its reusable session; create a new task"
5794
+ });
5795
+ }
5507
5796
  if (input.payload.name !== void 0) {
5508
5797
  update.name = trimmedScheduledTaskName(input.payload.name);
5509
5798
  }
@@ -5597,6 +5886,33 @@ async function validatedScheduledTaskUpdate(input) {
5597
5886
  }
5598
5887
  update.personalConnectionDelegations = personalConnectionDelegations;
5599
5888
  }
5889
+ if (existingTarget && (nextRunMode !== "existing_session" || nextTargetSessionId !== existingTarget)) {
5890
+ await validateScheduledTaskTarget({
5891
+ db: input.db,
5892
+ sessionAuthorization: input.sessionAuthorization,
5893
+ authorizationSurface: input.authorizationSurface,
5894
+ grant: input.grant,
5895
+ targetSessionId: existingTarget,
5896
+ runMode: "existing_session",
5897
+ variableSetId: input.existing.variableSetId,
5898
+ rigId: input.existing.rigId,
5899
+ agentConfig: input.existing.agentConfig
5900
+ });
5901
+ }
5902
+ await validateScheduledTaskTarget({
5903
+ db: input.db,
5904
+ sessionAuthorization: input.sessionAuthorization,
5905
+ authorizationSurface: input.authorizationSurface,
5906
+ grant: input.grant,
5907
+ targetSessionId: nextTargetSessionId,
5908
+ runMode: nextRunMode,
5909
+ variableSetId: input.payload.variableSetId !== void 0 ? input.payload.variableSetId : input.existing.variableSetId,
5910
+ rigId: input.payload.rigId !== void 0 ? input.payload.rigId : input.existing.rigId,
5911
+ agentConfig: update.agentConfig ?? input.existing.agentConfig
5912
+ });
5913
+ if (input.payload.targetSessionId !== void 0 || input.existing.runMode === "existing_session" || nextRunMode === "existing_session") {
5914
+ update.targetSessionId = nextTargetSessionId;
5915
+ }
5600
5916
  return update;
5601
5917
  }
5602
5918
  async function requireScheduledTaskForApi(db, workspaceId, taskId) {
@@ -5626,28 +5942,44 @@ async function restoreScheduledTask(db, previous) {
5626
5942
  overlapPolicy: task.overlapPolicy,
5627
5943
  agentConfig: task.agentConfig,
5628
5944
  personalConnectionDelegations: previous.personalConnectionDelegations,
5629
- reusableSessionId: task.reusableSessionId,
5945
+ ...task.runMode === "existing_session" ? { targetSessionId: task.targetSessionId } : { reusableSessionId: task.reusableSessionId },
5630
5946
  variableSetId: task.variableSetId,
5631
5947
  rigId: task.rigId,
5632
5948
  metadata: task.metadata
5633
5949
  });
5634
5950
  }
5951
+ var ScheduledTaskSyncError = class extends Error {
5952
+ persistenceRestored;
5953
+ constructor(cause, persistenceRestored) {
5954
+ super(cause instanceof Error ? cause.message : String(cause), { cause });
5955
+ this.name = "ScheduledTaskSyncError";
5956
+ this.persistenceRestored = persistenceRestored;
5957
+ }
5958
+ };
5635
5959
  async function syncCreatedScheduledTask(input) {
5636
5960
  try {
5637
5961
  await input.workflowClient.syncScheduledTask({ task: input.task });
5638
5962
  } catch (error) {
5639
- await deleteScheduledTask(input.db, input.task.workspaceId, input.task.id).catch(
5640
- () => void 0
5641
- );
5642
- throw error;
5963
+ let persistenceRestored = true;
5964
+ try {
5965
+ await deleteScheduledTask(input.db, input.task.workspaceId, input.task.id);
5966
+ } catch {
5967
+ persistenceRestored = false;
5968
+ }
5969
+ throw new ScheduledTaskSyncError(error, persistenceRestored);
5643
5970
  }
5644
5971
  }
5645
5972
  async function syncUpdatedScheduledTask(input) {
5646
5973
  try {
5647
5974
  await input.workflowClient.syncScheduledTask({ task: input.task });
5648
5975
  } catch (error) {
5649
- await restoreScheduledTask(input.db, input.previous).catch(() => void 0);
5650
- throw error;
5976
+ let persistenceRestored = true;
5977
+ try {
5978
+ await restoreScheduledTask(input.db, input.previous);
5979
+ } catch {
5980
+ persistenceRestored = false;
5981
+ }
5982
+ throw new ScheduledTaskSyncError(error, persistenceRestored);
5651
5983
  }
5652
5984
  }
5653
5985
  function scheduledTaskTemporalScheduleId(taskId) {
@@ -6157,7 +6489,6 @@ async function getWorkspaceInsights(db, settings, input) {
6157
6489
  // src/domain/memory-slack-publication.ts
6158
6490
  import { createHash } from "crypto";
6159
6491
  import {
6160
- redactSensitiveText,
6161
6492
  stableJson as stableJson3
6162
6493
  } from "@opengeni/contracts";
6163
6494
  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}$/;
@@ -6226,9 +6557,8 @@ function evaluateMemorySlackPublication(input) {
6226
6557
  const deliveryMode = effectiveDeliveryMode(policy, input.distribution);
6227
6558
  if (!deliveryMode) return denied("below_noise_policy");
6228
6559
  const collapsedSummary = collapseText(input.distribution.shareSummary);
6229
- const redactedSummary = redactSensitiveText(collapsedSummary);
6230
- if (!redactedSummary) return denied("missing_summary");
6231
- const summary = truncateUtf8(redactedSummary, MEMORY_SLACK_SUMMARY_MAX_UTF8_BYTES);
6560
+ if (!collapsedSummary) return denied("missing_summary");
6561
+ const summary = truncateUtf8(collapsedSummary, MEMORY_SLACK_SUMMARY_MAX_UTF8_BYTES);
6232
6562
  const namespace = normalizeNamespace(input.memory.namespace);
6233
6563
  const labels = normalizeLabels(input.memory.labels);
6234
6564
  if (!namespace || !labels) return denied("invalid_input");
@@ -6244,13 +6574,11 @@ function evaluateMemorySlackPublication(input) {
6244
6574
  importance: input.distribution.importance,
6245
6575
  deliveryMode,
6246
6576
  summary: summary.value,
6247
- summaryRedacted: redactedSummary !== collapsedSummary,
6248
6577
  summaryTruncated: summary.truncated,
6249
6578
  namespace,
6250
6579
  labels: labels.values,
6251
6580
  labelsTruncated: labels.truncated,
6252
6581
  ownerLabel: owner.value,
6253
- ownerLabelRedacted: owner.redacted,
6254
6582
  ownerLabelTruncated: owner.truncated,
6255
6583
  authoritativeRecord: {
6256
6584
  workspaceId: input.context.workspaceId,
@@ -6324,10 +6652,8 @@ function effectiveDeliveryMode(policy, distribution) {
6324
6652
  }
6325
6653
  function normalizeNamespace(value) {
6326
6654
  const trimmed = value.trim();
6327
- if (redactSensitiveText(trimmed) !== trimmed) return null;
6328
6655
  const namespace = trimmed.toLowerCase();
6329
6656
  if (!namespace || utf8Bytes(namespace) > MEMORY_SLACK_NAMESPACE_MAX_UTF8_BYTES) return null;
6330
- if (redactSensitiveText(namespace) !== namespace) return null;
6331
6657
  const segments = namespace.split("/");
6332
6658
  if (segments.some((segment) => !SELECTOR_SEGMENT_PATTERN.test(segment))) return null;
6333
6659
  return segments.join("/");
@@ -6338,9 +6664,8 @@ function normalizeLabels(values) {
6338
6664
  for (const value of values) {
6339
6665
  if (typeof value !== "string") return null;
6340
6666
  const trimmed = value.trim();
6341
- if (redactSensitiveText(trimmed) !== trimmed) return null;
6342
6667
  const label = trimmed.toLowerCase();
6343
- if (redactSensitiveText(label) !== label || !SELECTOR_SEGMENT_PATTERN.test(label) || utf8Bytes(label) > MEMORY_SLACK_LABEL_MAX_UTF8_BYTES) {
6668
+ if (!SELECTOR_SEGMENT_PATTERN.test(label) || utf8Bytes(label) > MEMORY_SLACK_LABEL_MAX_UTF8_BYTES) {
6344
6669
  return null;
6345
6670
  }
6346
6671
  labels.add(label);
@@ -6353,12 +6678,10 @@ function normalizeLabels(values) {
6353
6678
  }
6354
6679
  function boundedOptionalText(value, maxBytes) {
6355
6680
  const collapsed = collapseText(value ?? "");
6356
- if (!collapsed) return { value: null, redacted: false, truncated: false };
6357
- const redacted = redactSensitiveText(collapsed);
6358
- const bounded = truncateUtf8(redacted, maxBytes);
6681
+ if (!collapsed) return { value: null, truncated: false };
6682
+ const bounded = truncateUtf8(collapsed, maxBytes);
6359
6683
  return {
6360
6684
  value: bounded.value || null,
6361
- redacted: redacted !== collapsed,
6362
6685
  truncated: bounded.truncated
6363
6686
  };
6364
6687
  }
@@ -6437,7 +6760,7 @@ import {
6437
6760
  } from "@opengeni/contracts";
6438
6761
  import {
6439
6762
  getNewSessionDraftInTransaction,
6440
- getEnrollment as getEnrollment3,
6763
+ getEnrollment as getEnrollment4,
6441
6764
  getRig as getRig4,
6442
6765
  getSandbox as getSandbox4,
6443
6766
  getVariableSet as getVariableSet4,
@@ -6508,7 +6831,7 @@ async function hydrateNewSessionDraft(deps, grant, workspaceId, row) {
6508
6831
  }
6509
6832
  if (options.targetSandboxId) {
6510
6833
  const sandbox = await getSandbox4(deps.db, workspaceId, options.targetSandboxId);
6511
- const enrollment = sandbox?.enrollmentId ? await getEnrollment3(deps.db, workspaceId, sandbox.enrollmentId) : null;
6834
+ const enrollment = sandbox?.enrollmentId ? await getEnrollment4(deps.db, workspaceId, sandbox.enrollmentId) : null;
6512
6835
  if (!sandbox || sandbox.kind !== "selfhosted" || !enrollment || enrollment.status !== "active") {
6513
6836
  delete options.targetSandboxId;
6514
6837
  delete options.workingDir;
@@ -6619,7 +6942,7 @@ import {
6619
6942
  deleteSessionQueueItemInTransaction,
6620
6943
  editQueuedTurnInTransaction,
6621
6944
  getComposerDraftInTransaction,
6622
- getSession as getSession3,
6945
+ getSession as getSession4,
6623
6946
  getSessionEvent as getSessionEvent2,
6624
6947
  getWorkspaceControlEvent as getWorkspaceControlEvent2,
6625
6948
  getSessionQueueSnapshot,
@@ -6711,10 +7034,14 @@ async function publishAndWakeAgentCommand(deps, input) {
6711
7034
  wakeRevision: input.wakeRevision,
6712
7035
  ...input.controlRequested || input.interruptionCount > 0 ? { interruptionRequested: true } : {}
6713
7036
  });
6714
- } catch (error) {
7037
+ } catch {
6715
7038
  console.warn(
6716
- `[session-commands] immediate Agent command wake failed for ${input.workspaceId}/${input.sessionId}; durable outbox will retry`,
6717
- error
7039
+ "[session-commands] immediate Agent command wake failed; durable outbox will retry",
7040
+ {
7041
+ errorClass: "WorkflowWakeOperationError",
7042
+ errorCode: "agent_command_wake_failed",
7043
+ origin: "core"
7044
+ }
6718
7045
  );
6719
7046
  }
6720
7047
  }
@@ -6722,10 +7049,15 @@ async function requestControlWakeDispatch(deps, wakeCount) {
6722
7049
  if (wakeCount === 0) return;
6723
7050
  try {
6724
7051
  await deps.workflowClient.requestSessionWorkflowWakeDispatch();
6725
- } catch (error) {
7052
+ } catch {
6726
7053
  console.warn(
6727
- `[session-commands] immediate control wake dispatch failed for ${wakeCount} committed revisions; durable outbox will retry`,
6728
- error
7054
+ "[session-commands] immediate control wake dispatch failed; durable outbox will retry",
7055
+ {
7056
+ errorClass: "WorkflowWakeOperationError",
7057
+ errorCode: "control_wake_dispatch_failed",
7058
+ origin: "core",
7059
+ wakeCount
7060
+ }
6729
7061
  );
6730
7062
  }
6731
7063
  }
@@ -6989,7 +7321,7 @@ async function steerHumanQueuePrompt(deps, context, turnId, input) {
6989
7321
  await publishWorkspaceControlEvent(deps, context.workspaceId, result.workspaceControlEventId);
6990
7322
  return response;
6991
7323
  }
6992
- async function controlHumanSessionWorkstream(deps, context, input) {
7324
+ async function controlHumanSessionWorkstreamWithOutcome(deps, context, input) {
6993
7325
  const authorization = await authorizeHumanSessionCommand(deps, context, "session.control");
6994
7326
  const result = await withWorkspaceRls(
6995
7327
  deps.db,
@@ -7024,7 +7356,10 @@ async function controlHumanSessionWorkstream(deps, context, input) {
7024
7356
  }
7025
7357
  await publishWorkspaceControlEvent(deps, context.workspaceId, result.workspaceControlEventId);
7026
7358
  await requestControlWakeDispatch(deps, result.wakeCount);
7027
- return response;
7359
+ return { response, replay: result.replay };
7360
+ }
7361
+ async function controlHumanSessionWorkstream(deps, context, input) {
7362
+ return (await controlHumanSessionWorkstreamWithOutcome(deps, context, input)).response;
7028
7363
  }
7029
7364
  async function controlHumanWorkspace(deps, context, input) {
7030
7365
  const result = await withWorkspaceRls(
@@ -7067,7 +7402,7 @@ async function getHumanComposerDraft(deps, context) {
7067
7402
  );
7068
7403
  const mapped = composerDraft(row);
7069
7404
  if (mapped) return mapped;
7070
- const session = await getSession3(deps.db, context.workspaceId, context.sessionId);
7405
+ const session = await getSession4(deps.db, context.workspaceId, context.sessionId);
7071
7406
  if (!session) throw new Error(`Session not found: ${context.sessionId}`);
7072
7407
  return {
7073
7408
  revision: 0,
@@ -7091,6 +7426,7 @@ async function saveHumanComposerDraft(deps, context, input) {
7091
7426
  (tx) => saveComposerDraftInTransaction(tx, {
7092
7427
  ...context,
7093
7428
  ...input,
7429
+ resources: normalizeResources(input.resources),
7094
7430
  subjectId: context.subjectId
7095
7431
  })
7096
7432
  )
@@ -7119,11 +7455,14 @@ export {
7119
7455
  SESSION_WORKFLOW_WAKE_DISPATCHER_PERIOD_MS,
7120
7456
  SESSION_WORKFLOW_WAKE_DISPATCHER_SCHEDULE_ID,
7121
7457
  SESSION_WORKFLOW_WAKE_DISPATCHER_WORKFLOW_TYPE,
7458
+ ScheduledTaskSyncError,
7122
7459
  SessionAuthorizationDeniedError,
7123
7460
  SessionAuthorizationUnavailableError,
7124
7461
  SessionSpawnDeniedError,
7462
+ TRANSCRIPTION_PROVIDER_REQUEST_TIMEOUT_MILLISECONDS,
7125
7463
  TranscriptionServiceError,
7126
7464
  acceptSessionUserMessage,
7465
+ acceptSessionUserMessageWithOutcome,
7127
7466
  accessGrantAuthorizationFromContext,
7128
7467
  activateRigVersionForApi,
7129
7468
  appendRigSetupCommand,
@@ -7146,14 +7485,18 @@ export {
7146
7485
  captureScheduledTaskRestoreState,
7147
7486
  checkLimit,
7148
7487
  classifyRigVerificationOutcome,
7488
+ codexAppsCatalogItem,
7149
7489
  controlAgentSessionWorkstream,
7150
7490
  controlHumanSessionWorkstream,
7491
+ controlHumanSessionWorkstreamWithOutcome,
7151
7492
  controlHumanWorkspace,
7152
7493
  createAndStartSession,
7494
+ createAndStartSessionWithOutcome,
7153
7495
  createCatalogItem,
7154
7496
  createRigForApi,
7155
7497
  createRigVersionForApi,
7156
7498
  createSessionForRequest,
7499
+ createSessionForRequestWithOutcome,
7157
7500
  createValidatedScheduledTask,
7158
7501
  creationInitiatorForGrant,
7159
7502
  defaultSessionMcpServerIds,
@@ -7172,7 +7515,9 @@ export {
7172
7515
  getActorNewSessionDraft,
7173
7516
  getCapabilityPack,
7174
7517
  getHumanComposerDraft,
7518
+ getManagedSession,
7175
7519
  getWorkspaceInsights,
7520
+ hasLiteralPermission,
7176
7521
  hasPermission,
7177
7522
  hasReservedOpenGeniSlackBotMetadata,
7178
7523
  hasReservedOpenGeniSlackBotSessionMetadata,
@@ -7215,11 +7560,14 @@ export {
7215
7560
  recordWorkspaceUsage,
7216
7561
  relayConfigFromSettings,
7217
7562
  relayDialBaseFromSettings,
7563
+ reportSessionUsageRecordingFailure,
7218
7564
  requireAccessContext,
7219
7565
  requireAccessGrant,
7220
7566
  requireAccessGrantAuthorization,
7221
7567
  requireEnvironmentEncryption,
7222
7568
  requireLimit,
7569
+ requireLiteralPermission,
7570
+ requireLiveAgentAttemptAuthorization,
7223
7571
  requireOpenGeniSlackBotConnection,
7224
7572
  requirePermission,
7225
7573
  requireQueuedTurnForApi,
@@ -7242,6 +7590,8 @@ export {
7242
7590
  saveActorNewSessionDraft,
7243
7591
  saveHumanComposerDraft,
7244
7592
  scheduledSlackBotConnectionId,
7593
+ scheduledTaskForGrant,
7594
+ scheduledTaskRunForGrant,
7245
7595
  scheduledTaskTemporalScheduleId,
7246
7596
  scheduledTaskToolsProvided,
7247
7597
  scheduledTaskTriggerToken,
@@ -7270,6 +7620,7 @@ export {
7270
7620
  validateGitHubRepositorySelectionShapes,
7271
7621
  validateMcpCapabilityConnection,
7272
7622
  validateOpenGeniSlackBotConnectionSelection,
7623
+ validateScheduledTaskTarget,
7273
7624
  validateToolRefs,
7274
7625
  validateToolRefsForSessionPolicy,
7275
7626
  validateVariableSetAttachment,