@opengeni/core 0.21.2 → 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,6 +3,26 @@ 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
7
27
  var TRANSCRIPTION_PROVIDER_REQUEST_TIMEOUT_MILLISECONDS = 10 * 60 * 1e3;
8
28
  var TranscriptionServiceError = class extends Error {
@@ -79,7 +99,7 @@ function filenameForMimeType(mimeType) {
79
99
 
80
100
  // src/sandbox/fleet.ts
81
101
  import {
82
- getEnrollment,
102
+ getEnrollment as getEnrollment2,
83
103
  getSandbox as getSandbox2,
84
104
  listSandboxes,
85
105
  readActiveSandbox as readActiveSandbox2,
@@ -89,6 +109,7 @@ import {
89
109
  } from "@opengeni/db";
90
110
  import {
91
111
  NatsControlRpc as NatsControlRpc2,
112
+ NatsOpStreamTransport as NatsOpStreamTransport2,
92
113
  selfhostedLiveness,
93
114
  SelfhostedSession,
94
115
  swapTargetEstablishability
@@ -100,6 +121,7 @@ import { sandboxArchiveCaptureTimeoutMs } from "@opengeni/config";
100
121
  import {
101
122
  advanceWorkspaceGenerationForDirectRequest,
102
123
  advanceWorkspaceGenerationForRetainedProcess,
124
+ getEnrollment,
103
125
  getRetainedProcess,
104
126
  getSandbox,
105
127
  markWarmLeaseInstanceLost,
@@ -115,6 +137,7 @@ import {
115
137
  isProviderSandboxGoneDuringRoutedOperation,
116
138
  makeActiveBackendResolver,
117
139
  NatsControlRpc,
140
+ NatsOpStreamTransport,
118
141
  RoutingSandboxSession,
119
142
  resolveModalCheckpointProviderBindingForSession
120
143
  } from "@opengeni/runtime/sandbox";
@@ -154,6 +177,18 @@ function controlRpcFactory(bus) {
154
177
  return bus.getRequestConnection();
155
178
  });
156
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
+ }
157
192
  function routingEnabled(settings) {
158
193
  return settings.sandboxSelfhostedEnabled === true;
159
194
  }
@@ -317,6 +352,7 @@ function wrapChannelABoxWithRouting(services, ids, established) {
317
352
  } : null;
318
353
  },
319
354
  controlRpcFactory: controlRpcFactory(bus),
355
+ resolveSelfhostedOpStream: (sandbox) => resolveSelfhostedOpStream(services, ids.workspaceId, sandbox),
320
356
  relay: relayConfigFromSettings(settings),
321
357
  selfhostedTimeoutMs: settings.sandboxSelfhostedControlTimeoutMs,
322
358
  selfhostedExecTimeoutMs: settings.sandboxSelfhostedExecTimeoutMs,
@@ -463,6 +499,10 @@ async function listFleet(services, ctx) {
463
499
  const groupRecovering = Boolean(
464
500
  groupLease && (groupLease.liveness === "warming" || groupLease.recovery.restore.status === "pending" || groupLease.recovery.restore.status === "restoring" || groupLease.recovery.restore.status === "verifying")
465
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";
466
506
  entries.push({
467
507
  id: ctx.sessionGroupId,
468
508
  kind: ctx.sessionBackend === "selfhosted" ? "selfhosted" : "modal",
@@ -472,6 +512,7 @@ async function listFleet(services, ctx) {
472
512
  isSessionGroup: true,
473
513
  enrollmentId: null,
474
514
  attachable: groupOnline,
515
+ operationAvailability: groupOperationAvailability,
475
516
  providerStatus: groupLease?.recovery.provider.status ?? "not_created",
476
517
  leaseLiveness: groupLease?.liveness ?? null,
477
518
  routeStatus: groupActive ? "attached" : "detached",
@@ -489,7 +530,10 @@ async function listFleet(services, ctx) {
489
530
  if (sandbox.kind !== "selfhosted" || !sandbox.enrollmentId) {
490
531
  continue;
491
532
  }
492
- 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
+ }
493
537
  const probe = enrollment ? await probeEnrollment(services, ctx.workspaceId, enrollment) : { liveness: "offline", consented: false, hasDisplay: false };
494
538
  entries.push({
495
539
  id: sandbox.id,
@@ -500,6 +544,7 @@ async function listFleet(services, ctx) {
500
544
  isSessionGroup: false,
501
545
  enrollmentId: sandbox.enrollmentId,
502
546
  attachable: probe.liveness === "online",
547
+ operationAvailability: probe.liveness === "online" ? "ready" : probe.liveness === "reconnecting" ? "recovering" : "unavailable",
503
548
  consented: probe.consented,
504
549
  hasDisplay: probe.hasDisplay,
505
550
  lastSeenAt: enrollment?.lastSeenAt ?? null,
@@ -549,7 +594,7 @@ async function resolveTarget(services, ctx, target) {
549
594
  code: "offline_enrollment"
550
595
  };
551
596
  }
552
- const enrollment = await getEnrollment(services.db, ctx.workspaceId, sandbox.enrollmentId);
597
+ const enrollment = await getEnrollment2(services.db, ctx.workspaceId, sandbox.enrollmentId);
553
598
  if (!enrollment) {
554
599
  return {
555
600
  ok: false,
@@ -648,7 +693,8 @@ async function executeRunOnSelfhostedMachine(machine, target, op) {
648
693
  controlRpc: machine.controlRpc,
649
694
  relay: machine.relay,
650
695
  timeoutMs: machine.controlTimeoutMs,
651
- execTimeoutMs: machine.execTimeoutMs
696
+ execTimeoutMs: machine.execTimeoutMs,
697
+ ...machine.opStream !== void 0 ? { opStream: machine.opStream } : {}
652
698
  });
653
699
  try {
654
700
  if (op.kind === "exec") {
@@ -691,6 +737,8 @@ async function executeRunOnSelfhostedMachine(machine, target, op) {
691
737
  // so leave `timedOut` absent while still reporting the enforced deadline.
692
738
  ...op.kind === "exec" ? { deadlineMs: session.effectiveExecDeadlineMs } : {}
693
739
  };
740
+ } finally {
741
+ await session.finalizeOpStreamOps().catch(() => void 0);
694
742
  }
695
743
  }
696
744
  async function runOnSandbox(services, ctx, target, op) {
@@ -712,7 +760,7 @@ async function runOnSandbox(services, ctx, target, op) {
712
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)`
713
761
  };
714
762
  }
715
- const enrollment = await getEnrollment(services.db, ctx.workspaceId, sandbox.enrollmentId);
763
+ const enrollment = await getEnrollment2(services.db, ctx.workspaceId, sandbox.enrollmentId);
716
764
  if (!enrollment || enrollment.status !== "active") {
717
765
  return {
718
766
  target,
@@ -729,7 +777,14 @@ async function runOnSandbox(services, ctx, target, op) {
729
777
  controlRpc: controlRpc(services.bus),
730
778
  relay: relayConfigFromSettings(services.settings),
731
779
  controlTimeoutMs: services.settings.sandboxSelfhostedControlTimeoutMs,
732
- 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
+ } : {}
733
788
  },
734
789
  target,
735
790
  op
@@ -741,7 +796,7 @@ async function provisionSandbox(services, ctx, input) {
741
796
  const base = (services.settings.publicBaseUrl ?? "https://get.opengeni.ai").replace(/\/+$/, "");
742
797
  return {
743
798
  kind: "selfhosted",
744
- 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.",
745
800
  // Install from THIS control plane's origin (not a hardcoded public CDN): the
746
801
  // served install script is rewritten to pull the per-SHA agent baked into
747
802
  // this exact deployment (see apps/api/src/routes/install.ts), so a deployed
@@ -853,10 +908,43 @@ function requirePermission(grant, permission) {
853
908
  });
854
909
  }
855
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
+ }
856
921
  function hasPermission(permissions, permission) {
922
+ if (permission === "secrets:read") {
923
+ return permissions.includes("secrets:read");
924
+ }
857
925
  const aliases = {
858
926
  "variable-sets:use": ["environments:use"],
859
- "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"]
860
948
  };
861
949
  return permissions.includes(permission) || (aliases[permission]?.some((alias) => permissions.includes(alias)) ?? false) || permissions.includes("workspace:admin");
862
950
  }
@@ -912,9 +1000,7 @@ async function resolveAccessContext(c, deps) {
912
1000
  }
913
1001
  }
914
1002
  if (deps.managedAuth) {
915
- const session = await deps.managedAuth.api.getSession({
916
- headers: c.req.raw.headers
917
- });
1003
+ const session = await getManagedSession(c, deps.managedAuth);
918
1004
  if (session?.user) {
919
1005
  return await ensureManagedAccessForUser(deps.db, {
920
1006
  userId: session.user.id,
@@ -1054,6 +1140,13 @@ var SessionAuthorizationUnavailableError = class extends Error {
1054
1140
  this.name = "SessionAuthorizationUnavailableError";
1055
1141
  }
1056
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
+ }
1057
1150
  async function requireSessionAuthorization(deps, grant, input) {
1058
1151
  const port = deps.sessionAuthorization;
1059
1152
  const slackAccess = await getSlackInteractionSessionAccessForSession(deps.db, {
@@ -1749,7 +1842,8 @@ async function buildCapabilityCatalog(input) {
1749
1842
  workspacePacks,
1750
1843
  socialConnections,
1751
1844
  bundledSkills,
1752
- curatedLibrarySkills
1845
+ curatedLibrarySkills,
1846
+ codexAppsCredentialId
1753
1847
  ] = await Promise.all([
1754
1848
  listCapabilityCatalogItems(input.db, input.workspaceId),
1755
1849
  listCapabilityInstallations(input.db, input.workspaceId),
@@ -1757,7 +1851,8 @@ async function buildCapabilityCatalog(input) {
1757
1851
  listWorkspaceCapabilityPacks(input.db, input.workspaceId),
1758
1852
  listSocialConnections(input.db, input.workspaceId, 500, input.subjectId),
1759
1853
  discoverBundledSkills(),
1760
- discoverCuratedSkillLibraryItems()
1854
+ discoverCuratedSkillLibraryItems(),
1855
+ input.settings.codexConnectedAppsEnabled ? resolveCodexAppsCredentialIdForRun(input.db, input.workspaceId) : Promise.resolve(null)
1761
1856
  ]);
1762
1857
  const capabilityInstallationById = new Map(
1763
1858
  capabilityInstallations.map((installation) => [installation.capabilityId, installation])
@@ -1775,7 +1870,14 @@ async function buildCapabilityCatalog(input) {
1775
1870
  ...bundledSkills,
1776
1871
  ...curatedLibrarySkills
1777
1872
  ];
1778
- 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(
1779
1881
  (item) => applyCapabilityEnablement(item, capabilityInstallationById.get(item.id), activePackIds)
1780
1882
  ).sort(compareCatalogItems);
1781
1883
  return {
@@ -1795,7 +1897,7 @@ async function createCatalogItem(input) {
1795
1897
  message: "skill ids are managed by the OpenGeni skill library or runtime adapters"
1796
1898
  });
1797
1899
  }
1798
- 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)) {
1799
1901
  throw new HTTPException6(422, {
1800
1902
  message: `${CODEX_APPS_MCP_SERVER_ID} is reserved for the canonical Codex Apps service`
1801
1903
  });
@@ -2435,7 +2537,7 @@ function packCatalogItem(pack, source) {
2435
2537
  });
2436
2538
  }
2437
2539
  function configuredMcpCatalogItems(settings) {
2438
- return settings.mcpServers.map(
2540
+ return settings.mcpServers.filter((server) => server.id !== CODEX_APPS_MCP_SERVER_ID).map(
2439
2541
  (server) => CapabilityCatalogItem.parse({
2440
2542
  id: `mcp:${server.id}`,
2441
2543
  kind: "mcp",
@@ -2460,6 +2562,38 @@ function configuredMcpCatalogItems(settings) {
2460
2562
  })
2461
2563
  );
2462
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
+ }
2463
2597
  function platformApiCatalogItems(socialConnections) {
2464
2598
  const xConnection = preferredSocialConnection(socialConnections, "x");
2465
2599
  const xEnabled = xConnection?.status === "connected" || xConnection?.status === "needs_reauth";
@@ -2656,6 +2790,9 @@ function applyCapabilityEnablement(item, installation, activePackIds) {
2656
2790
  if (item.surfaceType === "first_party_social") {
2657
2791
  return { ...item, connectionRef: null };
2658
2792
  }
2793
+ if (item.surfaceType === "codex_apps") {
2794
+ return { ...item, connectionRef: null };
2795
+ }
2659
2796
  if (item.source === "built_in" || item.source === "configured") {
2660
2797
  return {
2661
2798
  ...item,
@@ -3487,6 +3624,7 @@ import {
3487
3624
  mergeResourceRefs as mergeContractResourceRefs,
3488
3625
  mergeToolRefs,
3489
3626
  normalizeRepositorySubpath,
3627
+ normalizeRepositoryTransportUri,
3490
3628
  normalizeResourceMountPath,
3491
3629
  resourceIdentityKey,
3492
3630
  resourceMountPath,
@@ -3558,21 +3696,18 @@ function normalizeResources(resources) {
3558
3696
  mountPath
3559
3697
  };
3560
3698
  } else {
3561
- const url = parseResourceUrl(resource.uri);
3562
- if (url.protocol !== "https:" || !url.hostname) {
3563
- throw new HTTPException8(422, { message: "repository resources must use HTTPS Git URLs" });
3564
- }
3565
- const path = url.pathname.replace(/^\/+|\/+$/g, "").replace(/\.git$/, "");
3566
- const parts = path.split("/").filter(Boolean);
3567
- if (parts.length < 2) {
3568
- 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
+ });
3569
3707
  }
3570
- const repo = parts.join("/");
3571
- const normalizedUri = `https://${url.host.toLowerCase()}/${repo}.git`;
3572
3708
  const mountPath = normalizeMountPath(
3573
- resource.mountPath ?? defaultRepositoryMountPath(normalizedUri)
3709
+ resource.mountPath ?? defaultRepositoryMountPath(normalizedUri, credentialProvider)
3574
3710
  );
3575
- const credentialProvider = gitCredentialProviderForRepository(resource);
3576
3711
  const credentialBindingId = gitCredentialBindingIdForRepository(resource, credentialProvider);
3577
3712
  if ((resource.credentialBindingId || resource.connectionId || resource.access) && !credentialProvider) {
3578
3713
  throw new HTTPException8(422, {
@@ -3732,13 +3867,6 @@ function normalizeMountPath(path) {
3732
3867
  throw new HTTPException8(422, { message: `invalid resource mount path: ${path}` });
3733
3868
  }
3734
3869
  }
3735
- function parseResourceUrl(uri) {
3736
- try {
3737
- return new URL(uri);
3738
- } catch {
3739
- throw new HTTPException8(422, { message: "repository resources must use valid URLs" });
3740
- }
3741
- }
3742
3870
  function positiveInteger(value) {
3743
3871
  if (typeof value === "number" && Number.isInteger(value) && value > 0) {
3744
3872
  return value;
@@ -3882,6 +4010,7 @@ import {
3882
4010
  getRig as getRig3,
3883
4011
  getScheduledTask,
3884
4012
  getScheduledTaskPersonalConnectionDelegations,
4013
+ getSession as getSession3,
3885
4014
  requireWorkspace as requireWorkspace2,
3886
4015
  updateScheduledTask
3887
4016
  } from "@opengeni/db";
@@ -3916,7 +4045,7 @@ import {
3916
4045
  createSessionWithIdempotencyKeyResult,
3917
4046
  encryptVariableSetValue as encryptVariableSetValue2,
3918
4047
  getAnySessionInGroup,
3919
- getEnrollment as getEnrollment2,
4048
+ getEnrollment as getEnrollment3,
3920
4049
  getRig as getRig2,
3921
4050
  getWorkspaceDefaultRigId,
3922
4051
  listDistinctVariableSetIdsInGroup,
@@ -4305,7 +4434,7 @@ function validateSessionMcpCredentialUpdates(input) {
4305
4434
  });
4306
4435
  return encryptedUpdates;
4307
4436
  }
4308
- async function createAndStartSession(input) {
4437
+ async function createAndStartSessionWithOutcome(input) {
4309
4438
  const sessionMetadata = {
4310
4439
  ...input.metadata,
4311
4440
  model: input.model,
@@ -4351,12 +4480,24 @@ async function createAndStartSession(input) {
4351
4480
  }
4352
4481
  const { session: keyed, created } = keyedResult;
4353
4482
  if (!created) {
4354
- return await finishStartSession(
4483
+ const finished3 = await finishStartSession(
4355
4484
  keyed.temporalWorkflowId ? { ...input, seedTargetSandbox: null } : input,
4356
4485
  keyed
4357
4486
  );
4487
+ return {
4488
+ session: finished3.session,
4489
+ outcome: finished3.changed ? "repaired" : "replayed",
4490
+ replay: !finished3.changed,
4491
+ changed: finished3.changed
4492
+ };
4358
4493
  }
4359
- 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
+ };
4360
4501
  }
4361
4502
  let session;
4362
4503
  try {
@@ -4398,7 +4539,16 @@ async function createAndStartSession(input) {
4398
4539
  }
4399
4540
  throw error;
4400
4541
  }
4401
- 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;
4402
4552
  }
4403
4553
  async function finishStartSession(input, session) {
4404
4554
  if (input.seedTargetSandbox) {
@@ -4460,7 +4610,10 @@ async function finishStartSession(input, session) {
4460
4610
  }
4461
4611
  const persisted = await requireSession2(input.db, session.workspaceId, session.id);
4462
4612
  const initialTurnId = started.turn?.id ?? (await listSessionTurns(input.db, session.workspaceId, session.id, 1))[0]?.id ?? null;
4463
- return { ...persisted, initialTurnId };
4613
+ return {
4614
+ session: { ...persisted, initialTurnId },
4615
+ changed: started.changed
4616
+ };
4464
4617
  }
4465
4618
  function workflowIdForSession(sessionId) {
4466
4619
  return `session-${sessionId}`;
@@ -4645,15 +4798,16 @@ async function postUserMessageTurn(input) {
4645
4798
  wakeRevision: result.wakeRevision,
4646
4799
  ...(input.delivery ?? "send") === "steer" || result.interruptionCount > 0 ? { interruptionRequested: true } : {}
4647
4800
  });
4648
- } catch (error) {
4649
- console.warn(
4650
- `[sessions] workflow wake failed for committed prompt ${workspaceId}/${sessionId}; durable outbox will retry`,
4651
- error
4652
- );
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
+ });
4653
4807
  }
4654
- return { accepted, turn };
4808
+ return { accepted, turn, replay: result.replay };
4655
4809
  }
4656
- async function createSessionForRequest(deps, grant, workspaceId, rawPayload) {
4810
+ async function createSessionForRequestWithOutcome(deps, grant, workspaceId, rawPayload) {
4657
4811
  const { settings, db, bus, workflowClient, objectStorage } = deps;
4658
4812
  const payload = CreateSessionRequest.parse(rawPayload);
4659
4813
  if (hasReservedOpenGeniSlackBotSessionMetadata(payload.metadata)) {
@@ -4924,7 +5078,7 @@ async function createSessionForRequest(deps, grant, workspaceId, rawPayload) {
4924
5078
  if (targetSandbox?.kind === "selfhosted") {
4925
5079
  machineHomeBackend = "selfhosted";
4926
5080
  if (targetSandbox.enrollmentId) {
4927
- const enrollment = await getEnrollment2(db, workspaceId, targetSandbox.enrollmentId);
5081
+ const enrollment = await getEnrollment3(db, workspaceId, targetSandbox.enrollmentId);
4928
5082
  if (enrollment && (enrollment.os === "macos" || enrollment.os === "windows" || enrollment.os === "linux")) {
4929
5083
  machineHomeOs = enrollment.os;
4930
5084
  }
@@ -4941,9 +5095,9 @@ async function createSessionForRequest(deps, grant, workspaceId, rawPayload) {
4941
5095
  });
4942
5096
  }
4943
5097
  const creationInitiator = creationInitiatorForGrant(grant);
4944
- let session;
5098
+ let createOutcome;
4945
5099
  try {
4946
- session = await createAndStartSession({
5100
+ createOutcome = await createAndStartSessionWithOutcome({
4947
5101
  ...payload.requestedSessionId ? { requestedSessionId: payload.requestedSessionId } : {},
4948
5102
  db,
4949
5103
  bus,
@@ -5018,26 +5172,45 @@ async function createSessionForRequest(deps, grant, workspaceId, rawPayload) {
5018
5172
  }
5019
5173
  throw error;
5020
5174
  }
5175
+ let usageRecording = "recorded";
5021
5176
  if (payload.startMode !== "realtime") {
5022
- await recordWorkspaceUsage(deps, {
5023
- accountId: grant.accountId,
5024
- workspaceId,
5025
- subjectId: grant.subjectId,
5026
- eventType: "agent_run.created",
5027
- quantity: 1,
5028
- unit: "run",
5029
- sourceResourceType: "session",
5030
- sourceResourceId: session.id,
5031
- sessionId: session.id,
5032
- initiator: session.createdBy,
5033
- initiatorContext: session.createdByContext,
5034
- origin: creationInitiator.actor ? "system" : "user",
5035
- idempotencyKey: `agent_run.created:${workspaceId}:${session.id}`
5036
- });
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
+ }
5037
5197
  }
5038
- return session;
5198
+ return { ...createOutcome, usageRecording };
5039
5199
  }
5040
- 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) {
5041
5214
  const { settings, db, bus, workflowClient, objectStorage } = deps;
5042
5215
  await requireSessionAuthorization(deps, grant, {
5043
5216
  sessionId,
@@ -5105,7 +5278,7 @@ async function acceptSessionUserMessage(deps, grant, workspaceId, sessionId, inp
5105
5278
  source: personalConnectionDelegationSourceForGrant(grant)
5106
5279
  });
5107
5280
  const delegatedServiceInitiator = serviceInitiatorForGrant(grant);
5108
- const { accepted, turn } = await postUserMessageTurn({
5281
+ const { accepted, turn, replay } = await postUserMessageTurn({
5109
5282
  db,
5110
5283
  bus,
5111
5284
  workflowClient,
@@ -5155,6 +5328,16 @@ async function acceptSessionUserMessage(deps, grant, workspaceId, sessionId, inp
5155
5328
  origin: turn.source,
5156
5329
  idempotencyKey: `agent_run.created:${workspaceId}:${turn.id}`
5157
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
+ );
5158
5341
  return { accepted, turn };
5159
5342
  }
5160
5343
  async function updateSessionTitle(deps, grant, sessionId, title, source) {
@@ -5452,6 +5635,17 @@ async function createValidatedScheduledTask(input) {
5452
5635
  });
5453
5636
  const id = crypto.randomUUID();
5454
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
+ });
5455
5649
  if (input.payload.variableSetId) {
5456
5650
  await validateVariableSetAttachment(
5457
5651
  { settings: input.settings, db: input.db },
@@ -5492,11 +5686,97 @@ async function createValidatedScheduledTask(input) {
5492
5686
  ...creationInitiator.context ? { createdByContext: creationInitiator.context } : {},
5493
5687
  createdByActor: creationInitiator.actor ?? null,
5494
5688
  personalConnectionDelegations,
5689
+ targetSessionId: target?.id ?? null,
5495
5690
  variableSetId: input.payload.variableSetId ?? null,
5496
5691
  rigId: input.payload.rigId ?? null,
5497
5692
  metadata: input.payload.metadata
5498
5693
  });
5499
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
+ }
5500
5780
  async function requireScheduledTaskRig(db, workspaceId, rigId) {
5501
5781
  const rig = await getRig3(db, workspaceId, rigId);
5502
5782
  if (!rig) {
@@ -5505,6 +5785,14 @@ async function requireScheduledTaskRig(db, workspaceId, rigId) {
5505
5785
  }
5506
5786
  async function validatedScheduledTaskUpdate(input) {
5507
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
+ }
5508
5796
  if (input.payload.name !== void 0) {
5509
5797
  update.name = trimmedScheduledTaskName(input.payload.name);
5510
5798
  }
@@ -5598,6 +5886,33 @@ async function validatedScheduledTaskUpdate(input) {
5598
5886
  }
5599
5887
  update.personalConnectionDelegations = personalConnectionDelegations;
5600
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
+ }
5601
5916
  return update;
5602
5917
  }
5603
5918
  async function requireScheduledTaskForApi(db, workspaceId, taskId) {
@@ -5627,28 +5942,44 @@ async function restoreScheduledTask(db, previous) {
5627
5942
  overlapPolicy: task.overlapPolicy,
5628
5943
  agentConfig: task.agentConfig,
5629
5944
  personalConnectionDelegations: previous.personalConnectionDelegations,
5630
- reusableSessionId: task.reusableSessionId,
5945
+ ...task.runMode === "existing_session" ? { targetSessionId: task.targetSessionId } : { reusableSessionId: task.reusableSessionId },
5631
5946
  variableSetId: task.variableSetId,
5632
5947
  rigId: task.rigId,
5633
5948
  metadata: task.metadata
5634
5949
  });
5635
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
+ };
5636
5959
  async function syncCreatedScheduledTask(input) {
5637
5960
  try {
5638
5961
  await input.workflowClient.syncScheduledTask({ task: input.task });
5639
5962
  } catch (error) {
5640
- await deleteScheduledTask(input.db, input.task.workspaceId, input.task.id).catch(
5641
- () => void 0
5642
- );
5643
- 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);
5644
5970
  }
5645
5971
  }
5646
5972
  async function syncUpdatedScheduledTask(input) {
5647
5973
  try {
5648
5974
  await input.workflowClient.syncScheduledTask({ task: input.task });
5649
5975
  } catch (error) {
5650
- await restoreScheduledTask(input.db, input.previous).catch(() => void 0);
5651
- 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);
5652
5983
  }
5653
5984
  }
5654
5985
  function scheduledTaskTemporalScheduleId(taskId) {
@@ -6158,7 +6489,6 @@ async function getWorkspaceInsights(db, settings, input) {
6158
6489
  // src/domain/memory-slack-publication.ts
6159
6490
  import { createHash } from "crypto";
6160
6491
  import {
6161
- redactSensitiveText,
6162
6492
  stableJson as stableJson3
6163
6493
  } from "@opengeni/contracts";
6164
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}$/;
@@ -6227,9 +6557,8 @@ function evaluateMemorySlackPublication(input) {
6227
6557
  const deliveryMode = effectiveDeliveryMode(policy, input.distribution);
6228
6558
  if (!deliveryMode) return denied("below_noise_policy");
6229
6559
  const collapsedSummary = collapseText(input.distribution.shareSummary);
6230
- const redactedSummary = redactSensitiveText(collapsedSummary);
6231
- if (!redactedSummary) return denied("missing_summary");
6232
- 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);
6233
6562
  const namespace = normalizeNamespace(input.memory.namespace);
6234
6563
  const labels = normalizeLabels(input.memory.labels);
6235
6564
  if (!namespace || !labels) return denied("invalid_input");
@@ -6245,13 +6574,11 @@ function evaluateMemorySlackPublication(input) {
6245
6574
  importance: input.distribution.importance,
6246
6575
  deliveryMode,
6247
6576
  summary: summary.value,
6248
- summaryRedacted: redactedSummary !== collapsedSummary,
6249
6577
  summaryTruncated: summary.truncated,
6250
6578
  namespace,
6251
6579
  labels: labels.values,
6252
6580
  labelsTruncated: labels.truncated,
6253
6581
  ownerLabel: owner.value,
6254
- ownerLabelRedacted: owner.redacted,
6255
6582
  ownerLabelTruncated: owner.truncated,
6256
6583
  authoritativeRecord: {
6257
6584
  workspaceId: input.context.workspaceId,
@@ -6325,10 +6652,8 @@ function effectiveDeliveryMode(policy, distribution) {
6325
6652
  }
6326
6653
  function normalizeNamespace(value) {
6327
6654
  const trimmed = value.trim();
6328
- if (redactSensitiveText(trimmed) !== trimmed) return null;
6329
6655
  const namespace = trimmed.toLowerCase();
6330
6656
  if (!namespace || utf8Bytes(namespace) > MEMORY_SLACK_NAMESPACE_MAX_UTF8_BYTES) return null;
6331
- if (redactSensitiveText(namespace) !== namespace) return null;
6332
6657
  const segments = namespace.split("/");
6333
6658
  if (segments.some((segment) => !SELECTOR_SEGMENT_PATTERN.test(segment))) return null;
6334
6659
  return segments.join("/");
@@ -6339,9 +6664,8 @@ function normalizeLabels(values) {
6339
6664
  for (const value of values) {
6340
6665
  if (typeof value !== "string") return null;
6341
6666
  const trimmed = value.trim();
6342
- if (redactSensitiveText(trimmed) !== trimmed) return null;
6343
6667
  const label = trimmed.toLowerCase();
6344
- 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) {
6345
6669
  return null;
6346
6670
  }
6347
6671
  labels.add(label);
@@ -6354,12 +6678,10 @@ function normalizeLabels(values) {
6354
6678
  }
6355
6679
  function boundedOptionalText(value, maxBytes) {
6356
6680
  const collapsed = collapseText(value ?? "");
6357
- if (!collapsed) return { value: null, redacted: false, truncated: false };
6358
- const redacted = redactSensitiveText(collapsed);
6359
- const bounded = truncateUtf8(redacted, maxBytes);
6681
+ if (!collapsed) return { value: null, truncated: false };
6682
+ const bounded = truncateUtf8(collapsed, maxBytes);
6360
6683
  return {
6361
6684
  value: bounded.value || null,
6362
- redacted: redacted !== collapsed,
6363
6685
  truncated: bounded.truncated
6364
6686
  };
6365
6687
  }
@@ -6438,7 +6760,7 @@ import {
6438
6760
  } from "@opengeni/contracts";
6439
6761
  import {
6440
6762
  getNewSessionDraftInTransaction,
6441
- getEnrollment as getEnrollment3,
6763
+ getEnrollment as getEnrollment4,
6442
6764
  getRig as getRig4,
6443
6765
  getSandbox as getSandbox4,
6444
6766
  getVariableSet as getVariableSet4,
@@ -6509,7 +6831,7 @@ async function hydrateNewSessionDraft(deps, grant, workspaceId, row) {
6509
6831
  }
6510
6832
  if (options.targetSandboxId) {
6511
6833
  const sandbox = await getSandbox4(deps.db, workspaceId, options.targetSandboxId);
6512
- 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;
6513
6835
  if (!sandbox || sandbox.kind !== "selfhosted" || !enrollment || enrollment.status !== "active") {
6514
6836
  delete options.targetSandboxId;
6515
6837
  delete options.workingDir;
@@ -6620,7 +6942,7 @@ import {
6620
6942
  deleteSessionQueueItemInTransaction,
6621
6943
  editQueuedTurnInTransaction,
6622
6944
  getComposerDraftInTransaction,
6623
- getSession as getSession3,
6945
+ getSession as getSession4,
6624
6946
  getSessionEvent as getSessionEvent2,
6625
6947
  getWorkspaceControlEvent as getWorkspaceControlEvent2,
6626
6948
  getSessionQueueSnapshot,
@@ -6712,10 +7034,14 @@ async function publishAndWakeAgentCommand(deps, input) {
6712
7034
  wakeRevision: input.wakeRevision,
6713
7035
  ...input.controlRequested || input.interruptionCount > 0 ? { interruptionRequested: true } : {}
6714
7036
  });
6715
- } catch (error) {
7037
+ } catch {
6716
7038
  console.warn(
6717
- `[session-commands] immediate Agent command wake failed for ${input.workspaceId}/${input.sessionId}; durable outbox will retry`,
6718
- 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
+ }
6719
7045
  );
6720
7046
  }
6721
7047
  }
@@ -6723,10 +7049,15 @@ async function requestControlWakeDispatch(deps, wakeCount) {
6723
7049
  if (wakeCount === 0) return;
6724
7050
  try {
6725
7051
  await deps.workflowClient.requestSessionWorkflowWakeDispatch();
6726
- } catch (error) {
7052
+ } catch {
6727
7053
  console.warn(
6728
- `[session-commands] immediate control wake dispatch failed for ${wakeCount} committed revisions; durable outbox will retry`,
6729
- 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
+ }
6730
7061
  );
6731
7062
  }
6732
7063
  }
@@ -6990,7 +7321,7 @@ async function steerHumanQueuePrompt(deps, context, turnId, input) {
6990
7321
  await publishWorkspaceControlEvent(deps, context.workspaceId, result.workspaceControlEventId);
6991
7322
  return response;
6992
7323
  }
6993
- async function controlHumanSessionWorkstream(deps, context, input) {
7324
+ async function controlHumanSessionWorkstreamWithOutcome(deps, context, input) {
6994
7325
  const authorization = await authorizeHumanSessionCommand(deps, context, "session.control");
6995
7326
  const result = await withWorkspaceRls(
6996
7327
  deps.db,
@@ -7025,7 +7356,10 @@ async function controlHumanSessionWorkstream(deps, context, input) {
7025
7356
  }
7026
7357
  await publishWorkspaceControlEvent(deps, context.workspaceId, result.workspaceControlEventId);
7027
7358
  await requestControlWakeDispatch(deps, result.wakeCount);
7028
- return response;
7359
+ return { response, replay: result.replay };
7360
+ }
7361
+ async function controlHumanSessionWorkstream(deps, context, input) {
7362
+ return (await controlHumanSessionWorkstreamWithOutcome(deps, context, input)).response;
7029
7363
  }
7030
7364
  async function controlHumanWorkspace(deps, context, input) {
7031
7365
  const result = await withWorkspaceRls(
@@ -7068,7 +7402,7 @@ async function getHumanComposerDraft(deps, context) {
7068
7402
  );
7069
7403
  const mapped = composerDraft(row);
7070
7404
  if (mapped) return mapped;
7071
- const session = await getSession3(deps.db, context.workspaceId, context.sessionId);
7405
+ const session = await getSession4(deps.db, context.workspaceId, context.sessionId);
7072
7406
  if (!session) throw new Error(`Session not found: ${context.sessionId}`);
7073
7407
  return {
7074
7408
  revision: 0,
@@ -7092,6 +7426,7 @@ async function saveHumanComposerDraft(deps, context, input) {
7092
7426
  (tx) => saveComposerDraftInTransaction(tx, {
7093
7427
  ...context,
7094
7428
  ...input,
7429
+ resources: normalizeResources(input.resources),
7095
7430
  subjectId: context.subjectId
7096
7431
  })
7097
7432
  )
@@ -7120,12 +7455,14 @@ export {
7120
7455
  SESSION_WORKFLOW_WAKE_DISPATCHER_PERIOD_MS,
7121
7456
  SESSION_WORKFLOW_WAKE_DISPATCHER_SCHEDULE_ID,
7122
7457
  SESSION_WORKFLOW_WAKE_DISPATCHER_WORKFLOW_TYPE,
7458
+ ScheduledTaskSyncError,
7123
7459
  SessionAuthorizationDeniedError,
7124
7460
  SessionAuthorizationUnavailableError,
7125
7461
  SessionSpawnDeniedError,
7126
7462
  TRANSCRIPTION_PROVIDER_REQUEST_TIMEOUT_MILLISECONDS,
7127
7463
  TranscriptionServiceError,
7128
7464
  acceptSessionUserMessage,
7465
+ acceptSessionUserMessageWithOutcome,
7129
7466
  accessGrantAuthorizationFromContext,
7130
7467
  activateRigVersionForApi,
7131
7468
  appendRigSetupCommand,
@@ -7148,14 +7485,18 @@ export {
7148
7485
  captureScheduledTaskRestoreState,
7149
7486
  checkLimit,
7150
7487
  classifyRigVerificationOutcome,
7488
+ codexAppsCatalogItem,
7151
7489
  controlAgentSessionWorkstream,
7152
7490
  controlHumanSessionWorkstream,
7491
+ controlHumanSessionWorkstreamWithOutcome,
7153
7492
  controlHumanWorkspace,
7154
7493
  createAndStartSession,
7494
+ createAndStartSessionWithOutcome,
7155
7495
  createCatalogItem,
7156
7496
  createRigForApi,
7157
7497
  createRigVersionForApi,
7158
7498
  createSessionForRequest,
7499
+ createSessionForRequestWithOutcome,
7159
7500
  createValidatedScheduledTask,
7160
7501
  creationInitiatorForGrant,
7161
7502
  defaultSessionMcpServerIds,
@@ -7174,7 +7515,9 @@ export {
7174
7515
  getActorNewSessionDraft,
7175
7516
  getCapabilityPack,
7176
7517
  getHumanComposerDraft,
7518
+ getManagedSession,
7177
7519
  getWorkspaceInsights,
7520
+ hasLiteralPermission,
7178
7521
  hasPermission,
7179
7522
  hasReservedOpenGeniSlackBotMetadata,
7180
7523
  hasReservedOpenGeniSlackBotSessionMetadata,
@@ -7217,11 +7560,14 @@ export {
7217
7560
  recordWorkspaceUsage,
7218
7561
  relayConfigFromSettings,
7219
7562
  relayDialBaseFromSettings,
7563
+ reportSessionUsageRecordingFailure,
7220
7564
  requireAccessContext,
7221
7565
  requireAccessGrant,
7222
7566
  requireAccessGrantAuthorization,
7223
7567
  requireEnvironmentEncryption,
7224
7568
  requireLimit,
7569
+ requireLiteralPermission,
7570
+ requireLiveAgentAttemptAuthorization,
7225
7571
  requireOpenGeniSlackBotConnection,
7226
7572
  requirePermission,
7227
7573
  requireQueuedTurnForApi,
@@ -7244,6 +7590,8 @@ export {
7244
7590
  saveActorNewSessionDraft,
7245
7591
  saveHumanComposerDraft,
7246
7592
  scheduledSlackBotConnectionId,
7593
+ scheduledTaskForGrant,
7594
+ scheduledTaskRunForGrant,
7247
7595
  scheduledTaskTemporalScheduleId,
7248
7596
  scheduledTaskToolsProvided,
7249
7597
  scheduledTaskTriggerToken,
@@ -7272,6 +7620,7 @@ export {
7272
7620
  validateGitHubRepositorySelectionShapes,
7273
7621
  validateMcpCapabilityConnection,
7274
7622
  validateOpenGeniSlackBotConnectionSelection,
7623
+ validateScheduledTaskTarget,
7275
7624
  validateToolRefs,
7276
7625
  validateToolRefsForSessionPolicy,
7277
7626
  validateVariableSetAttachment,