@opengeni/core 0.24.1 → 0.28.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -116,6 +116,9 @@ import {
116
116
  ogatxEditableArtifactMutationIntentCodec,
117
117
  validateEditableArtifactActor
118
118
  } from "./chunk-JJU6I5XD.js";
119
+ import {
120
+ getManagedSession
121
+ } from "./chunk-IBOEYG6N.js";
119
122
 
120
123
  // src/workflow-wake-contract.ts
121
124
  var SESSION_WORKFLOW_WAKE_DISPATCHER_SCHEDULE_ID = "opengeni-session-workflow-wake-dispatcher";
@@ -123,26 +126,6 @@ var SESSION_WORKFLOW_WAKE_DISPATCHER_WORKFLOW_TYPE = "sessionWorkflowWakeDispatc
123
126
  var SESSION_WORKFLOW_WAKE_DISPATCHER_PERIOD_MS = 1e4;
124
127
  var TURN_ACTIVITY_CANCELLATION_HEARTBEAT_INTERVAL_MS = 500;
125
128
 
126
- // src/managed-session.ts
127
- async function getManagedSession(c, auth) {
128
- const result = await auth.api.getSession({
129
- headers: c.req.raw.headers,
130
- returnHeaders: true
131
- });
132
- for (const cookie of setCookieHeaders(result.headers)) {
133
- c.header("set-cookie", cookie, { append: true });
134
- }
135
- return result.response;
136
- }
137
- function setCookieHeaders(headers) {
138
- const getSetCookie = headers.getSetCookie;
139
- if (getSetCookie) {
140
- return getSetCookie.call(headers);
141
- }
142
- const cookie = headers.get("set-cookie");
143
- return cookie ? [cookie] : [];
144
- }
145
-
146
129
  // src/transcription.ts
147
130
  var TRANSCRIPTION_PROVIDER_REQUEST_TIMEOUT_MILLISECONDS = 10 * 60 * 1e3;
148
131
  var TranscriptionServiceError = class extends Error {
@@ -234,7 +217,6 @@ import {
234
217
  SelfhostedSession,
235
218
  swapTargetEstablishability
236
219
  } from "@opengeni/runtime/sandbox";
237
- import { HTTPException } from "hono/http-exception";
238
220
 
239
221
  // src/sandbox/routing.ts
240
222
  import { sandboxLifecycleTransitionWaitMs } from "@opengeni/config";
@@ -553,11 +535,6 @@ function wrapChannelABoxWithRouting(services, ids, established) {
553
535
  // src/sandbox/fleet.ts
554
536
  async function buildFleetContextForSession(deps, ctx) {
555
537
  const session = await requireSession(deps.db, ctx.workspaceId, ctx.sessionId);
556
- if (session.sandboxBackend === "none") {
557
- throw new HTTPException(422, {
558
- message: "this session has no sandbox (backend: none); the fleet is unavailable"
559
- });
560
- }
561
538
  return {
562
539
  accountId: ctx.accountId,
563
540
  workspaceId: ctx.workspaceId,
@@ -613,40 +590,42 @@ async function listFleet(services, ctx) {
613
590
  activeEpoch: 0
614
591
  };
615
592
  const entries = [];
616
- const groupActive = pointer.activeSandboxId === null;
617
- const groupLease = await readLease(db, ctx.workspaceId, ctx.sessionGroupId);
618
- const groupOnline = Boolean(
619
- groupLease?.liveness === "warm" && groupLease.recovery.provider.status === "exists" && groupLease.recovery.workspace.status === "ready"
620
- );
621
- const groupRecovering = Boolean(
622
- groupLease && (groupLease.liveness === "warming" || groupLease.recovery.restore.status === "pending" || groupLease.recovery.restore.status === "restoring" || groupLease.recovery.restore.status === "verifying")
623
- );
624
- const groupRecoveryUnavailable = Boolean(
625
- groupLease && (groupLease.recovery.restore.status === "degraded" || groupLease.recovery.restore.status === "unrecoverable" || groupLease.recovery.workspace.status === "degraded" || groupLease.recovery.workspace.status === "unrecoverable")
626
- );
627
- const groupOperationAvailability = groupOnline ? "ready" : groupRecoveryUnavailable ? "unavailable" : groupRecovering ? "recovering" : ctx.sessionBackend === "selfhosted" ? "unavailable" : "wakeable";
628
- entries.push({
629
- id: ctx.sessionGroupId,
630
- kind: ctx.sessionBackend === "selfhosted" ? "selfhosted" : "modal",
631
- name: "session sandbox",
632
- liveness: groupOnline ? "online" : groupRecovering ? "reconnecting" : "offline",
633
- active: groupActive,
634
- isSessionGroup: true,
635
- enrollmentId: null,
636
- attachable: groupOnline,
637
- operationAvailability: groupOperationAvailability,
638
- providerStatus: groupLease?.recovery.provider.status ?? "not_created",
639
- leaseLiveness: groupLease?.liveness ?? null,
640
- routeStatus: groupActive ? "attached" : "detached",
641
- archiveStatus: groupLease?.recovery.archive.status ?? "none",
642
- restoreStatus: groupLease?.recovery.restore.status ?? "not_required",
643
- workspaceStatus: groupLease?.recovery.workspace.status ?? "unknown",
644
- leaseEpoch: groupLease?.leaseEpoch ?? null,
645
- routeEpoch: pointer.activeEpoch,
646
- workspaceGeneration: groupLease?.workspaceGeneration ?? null,
647
- archiveGeneration: groupLease?.archiveGeneration ?? null,
648
- archiveComplete: groupLease?.archiveComplete ?? false
649
- });
593
+ if (ctx.sessionBackend !== "none") {
594
+ const groupActive = pointer.activeSandboxId === null;
595
+ const groupLease = await readLease(db, ctx.workspaceId, ctx.sessionGroupId);
596
+ const groupOnline = Boolean(
597
+ groupLease?.liveness === "warm" && groupLease.recovery.provider.status === "exists" && groupLease.recovery.workspace.status === "ready"
598
+ );
599
+ const groupRecovering = Boolean(
600
+ groupLease && (groupLease.liveness === "warming" || groupLease.recovery.restore.status === "pending" || groupLease.recovery.restore.status === "restoring" || groupLease.recovery.restore.status === "verifying")
601
+ );
602
+ const groupRecoveryUnavailable = Boolean(
603
+ groupLease && (groupLease.recovery.restore.status === "degraded" || groupLease.recovery.restore.status === "unrecoverable" || groupLease.recovery.workspace.status === "degraded" || groupLease.recovery.workspace.status === "unrecoverable")
604
+ );
605
+ const groupOperationAvailability = groupOnline ? "ready" : groupRecoveryUnavailable ? "unavailable" : groupRecovering ? "recovering" : ctx.sessionBackend === "selfhosted" ? "unavailable" : "wakeable";
606
+ entries.push({
607
+ id: ctx.sessionGroupId,
608
+ kind: ctx.sessionBackend === "selfhosted" ? "selfhosted" : "modal",
609
+ name: "session sandbox",
610
+ liveness: groupOnline ? "online" : groupRecovering ? "reconnecting" : "offline",
611
+ active: groupActive,
612
+ isSessionGroup: true,
613
+ enrollmentId: null,
614
+ attachable: groupOnline,
615
+ operationAvailability: groupOperationAvailability,
616
+ providerStatus: groupLease?.recovery.provider.status ?? "not_created",
617
+ leaseLiveness: groupLease?.liveness ?? null,
618
+ routeStatus: groupActive ? "attached" : "detached",
619
+ archiveStatus: groupLease?.recovery.archive.status ?? "none",
620
+ restoreStatus: groupLease?.recovery.restore.status ?? "not_required",
621
+ workspaceStatus: groupLease?.recovery.workspace.status ?? "unknown",
622
+ leaseEpoch: groupLease?.leaseEpoch ?? null,
623
+ routeEpoch: pointer.activeEpoch,
624
+ workspaceGeneration: groupLease?.workspaceGeneration ?? null,
625
+ archiveGeneration: groupLease?.archiveGeneration ?? null,
626
+ archiveComplete: groupLease?.archiveComplete ?? false
627
+ });
628
+ }
650
629
  const sandboxes = await listSandboxes(db, ctx.workspaceId);
651
630
  for (const sandbox of sandboxes) {
652
631
  if (sandbox.kind !== "selfhosted" || !sandbox.enrollmentId) {
@@ -691,6 +670,13 @@ async function listFleet(services, ctx) {
691
670
  }
692
671
  async function resolveTarget(services, ctx, target) {
693
672
  if (target === ctx.sessionGroupId || target === "session" || target === "default") {
673
+ if (ctx.sessionBackend === "none") {
674
+ return {
675
+ ok: false,
676
+ reason: "this session has no home sandbox; attach a Connected Machine",
677
+ code: "unsupported_backend_context"
678
+ };
679
+ }
694
680
  return { ok: true, targetSandboxId: null };
695
681
  }
696
682
  const sandbox = await getSandbox2(services.db, ctx.workspaceId, target);
@@ -955,12 +941,18 @@ import {
955
941
  getWorkspaceGrant,
956
942
  requireWorkspace
957
943
  } from "@opengeni/db";
958
- import { HTTPException as HTTPException2 } from "hono/http-exception";
944
+ import { HTTPException } from "hono/http-exception";
959
945
  var bearerPrefix = "Bearer ";
946
+ var accessContextByRequest = /* @__PURE__ */ new WeakMap();
960
947
  async function requireAccessContext(c, deps) {
961
- const context = await resolveAccessContext(c, deps);
948
+ let pending = accessContextByRequest.get(c.req.raw);
949
+ if (!pending) {
950
+ pending = resolveAccessContext(c, deps);
951
+ accessContextByRequest.set(c.req.raw, pending);
952
+ }
953
+ const context = await pending;
962
954
  if (!context) {
963
- throw new HTTPException2(401, { message: "authentication required" });
955
+ throw new HTTPException(401, { message: "authentication required" });
964
956
  }
965
957
  return context;
966
958
  }
@@ -996,9 +988,9 @@ async function requireAccessGrantAuthorization(c, deps, workspaceId, permission)
996
988
  if (!grant) {
997
989
  const workspace = await requireWorkspace(deps.db, workspaceId).catch(() => null);
998
990
  if (!workspace) {
999
- throw new HTTPException2(404, { message: "workspace not found" });
991
+ throw new HTTPException(404, { message: "workspace not found" });
1000
992
  }
1001
- throw new HTTPException2(403, { message: "workspace access denied" });
993
+ throw new HTTPException(403, { message: "workspace access denied" });
1002
994
  }
1003
995
  if (permission) {
1004
996
  requirePermission(grant, permission);
@@ -1016,23 +1008,23 @@ function hostedHumanSessionPrincipalKind(context) {
1016
1008
  function requirePermission(grant, permission) {
1017
1009
  if (!hasPermission(grant.permissions, permission)) {
1018
1010
  if (permission === "variable-sets:use") {
1019
- throw new HTTPException2(403, {
1011
+ throw new HTTPException(403, {
1020
1012
  message: "missing permission: variable-sets:use (deprecated alias: environments:use)"
1021
1013
  });
1022
1014
  }
1023
1015
  if (permission === "variable-sets:manage") {
1024
- throw new HTTPException2(403, {
1016
+ throw new HTTPException(403, {
1025
1017
  message: "missing permission: variable-sets:manage (deprecated alias: environments:manage)"
1026
1018
  });
1027
1019
  }
1028
- throw new HTTPException2(403, {
1020
+ throw new HTTPException(403, {
1029
1021
  message: `missing permission: ${permission}`
1030
1022
  });
1031
1023
  }
1032
1024
  }
1033
1025
  function requireLiteralPermission(grant, permission) {
1034
1026
  if (!hasLiteralPermission(grant.permissions, permission)) {
1035
- throw new HTTPException2(403, {
1027
+ throw new HTTPException(403, {
1036
1028
  message: `missing literal permission: ${permission}`
1037
1029
  });
1038
1030
  }
@@ -1122,7 +1114,7 @@ async function resolveAccessContext(c, deps) {
1122
1114
  }
1123
1115
  }
1124
1116
  if (deps.managedAuth) {
1125
- const session = await getManagedSession(c, deps.managedAuth);
1117
+ const session = await getManagedSession(c, deps.managedAuth, { db: deps.db });
1126
1118
  if (session?.user) {
1127
1119
  return await ensureManagedAccessForUser(deps.db, {
1128
1120
  userId: session.user.id,
@@ -1207,6 +1199,8 @@ async function delegatedAccessContext(c, deps, mode, token = bearerToken(c)) {
1207
1199
  delegated: true,
1208
1200
  ...payload.sessionId ? { sessionId: payload.sessionId } : {},
1209
1201
  ...payload.firstPartyMcpTools !== void 0 ? { firstPartyMcpTools: payload.firstPartyMcpTools } : {},
1202
+ ...payload.nestedAgentDepth !== void 0 ? { nestedAgentDepth: payload.nestedAgentDepth } : {},
1203
+ ...payload.effectiveMaxNestedAgentDepth !== void 0 ? { effectiveMaxNestedAgentDepth: payload.effectiveMaxNestedAgentDepth } : {},
1210
1204
  // Caller identity: the turn that minted this token. Tools classify the
1211
1205
  // CALLER from this instead of re-reading the live active pointer.
1212
1206
  ...payload.turnId ? { turnId: payload.turnId } : {},
@@ -1241,10 +1235,11 @@ import {
1241
1235
  SessionAuthorizationListScope
1242
1236
  } from "@opengeni/contracts";
1243
1237
  import {
1238
+ getSessionAuthorityProjection,
1244
1239
  getSession,
1245
- getSessionRootId,
1246
1240
  getSessionTurnForAttempt,
1247
- getSlackInteractionSessionAccessForSession
1241
+ getSlackInteractionSessionAccessForSession,
1242
+ withSessionRlsActorContext
1248
1243
  } from "@opengeni/db";
1249
1244
  var SESSION_AUTHORIZATION_DEFAULT_REAUTHORIZE_MS = 15e3;
1250
1245
  var SessionAuthorizationDeniedError = class extends Error {
@@ -1262,8 +1257,48 @@ var SessionAuthorizationUnavailableError = class extends Error {
1262
1257
  this.name = "SessionAuthorizationUnavailableError";
1263
1258
  }
1264
1259
  };
1260
+ function grantHasAgentAttemptAuthority(grant) {
1261
+ const hasAgentAttemptClaim = grant.metadata?.["turnId"] !== void 0 || grant.metadata?.["attemptId"] !== void 0 || grant.metadata?.["executionGeneration"] !== void 0;
1262
+ return grant.principalKind ? grant.principalKind === "agent_attempt" : hasAgentAttemptClaim;
1263
+ }
1264
+ var AGENT_PARENT_READ_OPERATIONS = /* @__PURE__ */ new Set([
1265
+ "session.read",
1266
+ "session.events.read",
1267
+ "session.stream.read",
1268
+ "session.turns.read",
1269
+ "session.queue.read",
1270
+ "session.composer.read",
1271
+ "session.lineage.read",
1272
+ "session.capture.read",
1273
+ "session.files.read",
1274
+ "session.git.read",
1275
+ "session.terminal.read",
1276
+ "session.viewer.read",
1277
+ "session.goal.read",
1278
+ "session.human_input.read"
1279
+ ]);
1280
+ function enforceAgentSessionHierarchy(actor, callerParentSessionId, target, operation) {
1281
+ if (target.target.sessionId === actor.callerSessionId) return "root";
1282
+ if (target.parentSessionId === actor.callerSessionId) return "target";
1283
+ if (callerParentSessionId === target.target.sessionId) {
1284
+ if (operation === "session.append" || AGENT_PARENT_READ_OPERATIONS.has(operation)) {
1285
+ return "target";
1286
+ }
1287
+ throw new SessionAuthorizationDeniedError("forbidden");
1288
+ }
1289
+ throw new SessionAuthorizationDeniedError("forbidden");
1290
+ }
1291
+ function sessionRlsActorForAuthorization(authorization) {
1292
+ return authorization.actor.kind === "agent_attempt" ? {
1293
+ subjectId: authorization.actor.subjectId,
1294
+ initiatingHumanSubjectId: authorization.actor.initiatingHumanSubjectId
1295
+ } : { subjectId: authorization.actor.subjectId };
1296
+ }
1297
+ async function withResolvedSessionAuthorization(authorization, fn) {
1298
+ return await withSessionRlsActorContext(sessionRlsActorForAuthorization(authorization), fn);
1299
+ }
1265
1300
  async function requireLiveAgentAttemptAuthorization(db, grant, callerSessionId) {
1266
- const actor = await resolveSessionAuthorizationActor(db, grant);
1301
+ const { actor } = await resolveSessionAuthorizationActor(db, grant);
1267
1302
  if (actor.kind !== "agent_attempt" || actor.callerSessionId !== callerSessionId) {
1268
1303
  throw new SessionAuthorizationDeniedError("caller_stale");
1269
1304
  }
@@ -1271,19 +1306,47 @@ async function requireLiveAgentAttemptAuthorization(db, grant, callerSessionId)
1271
1306
  }
1272
1307
  async function requireSessionAuthorization(deps, grant, input) {
1273
1308
  const port = deps.sessionAuthorization;
1274
- const slackAccess = await getSlackInteractionSessionAccessForSession(deps.db, {
1275
- accountId: grant.accountId,
1276
- workspaceId: grant.workspaceId,
1277
- sessionId: input.sessionId
1278
- });
1279
- if (!port && slackAccess?.visibility !== "private") return null;
1280
- const actor = await resolveSessionAuthorizationActor(deps.db, grant);
1281
- const target = slackAccess ? { sessionId: input.sessionId, rootSessionId: slackAccess.rootSessionId } : await resolveSessionAuthorizationTarget(deps.db, grant, input.sessionId);
1309
+ const isAgentAttempt = grantHasAgentAttemptAuthority(grant);
1310
+ const [slackAccess, authority] = await Promise.all([
1311
+ getSlackInteractionSessionAccessForSession(deps.db, {
1312
+ accountId: grant.accountId,
1313
+ workspaceId: grant.workspaceId,
1314
+ sessionId: input.sessionId
1315
+ }),
1316
+ getSessionAuthorityProjection(deps.db, grant.workspaceId, input.sessionId)
1317
+ ]);
1318
+ if (!port && !isAgentAttempt && slackAccess?.visibility !== "private" && authority?.visibility !== "user_private") {
1319
+ return null;
1320
+ }
1321
+ if (!authority) throw new SessionAuthorizationDeniedError("not_found");
1322
+ const [resolvedActor, resolvedTarget] = await Promise.all([
1323
+ resolveSessionAuthorizationActor(deps.db, grant),
1324
+ resolveSessionAuthorizationTarget(deps.db, grant, input.sessionId)
1325
+ ]);
1326
+ const actor = resolvedActor.actor;
1327
+ const target = resolvedTarget.target;
1328
+ const agentRelatedSessionAccess = actor.kind === "agent_attempt" ? enforceAgentSessionHierarchy(
1329
+ actor,
1330
+ resolvedActor.callerParentSessionId,
1331
+ resolvedTarget,
1332
+ input.operation
1333
+ ) : null;
1334
+ if (authority.visibility === "user_private") {
1335
+ const allowed = authority.ownerSubjectId !== null && (actor.kind === "subject" ? actor.subjectId === authority.ownerSubjectId : actor.initiatingHumanSubjectId === authority.ownerSubjectId);
1336
+ if (!allowed) throw new SessionAuthorizationDeniedError("forbidden");
1337
+ }
1282
1338
  if (slackAccess?.visibility === "private") {
1283
1339
  const allowed = actor.kind === "subject" ? actor.subjectId === slackAccess.owningSubjectId : actor.callerRootSessionId === target.rootSessionId;
1284
1340
  if (!allowed) throw new SessionAuthorizationDeniedError("forbidden");
1285
1341
  }
1286
- if (!port) return null;
1342
+ if (!port) {
1343
+ return {
1344
+ actor,
1345
+ target,
1346
+ relatedSessionAccess: agentRelatedSessionAccess ?? "root",
1347
+ reauthorizeAfterMs: null
1348
+ };
1349
+ }
1287
1350
  let rawDecision;
1288
1351
  try {
1289
1352
  rawDecision = await port.authorizeSession({
@@ -1307,14 +1370,16 @@ async function requireSessionAuthorization(deps, grant, input) {
1307
1370
  return {
1308
1371
  actor,
1309
1372
  target,
1310
- relatedSessionAccess: parsed.data.relatedSessionAccess ?? "target",
1373
+ relatedSessionAccess: agentRelatedSessionAccess === "target" ? "target" : parsed.data.relatedSessionAccess ?? "target",
1311
1374
  reauthorizeAfterMs: parsed.data.reauthorizeAfterMs ?? null
1312
1375
  };
1313
1376
  }
1314
1377
  async function requireSessionAuthorizationListScope(deps, grant, surface) {
1315
1378
  const port = deps.sessionAuthorization;
1379
+ const isAgentAttempt = grantHasAgentAttemptAuthority(grant);
1380
+ if (!port && !isAgentAttempt) return null;
1381
+ const { actor } = await resolveSessionAuthorizationActor(deps.db, grant);
1316
1382
  if (!port) return null;
1317
- const actor = await resolveSessionAuthorizationActor(deps.db, grant);
1318
1383
  let rawScope;
1319
1384
  try {
1320
1385
  rawScope = await port.resolveListScope({
@@ -1342,53 +1407,52 @@ async function resolveSessionAuthorizationTarget(db, grant, sessionId) {
1342
1407
  if (!session || session.accountId !== grant.accountId) {
1343
1408
  throw new SessionAuthorizationDeniedError("not_found");
1344
1409
  }
1345
- let rootSessionId;
1346
- try {
1347
- rootSessionId = await getSessionRootId(db, grant.workspaceId, session.id);
1348
- } catch (error) {
1349
- throw new SessionAuthorizationUnavailableError({ cause: error });
1350
- }
1351
- if (!rootSessionId) {
1352
- throw new SessionAuthorizationDeniedError("not_found");
1353
- }
1354
- return { sessionId: session.id, rootSessionId };
1410
+ return {
1411
+ target: { sessionId: session.id, rootSessionId: session.rootSessionId },
1412
+ parentSessionId: session.parentSessionId
1413
+ };
1355
1414
  }
1356
1415
  async function resolveSessionAuthorizationActor(db, grant) {
1357
1416
  const callerSessionId = grant.metadata?.["sessionId"];
1358
1417
  const turnId = grant.metadata?.["turnId"];
1359
1418
  const attemptId = grant.metadata?.["attemptId"];
1360
1419
  const executionGeneration = grant.metadata?.["executionGeneration"];
1361
- const hasAttemptClaim = turnId !== void 0 || attemptId !== void 0 || executionGeneration !== void 0;
1362
- if (!hasAttemptClaim) {
1363
- return SessionAuthorizationActor.parse({
1364
- kind: "subject",
1365
- subjectId: grant.subjectId,
1366
- ...grant.subjectLabel ? { subjectLabel: grant.subjectLabel } : {}
1367
- });
1420
+ const isAgentAttempt = grantHasAgentAttemptAuthority(grant);
1421
+ if (!isAgentAttempt) {
1422
+ return {
1423
+ actor: SessionAuthorizationActor.parse({
1424
+ kind: "subject",
1425
+ subjectId: grant.subjectId,
1426
+ ...grant.subjectLabel ? { subjectLabel: grant.subjectLabel } : {}
1427
+ }),
1428
+ callerParentSessionId: null
1429
+ };
1368
1430
  }
1369
1431
  if (typeof callerSessionId !== "string" || typeof turnId !== "string" || typeof attemptId !== "string" || typeof executionGeneration !== "number" || !Number.isSafeInteger(executionGeneration) || executionGeneration < 1) {
1370
1432
  throw new SessionAuthorizationDeniedError("caller_stale");
1371
1433
  }
1372
- const [callerSession, turn, callerRootSessionId] = await Promise.all([
1434
+ const [callerSession, turn] = await Promise.all([
1373
1435
  getSession(db, grant.workspaceId, callerSessionId),
1374
- getSessionTurnForAttempt(db, grant.workspaceId, callerSessionId, attemptId),
1375
- getSessionRootId(db, grant.workspaceId, callerSessionId).catch(() => null)
1436
+ getSessionTurnForAttempt(db, grant.workspaceId, callerSessionId, attemptId)
1376
1437
  ]);
1377
- if (!callerSession || callerSession.accountId !== grant.accountId || !turn || turn.id !== turnId || turn.executionGeneration !== executionGeneration || callerSession.activeTurnId !== turn.id || !callerRootSessionId) {
1438
+ if (!callerSession || callerSession.accountId !== grant.accountId || !turn || turn.id !== turnId || turn.executionGeneration !== executionGeneration || callerSession.activeTurnId !== turn.id) {
1378
1439
  throw new SessionAuthorizationDeniedError("caller_stale");
1379
1440
  }
1380
- return SessionAuthorizationActor.parse({
1381
- kind: "agent_attempt",
1382
- subjectId: grant.subjectId,
1383
- callerSessionId,
1384
- callerRootSessionId,
1385
- turnId,
1386
- attemptId,
1387
- executionGeneration,
1388
- initiator: turn.initiator,
1389
- initiatorContext: turn.initiatorContext,
1390
- initiatingHumanSubjectId: turn.initiatingHumanSubjectId ?? (turn.initiator.kind === "subject" ? turn.initiator.subjectId : null)
1391
- });
1441
+ return {
1442
+ actor: SessionAuthorizationActor.parse({
1443
+ kind: "agent_attempt",
1444
+ subjectId: grant.subjectId,
1445
+ callerSessionId,
1446
+ callerRootSessionId: callerSession.rootSessionId,
1447
+ turnId,
1448
+ attemptId,
1449
+ executionGeneration,
1450
+ initiator: turn.initiator,
1451
+ initiatorContext: turn.initiatorContext,
1452
+ initiatingHumanSubjectId: turn.initiatingHumanSubjectId ?? (turn.initiator.kind === "subject" ? turn.initiator.subjectId : null)
1453
+ }),
1454
+ callerParentSessionId: callerSession.parentSessionId
1455
+ };
1392
1456
  }
1393
1457
 
1394
1458
  // src/billing/limits.ts
@@ -1402,13 +1466,13 @@ import {
1402
1466
  recordUsageEvent,
1403
1467
  sumUsageQuantity
1404
1468
  } from "@opengeni/db";
1405
- import { HTTPException as HTTPException3 } from "hono/http-exception";
1469
+ import { HTTPException as HTTPException2 } from "hono/http-exception";
1406
1470
  async function requireLimit(deps, input) {
1407
1471
  const decision = await checkLimit(deps, input);
1408
1472
  if (decision.allowed) {
1409
1473
  return;
1410
1474
  }
1411
- throw new HTTPException3(decision.code === "insufficient_credits" ? 402 : 429, {
1475
+ throw new HTTPException2(decision.code === "insufficient_credits" ? 402 : 429, {
1412
1476
  message: decision.message
1413
1477
  });
1414
1478
  }
@@ -1579,10 +1643,12 @@ function startOfUtcMonth() {
1579
1643
  // src/domain/capabilities.ts
1580
1644
  import { Client } from "@modelcontextprotocol/sdk/client/index.js";
1581
1645
  import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
1582
- import { environmentsEncryptionKeyBytes as environmentsEncryptionKeyBytes2 } from "@opengeni/config";
1646
+ import { environmentsEncryptionKeyBytes } from "@opengeni/config";
1583
1647
  import {
1584
1648
  CapabilityCatalogItem,
1585
- capabilityCatalogItemIsTrustedForExposure
1649
+ capabilityCatalogItemIsTrustedForExposure,
1650
+ FIKEN_PROVIDER_DOMAIN as FIKEN_PROVIDER_DOMAIN2,
1651
+ FIRST_PARTY_MCP_TOOL_NAMES
1586
1652
  } from "@opengeni/contracts";
1587
1653
  import {
1588
1654
  CODEX_APPS_MCP_SERVER_ID,
@@ -1595,120 +1661,63 @@ import {
1595
1661
  decryptedCapabilityHeaders,
1596
1662
  disableCapabilityInstallation,
1597
1663
  enableCapabilityInstallation,
1598
- enablePackInstallation,
1599
1664
  encryptVariableSetValue,
1600
1665
  getCapabilityCatalogItem,
1601
1666
  getCapabilityInstallation,
1602
1667
  getConnectionMetadata,
1603
1668
  getCodexAppsCredentialAuthorizationForRun,
1604
1669
  getWorkspaceGrant as getWorkspaceGrant2,
1605
- getPackInstallation as getPackInstallation2,
1606
1670
  getStoredCapabilityHeaderCiphertext,
1607
- getVariableSet as getVariableSet3,
1608
1671
  listCapabilityCatalogItems,
1609
1672
  listCapabilityInstallations,
1610
1673
  listConnectionsMetadata,
1611
1674
  listEnabledMcpCapabilityServers,
1612
1675
  listInstalledApiIntegrations,
1676
+ listInstalledSkills,
1613
1677
  listPackInstallations as listPackInstallations2,
1614
1678
  listSocialConnections,
1615
1679
  mcpServerIdForCapability,
1616
- updatePackInstallationStatus,
1617
1680
  upsertCapabilityCatalogItem
1618
1681
  } from "@opengeni/db";
1619
- import { HTTPException as HTTPException6 } from "hono/http-exception";
1620
- import {
1621
- getSkillLibraryEntry,
1622
- listSkillLibraryEntries
1623
- } from "@opengeni/runtime/skill-library";
1624
-
1625
- // src/domain/environments.ts
1626
- import { environmentsEncryptionKeyBytes } from "@opengeni/config";
1627
- import { getVariableSet, recordAuditEvent } from "@opengeni/db";
1628
1682
  import { HTTPException as HTTPException4 } from "hono/http-exception";
1629
- var MAX_ENVIRONMENTS_PER_WORKSPACE = 25;
1630
- var MAX_VARIABLES_PER_ENVIRONMENT = 100;
1631
- var reservedExactNames = /* @__PURE__ */ new Set([
1632
- "HOME",
1633
- "PATH",
1634
- "SHELL",
1635
- "USER",
1636
- "LOGNAME",
1637
- "TMPDIR",
1638
- "IFS",
1639
- "ENV",
1640
- "BASH_ENV",
1641
- "NODE_OPTIONS",
1642
- "PYTHONPATH",
1643
- "PYTHONSTARTUP",
1644
- "PERL5OPT",
1645
- "PERL5LIB",
1646
- "GH_TOKEN",
1647
- "GITHUB_TOKEN",
1648
- "GITLAB_TOKEN",
1649
- "AZURE_DEVOPS_EXT_PAT",
1650
- "GIT_ASKPASS",
1651
- "GIT_TERMINAL_PROMPT"
1652
- ]);
1653
- var reservedPrefixes = [
1654
- "OPENGENI_",
1655
- "GIT_CONFIG_",
1656
- "GIT_AUTHOR_",
1657
- "GIT_COMMITTER_",
1658
- "LD_",
1659
- "DYLD_"
1660
- ];
1661
- function assertAllowedVariableSetVariableName(name) {
1662
- if (reservedExactNames.has(name) || reservedPrefixes.some((prefix) => name.startsWith(prefix))) {
1663
- throw new HTTPException4(422, {
1664
- message: `reserved variable set variable name / reserved environment variable name: ${name}`
1665
- });
1666
- }
1683
+
1684
+ // src/domain/fiken.ts
1685
+ import {
1686
+ FIKEN_CREDENTIAL_LABEL,
1687
+ FIKEN_CREDENTIAL_ROLE,
1688
+ FIKEN_PROVIDER_DOMAIN,
1689
+ FikenConnectionMetadata
1690
+ } from "@opengeni/contracts";
1691
+ function fikenConnectionMetadata(metadata) {
1692
+ const parsed = FikenConnectionMetadata.safeParse(metadata);
1693
+ return parsed.success ? parsed.data : null;
1667
1694
  }
1668
- var assertAllowedEnvironmentVariableName = assertAllowedVariableSetVariableName;
1669
- function requireVariableSetEncryption(settings) {
1670
- const key = environmentsEncryptionKeyBytes(settings);
1671
- if (!key) {
1672
- throw new HTTPException4(503, {
1673
- message: "variable sets require OPENGENI_ENVIRONMENTS_ENCRYPTION_KEY"
1674
- });
1675
- }
1676
- return key;
1695
+ function isFikenConnection(connection) {
1696
+ return connection.subjectId === null && connection.providerDomain === FIKEN_PROVIDER_DOMAIN && (connection.kind === "api_key" || connection.kind === "oauth2") && fikenConnectionMetadata(connection.metadata)?.credentialRole === FIKEN_CREDENTIAL_ROLE;
1677
1697
  }
1678
- var requireEnvironmentEncryption = requireVariableSetEncryption;
1679
- async function requireVariableSetForApi(db, workspaceId, variableSetId) {
1680
- const variableSet = await getVariableSet(db, workspaceId, variableSetId);
1681
- if (!variableSet) {
1682
- throw new HTTPException4(404, { message: "variableSet not found" });
1683
- }
1684
- return variableSet;
1698
+ function hasReservedFikenMetadata(metadata) {
1699
+ return metadata?.credentialRole === FIKEN_CREDENTIAL_ROLE || metadata?.credentialLabel === FIKEN_CREDENTIAL_LABEL;
1685
1700
  }
1686
- async function validateVariableSetAttachment(deps, grant, workspaceId, variableSetId, options = {}) {
1687
- requireVariableSetEncryption(deps.settings);
1688
- if (!options.preauthorized) {
1689
- requirePermission(grant, "variable-sets:use");
1701
+ function resolveFikenDefaultCompanySlug(input) {
1702
+ const accessible = (slug) => slug !== null && input.companies.some((company) => company.slug === slug);
1703
+ if (input.requested !== null) {
1704
+ return accessible(input.requested) ? input.requested : null;
1690
1705
  }
1691
- const variableSet = await getVariableSet(deps.db, workspaceId, variableSetId);
1692
- if (!variableSet) {
1693
- throw new HTTPException4(422, { message: "unknown variableSetId" });
1706
+ if (accessible(input.previous)) {
1707
+ return input.previous;
1694
1708
  }
1695
- return variableSet;
1709
+ return input.companies.length === 1 ? input.companies[0].slug : null;
1696
1710
  }
1697
- async function recordVariableSetAuditEvent(db, input) {
1698
- await recordAuditEvent(db, {
1699
- accountId: input.grant.accountId,
1700
- workspaceId: input.grant.workspaceId,
1701
- subjectId: input.grant.subjectId,
1702
- action: input.action,
1703
- targetType: "workspace_variable_set",
1704
- targetId: input.variableSetId,
1705
- metadata: {
1706
- variableSetId: input.variableSetId,
1707
- ...input.variableName ? { name: input.variableName } : {}
1708
- }
1709
- });
1711
+ function preferredFikenConnection(connections) {
1712
+ const statusRank = (status) => status === "active" ? 0 : status === "needs_reauth" ? 1 : 2;
1713
+ return [...connections].sort(
1714
+ (left, right) => statusRank(left.status) - statusRank(right.status) || right.updatedAt.localeCompare(left.updatedAt) || right.id.localeCompare(left.id)
1715
+ )[0] ?? null;
1710
1716
  }
1711
1717
 
1718
+ // src/domain/capabilities.ts
1719
+ import { listSkillLibraryEntries } from "@opengeni/runtime/skill-library";
1720
+
1712
1721
  // src/domain/packs.ts
1713
1722
  import { createHash } from "crypto";
1714
1723
  import {
@@ -1718,7 +1727,7 @@ import {
1718
1727
  import {
1719
1728
  getPackInstallation,
1720
1729
  getRig,
1721
- getVariableSet as getVariableSet2,
1730
+ getVariableSet,
1722
1731
  getWorkspacePack,
1723
1732
  listPackInstallations,
1724
1733
  listWorkspacePacks,
@@ -1726,7 +1735,7 @@ import {
1726
1735
  resolvePackInlineSkillReferences
1727
1736
  } from "@opengeni/db";
1728
1737
  import { buildPortableSkillArtifact } from "@opengeni/runtime/skill-library";
1729
- import { HTTPException as HTTPException5 } from "hono/http-exception";
1738
+ import { HTTPException as HTTPException3 } from "hono/http-exception";
1730
1739
  var MARKETING_SOCIAL_PACK_ID = "marketing-social-daily-analysis";
1731
1740
  var marketingSocialPack = {
1732
1741
  id: MARKETING_SOCIAL_PACK_ID,
@@ -1915,7 +1924,7 @@ function capabilityPackRequiresInstallationPlan(pack) {
1915
1924
  function inlinePackSkillInstall(pack, skill) {
1916
1925
  const artifact = buildPortableSkillArtifact(skill.files);
1917
1926
  if (artifact.name.toLowerCase() !== skill.name.toLowerCase()) {
1918
- throw new HTTPException5(422, {
1927
+ throw new HTTPException3(422, {
1919
1928
  message: `Pack Skill ${skill.name} has SKILL.md name ${artifact.name}; the names must match`
1920
1929
  });
1921
1930
  }
@@ -1979,7 +1988,7 @@ async function previewCapabilityPackInstallation(db, workspaceId, pack, options
1979
1988
  if (pack.variableSet?.required && !variableSetId) {
1980
1989
  blockers.push("Choose saved configuration before installing this Pack");
1981
1990
  } else if (variableSetId) {
1982
- const variableSet = await getVariableSet2(db, workspaceId, variableSetId);
1991
+ const variableSet = await getVariableSet(db, workspaceId, variableSetId);
1983
1992
  if (!variableSet) {
1984
1993
  blockers.push("The selected configuration no longer exists in this workspace");
1985
1994
  } else {
@@ -2094,7 +2103,7 @@ async function assertPackSandboxImageCompatible(db, workspaceId, pack) {
2094
2103
  }
2095
2104
  const other = await resolveCapabilityPack(db, workspaceId, installation.packId);
2096
2105
  if (other?.sandboxImage) {
2097
- throw new HTTPException5(409, {
2106
+ throw new HTTPException3(409, {
2098
2107
  message: `pack ${pack.id} declares a sandbox image, but enabled pack ${other.id} already declares one; only one enabled pack per workspace may declare sandboxImage \u2014 disable ${other.id} first`
2099
2108
  });
2100
2109
  }
@@ -2159,7 +2168,9 @@ async function buildCapabilityCatalog(input) {
2159
2168
  packInstallations,
2160
2169
  workspacePacks,
2161
2170
  socialConnections,
2171
+ workspaceConnections,
2162
2172
  curatedLibrarySkills,
2173
+ installedSkills,
2163
2174
  codexAppsCredentialId
2164
2175
  ] = await Promise.all([
2165
2176
  listCapabilityCatalogItems(input.db, input.workspaceId),
@@ -2167,11 +2178,19 @@ async function buildCapabilityCatalog(input) {
2167
2178
  listPackInstallations2(input.db, input.workspaceId),
2168
2179
  listWorkspaceCapabilityPacks(input.db, input.workspaceId),
2169
2180
  listSocialConnections(input.db, input.workspaceId, 500, input.subjectId),
2181
+ listConnectionsMetadata(input.db, input.workspaceId, null),
2170
2182
  discoverCuratedSkillLibraryItems(),
2183
+ listInstalledSkills(input.db, input.workspaceId),
2171
2184
  input.settings.codexConnectedAppsEnabled ? resolveCodexAppsCredentialIdForRun(input.db, input.workspaceId) : Promise.resolve(null)
2172
2185
  ]);
2186
+ const catalogInstallations = capabilityInstallations.filter(
2187
+ (installation) => installation.kind === "mcp"
2188
+ );
2173
2189
  const capabilityInstallationById = new Map(
2174
- capabilityInstallations.map((installation) => [installation.capabilityId, installation])
2190
+ catalogInstallations.map((installation) => [installation.capabilityId, installation])
2191
+ );
2192
+ const installedSkillById = new Map(
2193
+ installedSkills.filter((skill) => skill.owners.some((owner) => owner.kind === "direct")).map((skill) => [skill.capabilityId, skill])
2175
2194
  );
2176
2195
  const activePackIds = new Set(
2177
2196
  packInstallations.filter((installation) => installation.status === "active").map((installation) => installation.packId)
@@ -2183,66 +2202,54 @@ async function buildCapabilityCatalog(input) {
2183
2202
  ),
2184
2203
  ...configuredMcpCatalogItems(input.settings),
2185
2204
  ...providerIntegrationCatalogItems(socialConnections),
2186
- ...curatedLibrarySkills
2205
+ fikenCatalogItem(workspaceConnections.filter(isFikenConnection)),
2206
+ ...curatedLibrarySkills,
2207
+ ...installedSkills.filter(
2208
+ (skill) => skill.source !== "library" && skill.owners.some((owner) => owner.kind === "direct")
2209
+ ).map(installedSkillCatalogItem)
2187
2210
  ];
2188
2211
  const codexApps = input.settings.codexConnectedAppsEnabled ? codexAppsCatalogItem(codexAppsCredentialId !== null) : null;
2189
2212
  const items = dedupeCatalogItems([
2190
2213
  ...builtIns,
2191
- ...persistedItems.filter((item) => !isReservedCodexAppsCatalogItem(item)),
2214
+ ...persistedItems.filter(
2215
+ (item) => item.kind !== "skill" && item.kind !== "api" && item.kind !== "plugin" && !isReservedCodexAppsCatalogItem(item)
2216
+ ),
2192
2217
  // Keep the reserved, server-derived item authoritative over any stale
2193
2218
  // legacy catalog row with the same id.
2194
2219
  ...codexApps ? [codexApps] : []
2195
2220
  ]).map((item) => {
2196
- const runtimeItem = applyInstalledArtifactRuntime(item);
2197
- const projected = applyCapabilityEnablement(
2198
- runtimeItem,
2199
- capabilityInstallationById.get(runtimeItem.id),
2200
- activePackIds
2201
- );
2221
+ const projected = item.kind === "skill" ? applyInstalledSkillEnablement(item, installedSkillById.get(item.id)) : applyCapabilityEnablement(item, capabilityInstallationById.get(item.id), activePackIds);
2202
2222
  return applyCapabilityLifecycle(projected);
2203
2223
  }).sort(compareCatalogItems);
2204
2224
  return {
2205
2225
  items,
2206
- installations: capabilityInstallations
2207
- };
2208
- }
2209
- function applyInstalledArtifactRuntime(item) {
2210
- if (item.kind === "api" && item.metadata.platformVersion === 2 && typeof item.metadata.pluginVersionId === "string" && typeof item.metadata.apiFacetId === "string" && typeof item.metadata.revisionId === "string" && typeof item.metadata.serverId === "string") {
2211
- return {
2212
- ...item,
2213
- runtime: {
2214
- available: true,
2215
- mcpServerId: item.metadata.serverId,
2216
- transport: "local-adapter",
2217
- notes: "Available through an immutable workspace API Integration revision."
2218
- }
2219
- };
2220
- }
2221
- if (item.kind !== "skill" || item.metadata.platformVersion !== 2 || typeof item.metadata.pluginVersionId !== "string" || typeof item.metadata.facetId !== "string" || typeof item.metadata.sourceCommit !== "string" || typeof item.metadata.contentSha256 !== "string") {
2222
- return item;
2223
- }
2224
- return {
2225
- ...item,
2226
- runtime: {
2227
- available: true,
2228
- notes: "Installed from an immutable workspace Skill artifact."
2229
- }
2226
+ installations: catalogInstallations
2230
2227
  };
2231
2228
  }
2232
2229
  async function createCatalogItem(input) {
2233
2230
  const id = input.payload.id?.trim() || generatedCapabilityId(input.payload);
2234
2231
  if (id.startsWith("pack:")) {
2235
- throw new HTTPException6(422, {
2232
+ throw new HTTPException4(422, {
2236
2233
  message: "packs are managed by OpenGeni and cannot be manually created"
2237
2234
  });
2238
2235
  }
2239
2236
  if (id.startsWith("skill:")) {
2240
- throw new HTTPException6(422, {
2241
- message: "skill ids are managed by the OpenGeni skill library or runtime adapters"
2237
+ throw new HTTPException4(422, {
2238
+ message: "Skills are installed through the Skill library or source import flow"
2239
+ });
2240
+ }
2241
+ if (id.startsWith("api:")) {
2242
+ throw new HTTPException4(422, {
2243
+ message: "API Integrations are installed from typed Integration Definitions"
2244
+ });
2245
+ }
2246
+ if (id.startsWith("plugin:")) {
2247
+ throw new HTTPException4(422, {
2248
+ message: "Plugins are installed through the Plugin Package flow"
2242
2249
  });
2243
2250
  }
2244
2251
  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)) {
2245
- throw new HTTPException6(422, {
2252
+ throw new HTTPException4(422, {
2246
2253
  message: `${CODEX_APPS_MCP_SERVER_ID} is reserved for the canonical Codex Apps service`
2247
2254
  });
2248
2255
  }
@@ -2275,8 +2282,28 @@ async function enableCapability(input) {
2275
2282
  input.settings,
2276
2283
  input.capabilityId
2277
2284
  );
2285
+ if (item.kind === "skill") {
2286
+ throw new HTTPException4(409, {
2287
+ message: "Install Skills through the Skill library or source import flow"
2288
+ });
2289
+ }
2290
+ if (item.kind === "api") {
2291
+ throw new HTTPException4(409, {
2292
+ message: "Install API Integrations through the Integration Definitions flow"
2293
+ });
2294
+ }
2295
+ if (item.kind === "plugin") {
2296
+ throw new HTTPException4(409, {
2297
+ message: "Install Plugins through the Plugin Package flow"
2298
+ });
2299
+ }
2300
+ if (item.kind === "pack") {
2301
+ throw new HTTPException4(409, {
2302
+ message: "Install Packs through the Pack installation preview flow"
2303
+ });
2304
+ }
2278
2305
  if (item.kind === "mcp" && !item.runtime.available) {
2279
- throw new HTTPException6(422, {
2306
+ throw new HTTPException4(422, {
2280
2307
  message: "MCP capabilities need a remote streamable HTTP endpoint before they can be enabled"
2281
2308
  });
2282
2309
  }
@@ -2286,41 +2313,6 @@ async function enableCapability(input) {
2286
2313
  delete installationConfig.headersEncrypted;
2287
2314
  delete installationConfig.headerNames;
2288
2315
  delete installationConfig.connectionRef;
2289
- if (item.kind === "skill" && item.source === "library") {
2290
- const libraryId = stringMetadata(item.metadata.libraryId);
2291
- const catalogVersion = stringMetadata(item.metadata.version);
2292
- if (!libraryId || !catalogVersion) {
2293
- throw new HTTPException6(422, {
2294
- message: `skill library metadata is incomplete for ${item.id}`
2295
- });
2296
- }
2297
- const requestedVersion = input.payload.config.version;
2298
- if (requestedVersion !== void 0 && typeof requestedVersion !== "string") {
2299
- throw new HTTPException6(422, {
2300
- message: "skill activation config.version must be a string"
2301
- });
2302
- }
2303
- const normalizedVersion = requestedVersion?.trim() || catalogVersion;
2304
- if (normalizedVersion !== catalogVersion) {
2305
- throw new HTTPException6(422, {
2306
- message: `skill ${libraryId} only supports immutable version ${catalogVersion}`
2307
- });
2308
- }
2309
- const entry = getSkillLibraryEntry(libraryId, normalizedVersion);
2310
- if (!entry) {
2311
- throw new HTTPException6(422, {
2312
- message: `skill library entry is unavailable: ${libraryId}@${normalizedVersion}`
2313
- });
2314
- }
2315
- installationConfig = { version: entry.version };
2316
- installationMetadata = {
2317
- libraryId: entry.id,
2318
- libraryVersion: entry.version,
2319
- contentSha256: entry.contentSha256,
2320
- sourceCommit: entry.sourceCommit,
2321
- provenance: entry.provenance
2322
- };
2323
- }
2324
2316
  if (item.kind === "mcp") {
2325
2317
  const headers = await resolveMcpCredentialHeaders(input, item);
2326
2318
  const connectionRef = input.payload.connectionRef ? await validateMcpCapabilityConnectionRef(input, item, input.payload.connectionRef) : null;
@@ -2339,71 +2331,6 @@ async function enableCapability(input) {
2339
2331
  );
2340
2332
  }
2341
2333
  }
2342
- if (item.kind === "pack") {
2343
- const packId = packIdFromCapabilityId(item.id);
2344
- const pack = await resolveCapabilityPack(input.db, input.workspaceId, packId);
2345
- if (!pack) {
2346
- throw new HTTPException6(404, { message: "pack not found" });
2347
- }
2348
- if (capabilityPackRequiresInstallationPlan(pack)) {
2349
- throw new HTTPException6(409, {
2350
- message: "This Pack uses component or Rig requirements. Preview and install it through the Pack installation flow."
2351
- });
2352
- }
2353
- await assertPackSandboxImageCompatible(input.db, input.workspaceId, pack);
2354
- const existing = await getPackInstallation2(input.db, input.workspaceId, packId);
2355
- const storedVariableSetId2 = typeof existing?.metadata.variableSetId === "string" ? existing.metadata.variableSetId : typeof existing?.metadata.environmentId === "string" ? existing.metadata.environmentId : void 0;
2356
- const requestedVariableSetId = input.payload.variableSetId;
2357
- const variableSetId = requestedVariableSetId ?? storedVariableSetId2;
2358
- if (pack.variableSet?.required && !variableSetId) {
2359
- throw new HTTPException6(422, {
2360
- message: `pack ${packId} requires an variableSet attachment; pass variableSetId`
2361
- });
2362
- }
2363
- if (variableSetId) {
2364
- if (requestedVariableSetId) {
2365
- const variableSet = await validateVariableSetAttachment(
2366
- { settings: input.settings, db: input.db },
2367
- input.grant,
2368
- input.workspaceId,
2369
- requestedVariableSetId
2370
- );
2371
- const missing = (pack.variableSet?.requiredVariables ?? []).filter(
2372
- (name) => !variableSet.variables.some((variable) => variable.name === name)
2373
- );
2374
- if (missing.length > 0) {
2375
- throw new HTTPException6(422, {
2376
- message: `variable set is missing required variable(s): ${missing.join(", ")}`
2377
- });
2378
- }
2379
- } else {
2380
- const variableSet = await getVariableSet3(input.db, input.workspaceId, variableSetId);
2381
- if (!variableSet) {
2382
- throw new HTTPException6(422, {
2383
- message: `the stored variableSet attachment for pack ${packId} no longer exists; re-enable it with variableSetId`
2384
- });
2385
- }
2386
- const missing = (pack.variableSet?.requiredVariables ?? []).filter(
2387
- (name) => !variableSet.variables.some((variable) => variable.name === name)
2388
- );
2389
- if (missing.length > 0) {
2390
- throw new HTTPException6(422, {
2391
- message: `variable set is missing required variable(s): ${missing.join(", ")}`
2392
- });
2393
- }
2394
- }
2395
- }
2396
- await enablePackInstallation(input.db, {
2397
- accountId: input.accountId,
2398
- workspaceId: input.workspaceId,
2399
- packId,
2400
- metadata: {
2401
- ...input.payload.metadata,
2402
- packVersion: pack.version,
2403
- ...variableSetId ? { variableSetId } : {}
2404
- }
2405
- });
2406
- }
2407
2334
  return await enableCapabilityInstallation(input.db, {
2408
2335
  accountId: input.accountId,
2409
2336
  workspaceId: input.workspaceId,
@@ -2436,7 +2363,7 @@ async function resolveMcpCredentialHeaders(input, item) {
2436
2363
  ])
2437
2364
  );
2438
2365
  } catch {
2439
- throw new HTTPException6(422, {
2366
+ throw new HTTPException4(422, {
2440
2367
  message: `stored credential headers for "${item.name}" could not be decrypted; supply them again in the enable request "headers" field`
2441
2368
  });
2442
2369
  }
@@ -2447,31 +2374,31 @@ function normalizedMcpCredentialHeaders(headers) {
2447
2374
  return null;
2448
2375
  }
2449
2376
  if (entries.length > maxMcpCredentialHeaders) {
2450
- throw new HTTPException6(422, {
2377
+ throw new HTTPException4(422, {
2451
2378
  message: `an MCP capability supports at most ${maxMcpCredentialHeaders} credential headers`
2452
2379
  });
2453
2380
  }
2454
2381
  const seen = /* @__PURE__ */ new Set();
2455
2382
  for (const [name, value] of entries) {
2456
2383
  if (!mcpCredentialHeaderName.test(name)) {
2457
- throw new HTTPException6(422, {
2384
+ throw new HTTPException4(422, {
2458
2385
  message: `invalid credential header name: ${name}`
2459
2386
  });
2460
2387
  }
2461
2388
  const lower = name.toLowerCase();
2462
2389
  if (seen.has(lower)) {
2463
- throw new HTTPException6(422, {
2390
+ throw new HTTPException4(422, {
2464
2391
  message: `duplicate credential header name: ${name}`
2465
2392
  });
2466
2393
  }
2467
2394
  seen.add(lower);
2468
2395
  if (value.length === 0 || value.length > maxMcpCredentialHeaderValueLength) {
2469
- throw new HTTPException6(422, {
2396
+ throw new HTTPException4(422, {
2470
2397
  message: `credential header ${name} must be 1-${maxMcpCredentialHeaderValueLength} characters`
2471
2398
  });
2472
2399
  }
2473
2400
  if (/[\u0000-\u0008\u000A-\u001F\u007F]/.test(value)) {
2474
- throw new HTTPException6(422, {
2401
+ throw new HTTPException4(422, {
2475
2402
  message: `credential header ${name} contains forbidden control characters`
2476
2403
  });
2477
2404
  }
@@ -2482,7 +2409,7 @@ async function validateMcpCapabilityConnectionRef(input, item, ref) {
2482
2409
  const subjectScope = ref.subjectScope ?? "workspace";
2483
2410
  const personalOnly = item.metadata.connectionOwnership === "personal_only" || item.endpointUrl?.replace(/\/+$/, "") === officialGmailMcpUrl;
2484
2411
  if (personalOnly && subjectScope !== "subject") {
2485
- throw new HTTPException6(422, {
2412
+ throw new HTTPException4(422, {
2486
2413
  message: "this capability requires a personal connection; each workspace member must connect their own account"
2487
2414
  });
2488
2415
  }
@@ -2501,12 +2428,12 @@ async function validateMcpCapabilityConnectionRef(input, item, ref) {
2501
2428
  } : {}
2502
2429
  };
2503
2430
  if (!normalized.providerDomain) {
2504
- throw new HTTPException6(422, {
2431
+ throw new HTTPException4(422, {
2505
2432
  message: "connectionRef.providerDomain is required"
2506
2433
  });
2507
2434
  }
2508
2435
  if (!item.endpointUrl || !item.runtime.mcpServerId) {
2509
- throw new HTTPException6(422, {
2436
+ throw new HTTPException4(422, {
2510
2437
  message: "MCP capabilities need a remote streamable HTTP endpoint before they can use a connectionRef"
2511
2438
  });
2512
2439
  }
@@ -2527,27 +2454,27 @@ async function validateMcpCapabilityConnectionRef(input, item, ref) {
2527
2454
  ) ?? null;
2528
2455
  }
2529
2456
  if (!connection) {
2530
- throw new HTTPException6(422, {
2457
+ throw new HTTPException4(422, {
2531
2458
  message: "connectionRef does not reference a visible active connection"
2532
2459
  });
2533
2460
  }
2534
2461
  if (subjectScope === "subject" && connection.subjectId !== input.grant.subjectId || subjectScope === "workspace" && connection.subjectId !== null) {
2535
- throw new HTTPException6(422, {
2462
+ throw new HTTPException4(422, {
2536
2463
  message: `connectionRef does not reference a ${subjectScope}-owned connection`
2537
2464
  });
2538
2465
  }
2539
2466
  if (connection.status !== "active") {
2540
- throw new HTTPException6(422, {
2467
+ throw new HTTPException4(422, {
2541
2468
  message: `connectionRef.connectionId is not active (${connection.status})`
2542
2469
  });
2543
2470
  }
2544
2471
  if (connection.providerDomain !== normalized.providerDomain) {
2545
- throw new HTTPException6(422, {
2472
+ throw new HTTPException4(422, {
2546
2473
  message: "connectionRef.providerDomain does not match the referenced connection"
2547
2474
  });
2548
2475
  }
2549
2476
  if (normalized.kind && connection.kind !== normalized.kind) {
2550
- throw new HTTPException6(422, {
2477
+ throw new HTTPException4(422, {
2551
2478
  message: "connectionRef.kind does not match the referenced connection"
2552
2479
  });
2553
2480
  }
@@ -2574,12 +2501,12 @@ function assertRequiredMcpCredentialHeaders(item, headers, connectionRef) {
2574
2501
  const names = new Set(Object.keys(headers ?? {}).map((name) => name.toLowerCase()));
2575
2502
  const missing = required.filter((name) => !names.has(name.toLowerCase()));
2576
2503
  if (missing.length > 0) {
2577
- throw new HTTPException6(422, {
2504
+ throw new HTTPException4(422, {
2578
2505
  message: `MCP capability "${item.name}" requires credential header(s) ${missing.join(", ")}; pass them in the enable request "headers" field`
2579
2506
  });
2580
2507
  }
2581
2508
  if (item.authModel && names.size === 0) {
2582
- throw new HTTPException6(422, {
2509
+ throw new HTTPException4(422, {
2583
2510
  message: `MCP capability "${item.name}" requires credentials; pass them in the enable request "headers" field`
2584
2511
  });
2585
2512
  }
@@ -2592,9 +2519,9 @@ function requiredCapabilityHeaders(metadata) {
2592
2519
  return value.filter((name) => typeof name === "string" && name.trim().length > 0).map((name) => name.trim());
2593
2520
  }
2594
2521
  function requireCapabilityHeaderEncryption(settings) {
2595
- const key = environmentsEncryptionKeyBytes2(settings);
2522
+ const key = environmentsEncryptionKeyBytes(settings);
2596
2523
  if (!key) {
2597
- throw new HTTPException6(503, {
2524
+ throw new HTTPException4(503, {
2598
2525
  message: "MCP credential headers require OPENGENI_ENVIRONMENTS_ENCRYPTION_KEY"
2599
2526
  });
2600
2527
  }
@@ -2605,7 +2532,7 @@ async function validateMcpCapabilityConnection(item, probe = probeStreamableHttp
2605
2532
  return {};
2606
2533
  }
2607
2534
  if (!item.endpointUrl || !item.runtime.mcpServerId) {
2608
- throw new HTTPException6(422, {
2535
+ throw new HTTPException4(422, {
2609
2536
  message: "MCP capabilities need a remote streamable HTTP endpoint before they can be enabled"
2610
2537
  });
2611
2538
  }
@@ -2625,7 +2552,7 @@ async function validateMcpCapabilityConnection(item, probe = probeStreamableHttp
2625
2552
  }
2626
2553
  };
2627
2554
  } catch (error) {
2628
- throw new HTTPException6(422, {
2555
+ throw new HTTPException4(422, {
2629
2556
  message: `MCP capability "${item.name}" could not be enabled because ${mcpProbeErrorMessage(error, item.endpointUrl)}`
2630
2557
  });
2631
2558
  }
@@ -2683,40 +2610,33 @@ async function disableCapability(input) {
2683
2610
  input.settings,
2684
2611
  input.capabilityId
2685
2612
  );
2686
- if ((item.source === "built_in" || item.source === "configured") && item.kind !== "pack") {
2687
- throw new HTTPException6(409, {
2688
- message: "built-in and configured capabilities are always available; remove them from configuration to disable them"
2613
+ if (item.kind === "skill") {
2614
+ throw new HTTPException4(409, {
2615
+ message: "Uninstall Skills through the Skill uninstall preview flow"
2616
+ });
2617
+ }
2618
+ if (item.kind === "api") {
2619
+ throw new HTTPException4(409, {
2620
+ message: "Remove API Integrations through the Integration instance flow"
2621
+ });
2622
+ }
2623
+ if (item.kind === "plugin") {
2624
+ throw new HTTPException4(409, {
2625
+ message: "Remove Plugins through the Plugin Package flow"
2689
2626
  });
2690
2627
  }
2691
2628
  if (item.kind === "pack") {
2692
- const packInstallation = await getPackInstallation2(
2693
- input.db,
2694
- input.workspaceId,
2695
- packIdFromCapabilityId(item.id)
2696
- );
2697
- if (packInstallation && (packInstallation.manifestDigest !== null || packInstallation.manifestSnapshot !== null)) {
2698
- throw new HTTPException6(409, {
2699
- message: "This Pack owns explicit components. Uninstall it through the Pack uninstall preview flow."
2700
- });
2701
- }
2702
- await updatePackInstallationStatus(
2703
- input.db,
2704
- input.workspaceId,
2705
- packIdFromCapabilityId(item.id),
2706
- "disabled"
2707
- ).catch(() => void 0);
2708
- if (!await getCapabilityInstallation(input.db, input.workspaceId, item.id)) {
2709
- await enableCapabilityInstallation(input.db, {
2710
- accountId: input.accountId,
2711
- workspaceId: input.workspaceId,
2712
- capabilityId: item.id,
2713
- kind: "pack",
2714
- metadata: {},
2715
- config: {}
2716
- });
2717
- }
2718
- } else if (!await getCapabilityInstallation(input.db, input.workspaceId, item.id)) {
2719
- throw new HTTPException6(409, {
2629
+ throw new HTTPException4(409, {
2630
+ message: "Uninstall Packs through the Pack uninstall preview flow"
2631
+ });
2632
+ }
2633
+ if (item.source === "built_in" || item.source === "configured") {
2634
+ throw new HTTPException4(409, {
2635
+ message: "built-in and configured capabilities are always available; remove them from configuration to disable them"
2636
+ });
2637
+ }
2638
+ if (!await getCapabilityInstallation(input.db, input.workspaceId, item.id)) {
2639
+ throw new HTTPException4(409, {
2720
2640
  message: "capability is not currently enabled"
2721
2641
  });
2722
2642
  }
@@ -2836,7 +2756,7 @@ function settingsWithMcpCapabilityServers(settings, enabled) {
2836
2756
  if (enabled.length === 0) {
2837
2757
  return settings;
2838
2758
  }
2839
- const encryptionKey = environmentsEncryptionKeyBytes2(settings);
2759
+ const encryptionKey = environmentsEncryptionKeyBytes(settings);
2840
2760
  const existingIds = new Set(settings.mcpServers.map((server) => server.id));
2841
2761
  const dynamicServers = enabled.filter((server) => !existingIds.has(server.id)).flatMap((server) => {
2842
2762
  const headers = decryptedCapabilityHeaders(server, encryptionKey);
@@ -2916,21 +2836,21 @@ async function fetchMcpRegistryPage(url, options = {}) {
2916
2836
  try {
2917
2837
  const response = await fetchImpl(url, { signal: controller.signal });
2918
2838
  if (!response.ok) {
2919
- throw new HTTPException6(502, {
2839
+ throw new HTTPException4(502, {
2920
2840
  message: `MCP registry returned ${response.status}`
2921
2841
  });
2922
2842
  }
2923
2843
  return await response.json();
2924
2844
  } catch (error) {
2925
- if (error instanceof HTTPException6) {
2845
+ if (error instanceof HTTPException4) {
2926
2846
  throw error;
2927
2847
  }
2928
2848
  if (error instanceof Error && error.name === "AbortError") {
2929
- throw new HTTPException6(504, {
2849
+ throw new HTTPException4(504, {
2930
2850
  message: "MCP registry request timed out"
2931
2851
  });
2932
2852
  }
2933
- throw new HTTPException6(502, {
2853
+ throw new HTTPException4(502, {
2934
2854
  message: `MCP registry request failed: ${error instanceof Error ? error.message : String(error)}`
2935
2855
  });
2936
2856
  } finally {
@@ -2941,7 +2861,7 @@ async function requireCatalogItem(db, workspaceId, settings, capabilityId) {
2941
2861
  const catalog = await buildCapabilityCatalog({ db, workspaceId, settings });
2942
2862
  const item = catalog.items.find((candidate) => candidate.id === capabilityId) ?? await getCapabilityCatalogItem(db, workspaceId, capabilityId);
2943
2863
  if (!item) {
2944
- throw new HTTPException6(404, { message: "capability not found" });
2864
+ throw new HTTPException4(404, { message: "capability not found" });
2945
2865
  }
2946
2866
  return item;
2947
2867
  }
@@ -3083,6 +3003,39 @@ var SOCIAL_PROVIDER_TOOL_NAMES = {
3083
3003
  "reddit_post_reply"
3084
3004
  ]
3085
3005
  };
3006
+ function fikenCatalogItem(fikenConnections) {
3007
+ const fikenConnection = preferredFikenConnection(fikenConnections);
3008
+ const fikenEnabled = fikenConnection?.status === "active" || fikenConnection?.status === "needs_reauth";
3009
+ return CapabilityCatalogItem.parse({
3010
+ id: "api:fiken",
3011
+ kind: "api",
3012
+ source: "built_in",
3013
+ name: "Fiken",
3014
+ description: "Connect Fiken accounting for contacts, products, invoices, invoice drafts, purchases, sales, and bank accounts.",
3015
+ category: "finance",
3016
+ tags: ["api", "fiken", "accounting", "invoicing", "norway"],
3017
+ homepageUrl: "https://fiken.no",
3018
+ authModel: "personal_api_token",
3019
+ providerDomain: FIKEN_PROVIDER_DOMAIN2,
3020
+ surfaceType: "first_party_fiken",
3021
+ authKind: "api_key",
3022
+ tools: [{ kind: "mcp", id: "opengeni" }],
3023
+ runtime: {
3024
+ available: true,
3025
+ mcpServerId: "opengeni",
3026
+ notes: "Fiken access is provided through OpenGeni's first-party fiken tools."
3027
+ },
3028
+ enabled: fikenEnabled,
3029
+ enabledReason: fikenEnabled ? fikenConnection.status === "active" ? "workspace Fiken connection active" : "workspace Fiken connection needs reconnection" : null,
3030
+ metadata: {
3031
+ connectorMode: "first_party_fiken",
3032
+ ownership: "workspace",
3033
+ // Derived from the contracts catalog so a new fiken_* tool cannot be
3034
+ // registered without also appearing on the capability tile.
3035
+ firstPartyMcpTools: FIRST_PARTY_MCP_TOOL_NAMES.filter((name) => name.startsWith("fiken_"))
3036
+ }
3037
+ });
3038
+ }
3086
3039
  function providerIntegrationCatalogItems(socialConnections) {
3087
3040
  return SOCIAL_PROVIDER_INTEGRATIONS.map((definition) => {
3088
3041
  const counts = socialConnectionCounts(socialConnections, definition.provider);
@@ -3212,6 +3165,51 @@ function curatedSkillCatalogItem(entry) {
3212
3165
  }
3213
3166
  });
3214
3167
  }
3168
+ function installedSkillCatalogItem(skill) {
3169
+ return CapabilityCatalogItem.parse({
3170
+ id: skill.capabilityId,
3171
+ kind: "skill",
3172
+ source: skill.source === "library" ? "library" : "manual",
3173
+ name: skill.name,
3174
+ description: skill.description,
3175
+ category: skill.category,
3176
+ tags: skill.tags,
3177
+ homepageUrl: skill.repositoryUrl,
3178
+ installUrl: skill.sourceUrl,
3179
+ provenance: skill.provenance,
3180
+ tier: skill.source === "library" ? "verified" : "community",
3181
+ runtime: {
3182
+ available: true,
3183
+ notes: "Available from an immutable Skill installation."
3184
+ },
3185
+ metadata: {
3186
+ version: skill.version,
3187
+ contentSha256: skill.contentSha256,
3188
+ sourceCommit: skill.sourceCommit,
3189
+ sourcePath: skill.sourcePath,
3190
+ sourceUrl: skill.sourceUrl,
3191
+ repositoryUrl: skill.repositoryUrl,
3192
+ provenance: skill.provenance,
3193
+ license: skill.license,
3194
+ installedSkill: installedSkillMetadata(skill)
3195
+ }
3196
+ });
3197
+ }
3198
+ function installedSkillMetadata(skill) {
3199
+ return {
3200
+ pluginKey: skill.pluginKey,
3201
+ installationVersion: skill.installationVersion,
3202
+ source: skill.source,
3203
+ version: skill.version,
3204
+ sourceCommit: skill.sourceCommit,
3205
+ contentSha256: skill.contentSha256,
3206
+ fileCount: skill.fileCount,
3207
+ totalBytes: skill.totalBytes,
3208
+ installedAt: skill.installedAt,
3209
+ updatedAt: skill.updatedAt,
3210
+ owners: skill.owners.map((owner) => ({ ...owner }))
3211
+ };
3212
+ }
3215
3213
  function stringMetadata(value) {
3216
3214
  return typeof value === "string" && value.trim().length > 0 ? value.trim() : null;
3217
3215
  }
@@ -3233,8 +3231,11 @@ function socialProviderConnectionCounts(item) {
3233
3231
  };
3234
3232
  }
3235
3233
  function applyCapabilityEnablement(item, installation, activePackIds) {
3234
+ if (item.kind === "skill" || item.kind === "api" || item.kind === "plugin") {
3235
+ return { ...item, enabled: false, enabledReason: null, connectionRef: null };
3236
+ }
3236
3237
  if (item.kind === "pack") {
3237
- const enabled2 = activePackIds.has(packIdFromCapabilityId(item.id)) || installation?.status === "active";
3238
+ const enabled2 = activePackIds.has(packIdFromCapabilityId(item.id));
3238
3239
  return {
3239
3240
  ...item,
3240
3241
  enabled: enabled2,
@@ -3244,6 +3245,9 @@ function applyCapabilityEnablement(item, installation, activePackIds) {
3244
3245
  if (isSocialProviderIntegration(item)) {
3245
3246
  return { ...item, connectionRef: null };
3246
3247
  }
3248
+ if (item.surfaceType === "first_party_fiken") {
3249
+ return { ...item, connectionRef: null };
3250
+ }
3247
3251
  if (item.surfaceType === "codex_apps") {
3248
3252
  return { ...item, connectionRef: null };
3249
3253
  }
@@ -3258,33 +3262,8 @@ function applyCapabilityEnablement(item, installation, activePackIds) {
3258
3262
  if (item.source === "built_in") {
3259
3263
  return { ...item, connectionRef: null };
3260
3264
  }
3261
- if (item.source === "library") {
3262
- const enabled2 = installation?.status === "active" && skillLibraryInstallationRuntimeReady(item, installation);
3263
- return {
3264
- ...item,
3265
- enabled: enabled2,
3266
- enabledReason: enabled2 ? "explicitly selected" : null,
3267
- connectionRef: null
3268
- };
3269
- }
3270
3265
  const activeInstallation = installation?.status === "active";
3271
3266
  const enabled = !!activeInstallation && capabilityInstallationRuntimeReady(item, installation);
3272
- if (item.kind === "api" && item.metadata.platformVersion === 2) {
3273
- const connectionBound = installation?.metadata.connectionBound === true;
3274
- const providerDomain = stringMetadata(installation?.metadata.providerDomain);
3275
- const connectionKind = stringMetadata(installation?.metadata.connectionKind);
3276
- const connectionOwnership = stringMetadata(installation?.metadata.connectionOwnership);
3277
- return {
3278
- ...item,
3279
- enabled,
3280
- enabledReason: enabled ? "installed immutable Integration revision" : null,
3281
- connectionRef: enabled && connectionBound && providerDomain && connectionKind ? {
3282
- providerDomain,
3283
- kind: connectionKind,
3284
- ...connectionOwnership === "subject" ? { subjectScope: "subject" } : {}
3285
- } : null
3286
- };
3287
- }
3288
3267
  return {
3289
3268
  ...item,
3290
3269
  enabled,
@@ -3357,23 +3336,23 @@ function applyCapabilityLifecycle(item) {
3357
3336
  actions
3358
3337
  };
3359
3338
  }
3360
- function skillLibraryInstallationRuntimeReady(item, installation) {
3361
- if (item.kind !== "skill" || item.source !== "library" || installation.kind !== "skill") {
3362
- return false;
3363
- }
3364
- const libraryId = stringMetadata(item.metadata.libraryId);
3365
- const version = stringMetadata(item.metadata.version);
3366
- const contentSha256 = stringMetadata(item.metadata.contentSha256);
3367
- const sourceCommit = stringMetadata(item.metadata.sourceCommit);
3368
- const provenance = stringMetadata(item.metadata.provenance);
3369
- if (!libraryId || !version || !contentSha256 || !sourceCommit || !provenance) {
3370
- return false;
3339
+ function applyInstalledSkillEnablement(item, installation) {
3340
+ if (!installation) {
3341
+ return { ...item, enabled: false, enabledReason: null, connectionRef: null };
3371
3342
  }
3372
- const entry = getSkillLibraryEntry(libraryId, version);
3373
- if (!entry || item.id !== `skill:${entry.id}` || installation.capabilityId !== `skill:${entry.id}` || contentSha256 !== entry.contentSha256 || sourceCommit !== entry.sourceCommit || provenance !== entry.provenance) {
3374
- return false;
3375
- }
3376
- return installation.config.version === entry.version && stringMetadata(installation.metadata.libraryId) === entry.id && stringMetadata(installation.metadata.libraryVersion) === entry.version && stringMetadata(installation.metadata.contentSha256) === entry.contentSha256 && stringMetadata(installation.metadata.sourceCommit) === entry.sourceCommit && stringMetadata(installation.metadata.provenance) === entry.provenance;
3343
+ const catalogVersion = stringMetadata(item.metadata.version);
3344
+ const current = catalogVersion === null || catalogVersion === installation.version && stringMetadata(item.metadata.contentSha256) === installation.contentSha256 && stringMetadata(item.metadata.sourceCommit) === installation.sourceCommit;
3345
+ return {
3346
+ ...item,
3347
+ enabled: true,
3348
+ enabledReason: current ? "explicitly installed" : `version ${installation.version} installed; update available`,
3349
+ connectionRef: null,
3350
+ metadata: {
3351
+ ...item.metadata,
3352
+ installedSkill: installedSkillMetadata(installation),
3353
+ updateAvailable: !current
3354
+ }
3355
+ };
3377
3356
  }
3378
3357
  function installationConnectionRef(config) {
3379
3358
  const ref = config.connectionRef;
@@ -3611,7 +3590,7 @@ import {
3611
3590
  PORTABLE_SKILL_MAX_FILES,
3612
3591
  PORTABLE_SKILL_MAX_TOTAL_BYTES
3613
3592
  } from "@opengeni/runtime/skill-library";
3614
- import { HTTPException as HTTPException7 } from "hono/http-exception";
3593
+ import { HTTPException as HTTPException5 } from "hono/http-exception";
3615
3594
  var githubSegment = /^[A-Za-z0-9](?:[A-Za-z0-9._-]{0,98}[A-Za-z0-9])?$/u;
3616
3595
  var gitCommit = /^[0-9a-f]{40,64}$/u;
3617
3596
  var maxConcurrentBlobReads = 8;
@@ -3619,19 +3598,19 @@ async function resolveSkillImport(rawUrl, client) {
3619
3598
  const parsed = parseSkillSource(rawUrl);
3620
3599
  const sourceCommit = await client.resolveCommit(parsed.owner, parsed.repository, parsed.ref);
3621
3600
  if (!gitCommit.test(sourceCommit)) {
3622
- throw new HTTPException7(422, { message: "GitHub returned an invalid source commit" });
3601
+ throw new HTTPException5(422, { message: "GitHub returned an invalid source commit" });
3623
3602
  }
3624
3603
  const tree = await client.listTree(parsed.owner, parsed.repository, sourceCommit);
3625
3604
  const sourcePath = selectSkillRoot(parsed, tree);
3626
3605
  const entries = skillFilesUnderRoot(tree, sourcePath);
3627
3606
  const declaredBytes = entries.reduce((sum, entry) => sum + (entry.size ?? 0), 0);
3628
3607
  if (entries.length > PORTABLE_SKILL_MAX_FILES) {
3629
- throw new HTTPException7(422, {
3608
+ throw new HTTPException5(422, {
3630
3609
  message: `Skill contains more than ${PORTABLE_SKILL_MAX_FILES} files`
3631
3610
  });
3632
3611
  }
3633
3612
  if (declaredBytes > PORTABLE_SKILL_MAX_TOTAL_BYTES) {
3634
- throw new HTTPException7(422, {
3613
+ throw new HTTPException5(422, {
3635
3614
  message: `Skill exceeds ${PORTABLE_SKILL_MAX_TOTAL_BYTES} bytes`
3636
3615
  });
3637
3616
  }
@@ -3642,7 +3621,7 @@ async function resolveSkillImport(rawUrl, client) {
3642
3621
  await client.readBlob(parsed.owner, parsed.repository, entry.sha)
3643
3622
  );
3644
3623
  } catch {
3645
- throw new HTTPException7(422, {
3624
+ throw new HTTPException5(422, {
3646
3625
  message: `Skill file is not valid UTF-8 text: ${relativeSkillPath(entry.path, sourcePath)}`
3647
3626
  });
3648
3627
  }
@@ -3652,7 +3631,7 @@ async function resolveSkillImport(rawUrl, client) {
3652
3631
  try {
3653
3632
  artifact = buildPortableSkillArtifact2(files);
3654
3633
  } catch (error) {
3655
- throw new HTTPException7(422, {
3634
+ throw new HTTPException5(422, {
3656
3635
  message: error instanceof Error ? error.message : "Skill artifact is invalid"
3657
3636
  });
3658
3637
  }
@@ -3709,24 +3688,24 @@ function parseSkillSource(rawUrl) {
3709
3688
  try {
3710
3689
  url = new URL(rawUrl);
3711
3690
  } catch {
3712
- throw new HTTPException7(422, { message: "Enter a valid GitHub or skills.sh URL" });
3691
+ throw new HTTPException5(422, { message: "Enter a valid GitHub or skills.sh URL" });
3713
3692
  }
3714
3693
  if (url.protocol !== "https:" || url.username || url.password || url.hash) {
3715
- throw new HTTPException7(422, {
3694
+ throw new HTTPException5(422, {
3716
3695
  message: "Skill imports require a credential-free HTTPS URL without a fragment"
3717
3696
  });
3718
3697
  }
3719
3698
  const segments = url.pathname.split("/").filter(Boolean).map(decodeURIComponent);
3720
3699
  if (url.hostname === "skills.sh" || url.hostname === "www.skills.sh") {
3721
3700
  if (segments.length !== 3) {
3722
- throw new HTTPException7(422, {
3701
+ throw new HTTPException5(422, {
3723
3702
  message: "A skills.sh URL must identify one owner, repository, and Skill"
3724
3703
  });
3725
3704
  }
3726
3705
  const [owner2, repository2, skillSlug] = segments;
3727
3706
  assertGitHubRepository(owner2, repository2);
3728
3707
  if (!skillSlug || !githubSegment.test(skillSlug)) {
3729
- throw new HTTPException7(422, { message: "The skills.sh Skill name is invalid" });
3708
+ throw new HTTPException5(422, { message: "The skills.sh Skill name is invalid" });
3730
3709
  }
3731
3710
  return {
3732
3711
  source: "skills_sh",
@@ -3739,12 +3718,12 @@ function parseSkillSource(rawUrl) {
3739
3718
  };
3740
3719
  }
3741
3720
  if (url.hostname !== "github.com" && url.hostname !== "www.github.com") {
3742
- throw new HTTPException7(422, {
3721
+ throw new HTTPException5(422, {
3743
3722
  message: "Only github.com and skills.sh imports are supported"
3744
3723
  });
3745
3724
  }
3746
3725
  if (segments.length < 2) {
3747
- throw new HTTPException7(422, { message: "A GitHub URL must identify a repository" });
3726
+ throw new HTTPException5(422, { message: "A GitHub URL must identify a repository" });
3748
3727
  }
3749
3728
  const owner = segments[0];
3750
3729
  const repository = stripGitSuffix(segments[1]);
@@ -3762,15 +3741,15 @@ function parseSkillSource(rawUrl) {
3762
3741
  }
3763
3742
  const mode = segments[2];
3764
3743
  if (mode !== "tree" && mode !== "blob") {
3765
- throw new HTTPException7(422, {
3744
+ throw new HTTPException5(422, {
3766
3745
  message: "Use a GitHub repository, tree, folder, or SKILL.md URL"
3767
3746
  });
3768
3747
  }
3769
3748
  const ref = segments[3];
3770
- if (!ref) throw new HTTPException7(422, { message: "The GitHub URL is missing a revision" });
3749
+ if (!ref) throw new HTTPException5(422, { message: "The GitHub URL is missing a revision" });
3771
3750
  const pathSegments = segments.slice(4);
3772
3751
  if (pathSegments.length === 0) {
3773
- throw new HTTPException7(422, { message: "The GitHub URL is missing a Skill folder" });
3752
+ throw new HTTPException5(422, { message: "The GitHub URL is missing a Skill folder" });
3774
3753
  }
3775
3754
  const requestedPath = normalizeGitHubPath(
3776
3755
  mode === "blob" && pathSegments.at(-1)?.toLowerCase() === "skill.md" ? pathSegments.slice(0, -1) : pathSegments
@@ -3792,7 +3771,7 @@ function selectSkillRoot(source, tree) {
3792
3771
  if (source.requestedPath) {
3793
3772
  const root = source.requestedPath;
3794
3773
  if (!skillFiles.includes(root)) {
3795
- throw new HTTPException7(422, {
3774
+ throw new HTTPException5(422, {
3796
3775
  message: `No top-level SKILL.md was found in ${root}`
3797
3776
  });
3798
3777
  }
@@ -3800,10 +3779,10 @@ function selectSkillRoot(source, tree) {
3800
3779
  }
3801
3780
  const candidates = source.skillSlug ? skillFiles.filter((path) => path.split("/").at(-1) === source.skillSlug) : skillFiles;
3802
3781
  if (candidates.length === 0) {
3803
- throw new HTTPException7(422, { message: "No Skill folder with SKILL.md was found" });
3782
+ throw new HTTPException5(422, { message: "No Skill folder with SKILL.md was found" });
3804
3783
  }
3805
3784
  if (candidates.length > 1) {
3806
- throw new HTTPException7(422, {
3785
+ throw new HTTPException5(422, {
3807
3786
  message: "This source contains multiple Skills; paste the exact GitHub folder URL"
3808
3787
  });
3809
3788
  }
@@ -3816,13 +3795,13 @@ function skillFilesUnderRoot(tree, root) {
3816
3795
  (entry) => entry.type === "commit" || entry.type === "blob" && entry.mode === "120000"
3817
3796
  );
3818
3797
  if (unsupported) {
3819
- throw new HTTPException7(422, {
3798
+ throw new HTTPException5(422, {
3820
3799
  message: `Skill folders may not contain symbolic links or submodules (${unsupported.path})`
3821
3800
  });
3822
3801
  }
3823
3802
  const files = inside.filter((entry) => entry.type === "blob").sort((left, right) => left.path.localeCompare(right.path));
3824
3803
  if (files.length === 0) {
3825
- throw new HTTPException7(422, { message: "The selected Skill folder is empty" });
3804
+ throw new HTTPException5(422, { message: "The selected Skill folder is empty" });
3826
3805
  }
3827
3806
  return files;
3828
3807
  }
@@ -3833,7 +3812,7 @@ function normalizeGitHubPath(segments) {
3833
3812
  if (segments.length === 0 || segments.some(
3834
3813
  (segment) => segment.length === 0 || segment === "." || segment === ".." || segment.includes("\\") || /[\u0000-\u001f\u007f]/u.test(segment)
3835
3814
  )) {
3836
- throw new HTTPException7(422, { message: "The GitHub Skill path is invalid" });
3815
+ throw new HTTPException5(422, { message: "The GitHub Skill path is invalid" });
3837
3816
  }
3838
3817
  return segments.join("/");
3839
3818
  }
@@ -3845,7 +3824,7 @@ function stripGitSuffix(value) {
3845
3824
  }
3846
3825
  function assertGitHubRepository(owner, repository) {
3847
3826
  if (!githubSegment.test(owner) || !githubSegment.test(stripGitSuffix(repository))) {
3848
- throw new HTTPException7(422, { message: "The GitHub owner or repository name is invalid" });
3827
+ throw new HTTPException5(422, { message: "The GitHub owner or repository name is invalid" });
3849
3828
  }
3850
3829
  }
3851
3830
  async function mapConcurrent(values, concurrency, map) {
@@ -3867,6 +3846,93 @@ function sha256Hex2(bytes) {
3867
3846
  return createHash2("sha256").update(bytes).digest("hex");
3868
3847
  }
3869
3848
 
3849
+ // src/domain/environments.ts
3850
+ import { environmentsEncryptionKeyBytes as environmentsEncryptionKeyBytes2 } from "@opengeni/config";
3851
+ import { getVariableSet as getVariableSet2, recordAuditEvent } from "@opengeni/db";
3852
+ import { HTTPException as HTTPException6 } from "hono/http-exception";
3853
+ var MAX_ENVIRONMENTS_PER_WORKSPACE = 25;
3854
+ var MAX_VARIABLES_PER_ENVIRONMENT = 100;
3855
+ var reservedExactNames = /* @__PURE__ */ new Set([
3856
+ "HOME",
3857
+ "PATH",
3858
+ "SHELL",
3859
+ "USER",
3860
+ "LOGNAME",
3861
+ "TMPDIR",
3862
+ "IFS",
3863
+ "ENV",
3864
+ "BASH_ENV",
3865
+ "NODE_OPTIONS",
3866
+ "PYTHONPATH",
3867
+ "PYTHONSTARTUP",
3868
+ "PERL5OPT",
3869
+ "PERL5LIB",
3870
+ "GH_TOKEN",
3871
+ "GITHUB_TOKEN",
3872
+ "GITLAB_TOKEN",
3873
+ "AZURE_DEVOPS_EXT_PAT",
3874
+ "GIT_ASKPASS",
3875
+ "GIT_TERMINAL_PROMPT"
3876
+ ]);
3877
+ var reservedPrefixes = [
3878
+ "OPENGENI_",
3879
+ "GIT_CONFIG_",
3880
+ "GIT_AUTHOR_",
3881
+ "GIT_COMMITTER_",
3882
+ "LD_",
3883
+ "DYLD_"
3884
+ ];
3885
+ function assertAllowedVariableSetVariableName(name) {
3886
+ if (reservedExactNames.has(name) || reservedPrefixes.some((prefix) => name.startsWith(prefix))) {
3887
+ throw new HTTPException6(422, {
3888
+ message: `reserved variable set variable name / reserved environment variable name: ${name}`
3889
+ });
3890
+ }
3891
+ }
3892
+ var assertAllowedEnvironmentVariableName = assertAllowedVariableSetVariableName;
3893
+ function requireVariableSetEncryption(settings) {
3894
+ const key = environmentsEncryptionKeyBytes2(settings);
3895
+ if (!key) {
3896
+ throw new HTTPException6(503, {
3897
+ message: "variable sets require OPENGENI_ENVIRONMENTS_ENCRYPTION_KEY"
3898
+ });
3899
+ }
3900
+ return key;
3901
+ }
3902
+ var requireEnvironmentEncryption = requireVariableSetEncryption;
3903
+ async function requireVariableSetForApi(db, workspaceId, variableSetId) {
3904
+ const variableSet = await getVariableSet2(db, workspaceId, variableSetId);
3905
+ if (!variableSet) {
3906
+ throw new HTTPException6(404, { message: "variableSet not found" });
3907
+ }
3908
+ return variableSet;
3909
+ }
3910
+ async function validateVariableSetAttachment(deps, grant, workspaceId, variableSetId, options = {}) {
3911
+ requireVariableSetEncryption(deps.settings);
3912
+ if (!options.preauthorized) {
3913
+ requirePermission(grant, "variable-sets:use");
3914
+ }
3915
+ const variableSet = await getVariableSet2(deps.db, workspaceId, variableSetId);
3916
+ if (!variableSet) {
3917
+ throw new HTTPException6(422, { message: "unknown variableSetId" });
3918
+ }
3919
+ return variableSet;
3920
+ }
3921
+ async function recordVariableSetAuditEvent(db, input) {
3922
+ await recordAuditEvent(db, {
3923
+ accountId: input.grant.accountId,
3924
+ workspaceId: input.grant.workspaceId,
3925
+ subjectId: input.grant.subjectId,
3926
+ action: input.action,
3927
+ targetType: "workspace_variable_set",
3928
+ targetId: input.variableSetId,
3929
+ metadata: {
3930
+ variableSetId: input.variableSetId,
3931
+ ...input.variableName ? { name: input.variableName } : {}
3932
+ }
3933
+ });
3934
+ }
3935
+
3870
3936
  // src/rigs/index.ts
3871
3937
  import {
3872
3938
  activateRigVersion,
@@ -3880,7 +3946,7 @@ import {
3880
3946
  getRigByName,
3881
3947
  getRigChange,
3882
3948
  getRigVersion,
3883
- getVariableSet as getVariableSet4,
3949
+ getVariableSet as getVariableSet3,
3884
3950
  listRigChanges,
3885
3951
  listRigVersions,
3886
3952
  recordAuditEvent as recordAuditEvent2,
@@ -3888,7 +3954,7 @@ import {
3888
3954
  RigChangeTransitionError,
3889
3955
  updateRig
3890
3956
  } from "@opengeni/db";
3891
- import { HTTPException as HTTPException8 } from "hono/http-exception";
3957
+ import { HTTPException as HTTPException7 } from "hono/http-exception";
3892
3958
 
3893
3959
  // src/rigs/provider-images.ts
3894
3960
  import { createHash as createHash3 } from "crypto";
@@ -3962,21 +4028,21 @@ function rigActorForGrant(grant) {
3962
4028
  async function requireRigForApi(db, workspaceId, rigId) {
3963
4029
  const rig = await getRig2(db, workspaceId, rigId);
3964
4030
  if (!rig) {
3965
- throw new HTTPException8(404, { message: "rig not found" });
4031
+ throw new HTTPException7(404, { message: "rig not found" });
3966
4032
  }
3967
4033
  return rig;
3968
4034
  }
3969
4035
  async function requireRigChangeForApi(db, workspaceId, rigId, changeId) {
3970
4036
  const change = await getRigChange(db, workspaceId, changeId);
3971
4037
  if (!change || change.rigId !== rigId) {
3972
- throw new HTTPException8(404, { message: "rig change not found" });
4038
+ throw new HTTPException7(404, { message: "rig change not found" });
3973
4039
  }
3974
4040
  return change;
3975
4041
  }
3976
4042
  function trimmedRigName(name) {
3977
4043
  const trimmed = name.trim();
3978
4044
  if (!trimmed) {
3979
- throw new HTTPException8(422, { message: "rig name is required" });
4045
+ throw new HTTPException7(422, { message: "rig name is required" });
3980
4046
  }
3981
4047
  return trimmed;
3982
4048
  }
@@ -3987,7 +4053,7 @@ function assertUniqueCheckNames(checks) {
3987
4053
  const seen = /* @__PURE__ */ new Set();
3988
4054
  for (const check of checks) {
3989
4055
  if (seen.has(check.name)) {
3990
- throw new HTTPException8(422, { message: `duplicate rig check name: ${check.name}` });
4056
+ throw new HTTPException7(422, { message: `duplicate rig check name: ${check.name}` });
3991
4057
  }
3992
4058
  seen.add(check.name);
3993
4059
  }
@@ -3998,9 +4064,9 @@ async function assertVariableSetsExist(db, workspaceId, ids) {
3998
4064
  }
3999
4065
  const unique = [...new Set(ids)];
4000
4066
  for (const id of unique) {
4001
- const variableSet = await getVariableSet4(db, workspaceId, id);
4067
+ const variableSet = await getVariableSet3(db, workspaceId, id);
4002
4068
  if (!variableSet) {
4003
- throw new HTTPException8(422, { message: `unknown defaultVariableSetId: ${id}` });
4069
+ throw new HTTPException7(422, { message: `unknown defaultVariableSetId: ${id}` });
4004
4070
  }
4005
4071
  }
4006
4072
  }
@@ -4010,12 +4076,12 @@ async function createRigForApi(deps, grant, payload) {
4010
4076
  assertUniqueCheckNames(payload.checks);
4011
4077
  await assertVariableSetsExist(deps.db, workspaceId, payload.defaultVariableSetIds);
4012
4078
  if (await countRigs(deps.db, workspaceId) >= MAX_RIGS_PER_WORKSPACE) {
4013
- throw new HTTPException8(422, {
4079
+ throw new HTTPException7(422, {
4014
4080
  message: `a workspace supports at most ${MAX_RIGS_PER_WORKSPACE} rigs`
4015
4081
  });
4016
4082
  }
4017
4083
  if (await getRigByName(deps.db, workspaceId, name)) {
4018
- throw new HTTPException8(409, { message: `rig name is already in use: ${name}` });
4084
+ throw new HTTPException7(409, { message: `rig name is already in use: ${name}` });
4019
4085
  }
4020
4086
  const createdBy = rigActorForGrant(grant);
4021
4087
  const rig = await createRig(deps.db, {
@@ -4043,7 +4109,7 @@ async function updateRigForApi(deps, grant, rig, payload) {
4043
4109
  if (name !== void 0 && name !== rig.name) {
4044
4110
  const existing = await getRigByName(deps.db, workspaceId, name);
4045
4111
  if (existing && existing.id !== rig.id) {
4046
- throw new HTTPException8(409, { message: `rig name is already in use: ${name}` });
4112
+ throw new HTTPException7(409, { message: `rig name is already in use: ${name}` });
4047
4113
  }
4048
4114
  }
4049
4115
  const updated = await updateRig(deps.db, workspaceId, rig.id, {
@@ -4057,19 +4123,19 @@ async function deleteRigForApi(deps, grant, rig) {
4057
4123
  const workspaceId = grant.workspaceId;
4058
4124
  const deleted = await deleteRigIfNoActiveSessions(deps.db, workspaceId, rig.id);
4059
4125
  if (deleted.activeSessionCount > 0) {
4060
- throw new HTTPException8(409, {
4126
+ throw new HTTPException7(409, {
4061
4127
  message: `rig is referenced by ${deleted.activeSessionCount} active session(s); it cannot be deleted`
4062
4128
  });
4063
4129
  }
4064
4130
  if (!deleted.deleted) {
4065
- throw new HTTPException8(404, { message: "rig not found" });
4131
+ throw new HTTPException7(404, { message: "rig not found" });
4066
4132
  }
4067
4133
  await recordRigAuditEvent(deps.db, { grant, action: "rig.deleted", rigId: rig.id });
4068
4134
  }
4069
4135
  async function proposeRigChangeForApi(deps, grant, rig, request, options = {}) {
4070
4136
  const workspaceId = grant.workspaceId;
4071
4137
  if (!rig.activeVersion) {
4072
- throw new HTTPException8(422, { message: "rig has no active version to base a change on" });
4138
+ throw new HTTPException7(422, { message: "rig has no active version to base a change on" });
4073
4139
  }
4074
4140
  if (request.kind === "definition_edit") {
4075
4141
  assertUniqueCheckNames(request.payload.checks);
@@ -4118,35 +4184,35 @@ async function promoteChangeWithActiveCas(deps, workspaceId, rigId, changeId, in
4118
4184
  return await createRigVersionForChangePromotion(deps.db, workspaceId, rigId, changeId, input);
4119
4185
  } catch (error) {
4120
4186
  if (error instanceof RigActiveVersionChangedError) {
4121
- throw new HTTPException8(409, {
4187
+ throw new HTTPException7(409, {
4122
4188
  message: `rig moved since this change was verified (base ${error.expectedVersionId}, now ${error.actualVersionId ?? "none"}); re-verify before promoting`
4123
4189
  });
4124
4190
  }
4125
4191
  if (error instanceof RigChangeTransitionError) {
4126
- throw new HTTPException8(409, { message: error.message });
4192
+ throw new HTTPException7(409, { message: error.message });
4127
4193
  }
4128
4194
  throw error;
4129
4195
  }
4130
4196
  }
4131
4197
  async function promoteSetupAppendChange(deps, grant, rig, change) {
4132
4198
  if (change.kind !== "setup_append") {
4133
- throw new HTTPException8(422, {
4199
+ throw new HTTPException7(422, {
4134
4200
  message: "only setup_append changes auto-promote through this path"
4135
4201
  });
4136
4202
  }
4137
4203
  if (change.status !== "proposed" && change.status !== "verifying") {
4138
- throw new HTTPException8(409, { message: `rig change is ${change.status}; cannot promote` });
4204
+ throw new HTTPException7(409, { message: `rig change is ${change.status}; cannot promote` });
4139
4205
  }
4140
4206
  if (!change.baseVersionId) {
4141
- throw new HTTPException8(422, { message: "rig change has no base version" });
4207
+ throw new HTTPException7(422, { message: "rig change has no base version" });
4142
4208
  }
4143
4209
  const base = await getRigVersion(deps.db, grant.workspaceId, rig.id, change.baseVersionId);
4144
4210
  if (!base) {
4145
- throw new HTTPException8(404, { message: "base rig version not found" });
4211
+ throw new HTTPException7(404, { message: "base rig version not found" });
4146
4212
  }
4147
4213
  const payload = change.payload;
4148
4214
  if (typeof payload.command !== "string" || !payload.command.trim()) {
4149
- throw new HTTPException8(422, { message: "setup_append change is missing command" });
4215
+ throw new HTTPException7(422, { message: "setup_append change is missing command" });
4150
4216
  }
4151
4217
  const nextDefinition = {
4152
4218
  image: base.image,
@@ -4184,22 +4250,22 @@ async function promoteSetupAppendChange(deps, grant, rig, change) {
4184
4250
  }
4185
4251
  async function promoteVerifiedDefinitionEditChangeForApi(deps, grant, rig, change) {
4186
4252
  if (change.kind !== "definition_edit") {
4187
- throw new HTTPException8(422, { message: "only definition_edit changes use explicit promote" });
4253
+ throw new HTTPException7(422, { message: "only definition_edit changes use explicit promote" });
4188
4254
  }
4189
4255
  if (change.status !== "proposed") {
4190
- throw new HTTPException8(409, { message: `rig change is ${change.status}; cannot promote` });
4256
+ throw new HTTPException7(409, { message: `rig change is ${change.status}; cannot promote` });
4191
4257
  }
4192
4258
  if (change.verification?.passed !== true) {
4193
- throw new HTTPException8(422, {
4259
+ throw new HTTPException7(422, {
4194
4260
  message: "definition_edit change must pass verification before promote"
4195
4261
  });
4196
4262
  }
4197
4263
  if (!change.baseVersionId) {
4198
- throw new HTTPException8(422, { message: "rig change has no base version" });
4264
+ throw new HTTPException7(422, { message: "rig change has no base version" });
4199
4265
  }
4200
4266
  const base = await getRigVersion(deps.db, grant.workspaceId, rig.id, change.baseVersionId);
4201
4267
  if (!base) {
4202
- throw new HTTPException8(404, { message: "base rig version not found" });
4268
+ throw new HTTPException7(404, { message: "base rig version not found" });
4203
4269
  }
4204
4270
  const payload = change.payload;
4205
4271
  const nextDefinition = {
@@ -4238,7 +4304,7 @@ async function promoteVerifiedDefinitionEditChangeForApi(deps, grant, rig, chang
4238
4304
  }
4239
4305
  async function createRigVersionForApi(deps, grant, rig, payload) {
4240
4306
  if (!rig.activeVersion) {
4241
- throw new HTTPException8(422, { message: "rig has no active version" });
4307
+ throw new HTTPException7(422, { message: "rig has no active version" });
4242
4308
  }
4243
4309
  assertUniqueCheckNames(payload.checks);
4244
4310
  await assertVariableSetsExist(
@@ -4594,13 +4660,13 @@ import {
4594
4660
  stableJson as stableJson3
4595
4661
  } from "@opengeni/contracts";
4596
4662
  import { areGitHubRepositoriesAllowedForWorkspace, requireFile } from "@opengeni/db";
4597
- import { HTTPException as HTTPException9 } from "hono/http-exception";
4663
+ import { HTTPException as HTTPException8 } from "hono/http-exception";
4598
4664
  function validateToolRefs(tools, settings) {
4599
4665
  const mcpServerIds = new Set(settings.mcpServers.map((server) => server.id));
4600
4666
  const out = [];
4601
4667
  for (const tool of tools) {
4602
4668
  if (tool.kind !== "mcp") {
4603
- throw new HTTPException9(422, {
4669
+ throw new HTTPException8(422, {
4604
4670
  message: `unsupported tool kind: ${tool.kind}`
4605
4671
  });
4606
4672
  }
@@ -4609,7 +4675,7 @@ function validateToolRefs(tools, settings) {
4609
4675
  if (optional) {
4610
4676
  continue;
4611
4677
  }
4612
- throw new HTTPException9(422, { message: `unknown MCP server id: ${tool.id}` });
4678
+ throw new HTTPException8(422, { message: `unknown MCP server id: ${tool.id}` });
4613
4679
  }
4614
4680
  out.push(
4615
4681
  optional ? { kind: "mcp", id: tool.id, optional: true } : { kind: "mcp", id: tool.id }
@@ -4632,7 +4698,7 @@ function assertToolRefsSubset(requested, allowed, message = "requested tools exc
4632
4698
  const allowedIds = new Set(allowed.map((tool) => `${tool.kind}:${tool.id}`));
4633
4699
  const widened = requested.find((tool) => !allowedIds.has(`${tool.kind}:${tool.id}`));
4634
4700
  if (widened) {
4635
- throw new HTTPException9(403, { message: `${message}: ${widened.id}` });
4701
+ throw new HTTPException8(403, { message: `${message}: ${widened.id}` });
4636
4702
  }
4637
4703
  }
4638
4704
  function validateToolRefsForSessionPolicy(input) {
@@ -4661,7 +4727,7 @@ function normalizeResources(resources) {
4661
4727
  try {
4662
4728
  normalizedUri = normalizeRepositoryTransportUri(resource.uri);
4663
4729
  } catch (error) {
4664
- throw new HTTPException9(422, {
4730
+ throw new HTTPException8(422, {
4665
4731
  message: error instanceof Error ? error.message : "invalid repository URI"
4666
4732
  });
4667
4733
  }
@@ -4670,14 +4736,14 @@ function normalizeResources(resources) {
4670
4736
  );
4671
4737
  const credentialBindingId = gitCredentialBindingIdForRepository(resource, credentialProvider);
4672
4738
  if ((resource.credentialBindingId || resource.connectionId || resource.access) && !credentialProvider) {
4673
- throw new HTTPException9(422, {
4739
+ throw new HTTPException8(422, {
4674
4740
  message: "repository credential bindings and access intent require a Git provider"
4675
4741
  });
4676
4742
  }
4677
4743
  if (credentialProvider && credentialBindingId) {
4678
4744
  const boundProvider = credentialBindingProviders.get(credentialBindingId);
4679
4745
  if (boundProvider && boundProvider !== credentialProvider) {
4680
- throw new HTTPException9(422, {
4746
+ throw new HTTPException8(422, {
4681
4747
  message: `credential binding ${credentialBindingId} is assigned to multiple Git providers`
4682
4748
  });
4683
4749
  }
@@ -4704,7 +4770,7 @@ function normalizeResources(resources) {
4704
4770
  const mountCollisionKey = normalized.mountPath ? resourceMountPathCollisionKey(normalized.mountPath) : void 0;
4705
4771
  const mounted = mountCollisionKey ? mountPaths.get(mountCollisionKey) : void 0;
4706
4772
  if (mounted && mounted !== key) {
4707
- throw new HTTPException9(422, {
4773
+ throw new HTTPException8(422, {
4708
4774
  message: `duplicate resource mount path: ${normalized.mountPath}`
4709
4775
  });
4710
4776
  }
@@ -4714,7 +4780,7 @@ function normalizeResources(resources) {
4714
4780
  const identity = resourceIdentityKey(normalized);
4715
4781
  const seenIdentity = identities.get(identity);
4716
4782
  if (seenIdentity && seenIdentity !== key) {
4717
- throw new HTTPException9(422, {
4783
+ throw new HTTPException8(422, {
4718
4784
  message: `duplicate resource with different settings: ${identity}`
4719
4785
  });
4720
4786
  }
@@ -4731,7 +4797,7 @@ function mergeResourceRefs(existing, additions) {
4731
4797
  return mergeContractResourceRefs(existing, additions, { rejectConflicts: true });
4732
4798
  } catch (error) {
4733
4799
  if (error instanceof ResourceRefConflictError) {
4734
- throw new HTTPException9(422, { message: error.message });
4800
+ throw new HTTPException8(422, { message: error.message });
4735
4801
  }
4736
4802
  throw error;
4737
4803
  }
@@ -4746,7 +4812,7 @@ function validateGitHubRepositorySelectionShapes(resources) {
4746
4812
  function validateGitHubRepositorySelectionShape(resources) {
4747
4813
  const installationIds = validateGitHubRepositorySelectionShapes(resources);
4748
4814
  if (installationIds.length > 1) {
4749
- throw new HTTPException9(422, {
4815
+ throw new HTTPException8(422, {
4750
4816
  message: "GitHub App repository resources must belong to one installation"
4751
4817
  });
4752
4818
  }
@@ -4768,7 +4834,7 @@ function gitHubRepositorySelections(resources) {
4768
4834
  const installationId = positiveInteger(installationRaw);
4769
4835
  const repositoryId = positiveInteger(repositoryRaw);
4770
4836
  if (!installationId || !repositoryId) {
4771
- throw new HTTPException9(422, {
4837
+ throw new HTTPException8(422, {
4772
4838
  message: "GitHub App repository resources require positive github_installation_id and github_repository_id"
4773
4839
  });
4774
4840
  }
@@ -4789,14 +4855,14 @@ async function validateGitHubRepositorySelection(db, workspaceId, resources) {
4789
4855
  installationId,
4790
4856
  repositoryIds
4791
4857
  )) {
4792
- throw new HTTPException9(422, {
4858
+ throw new HTTPException8(422, {
4793
4859
  message: "GitHub App repository resources must be authorized for a GitHub App installation linked to this workspace"
4794
4860
  });
4795
4861
  }
4796
4862
  }
4797
4863
  }
4798
4864
  function isAuthoritativeGitHubRepositorySelectionError(error) {
4799
- return error instanceof HTTPException9 && error.status === 422;
4865
+ return error instanceof HTTPException8 && error.status === 422;
4800
4866
  }
4801
4867
  async function validateFileResources(db, workspaceId, resources) {
4802
4868
  const fileIds = /* @__PURE__ */ new Set();
@@ -4805,15 +4871,15 @@ async function validateFileResources(db, workspaceId, resources) {
4805
4871
  continue;
4806
4872
  }
4807
4873
  if (fileIds.has(resource.fileId)) {
4808
- throw new HTTPException9(422, { message: `duplicate file resource: ${resource.fileId}` });
4874
+ throw new HTTPException8(422, { message: `duplicate file resource: ${resource.fileId}` });
4809
4875
  }
4810
4876
  fileIds.add(resource.fileId);
4811
4877
  const file = await requireFile(db, workspaceId, resource.fileId).catch(() => null);
4812
4878
  if (!file) {
4813
- throw new HTTPException9(422, { message: `unknown file resource: ${resource.fileId}` });
4879
+ throw new HTTPException8(422, { message: `unknown file resource: ${resource.fileId}` });
4814
4880
  }
4815
4881
  if (file.status !== "ready") {
4816
- throw new HTTPException9(422, {
4882
+ throw new HTTPException8(422, {
4817
4883
  message: `file resource ${resource.fileId} is ${file.status}`
4818
4884
  });
4819
4885
  }
@@ -4824,7 +4890,7 @@ function normalizeMountPath(path) {
4824
4890
  return normalizeResourceMountPath(path);
4825
4891
  } catch (error) {
4826
4892
  if (!(error instanceof ResourceMountPathError)) throw error;
4827
- throw new HTTPException9(422, { message: `invalid resource mount path: ${path}` });
4893
+ throw new HTTPException8(422, { message: `invalid resource mount path: ${path}` });
4828
4894
  }
4829
4895
  }
4830
4896
  function positiveInteger(value) {
@@ -4976,27 +5042,31 @@ import {
4976
5042
  getRig as getRig4,
4977
5043
  getScheduledTask,
4978
5044
  getScheduledTaskPersonalConnectionDelegations,
5045
+ getSessionTurnXaiProviderAccountAuthoritySnapshot as getSessionTurnXaiProviderAccountAuthoritySnapshot2,
4979
5046
  getSession as getSession3,
4980
5047
  requireWorkspace as requireWorkspace2,
4981
5048
  scopedKnowledgeScopeKey,
4982
- updateScheduledTask
5049
+ updateScheduledTask,
5050
+ resolveXaiProviderAccountAuthoritySnapshotForAcceptance
4983
5051
  } from "@opengeni/db";
4984
- import { HTTPException as HTTPException13 } from "hono/http-exception";
5052
+ import { HTTPException as HTTPException12 } from "hono/http-exception";
4985
5053
 
4986
5054
  // src/domain/sessions.ts
4987
5055
  import { CODEX_MODEL_ID_PREFIX, isCodexBilledModel } from "@opengeni/codex";
4988
5056
  import {
4989
5057
  canonicalizeConfiguredModelId,
4990
5058
  configuredAllowedModels,
5059
+ resolveFirstPartyMcpToolPolicy,
4991
5060
  policyProviderIdForModel,
4992
5061
  resolveTurnExecutionPolicyV1,
4993
- WORKSPACE_GATEWAY_MODEL_ID_PREFIX
5062
+ WORKSPACE_GATEWAY_MODEL_ID_PREFIX,
5063
+ XAI_SUBSCRIPTION_MODEL_ID_PREFIX
4994
5064
  } from "@opengeni/config";
4995
5065
  import {
4996
5066
  CreateSessionRequest,
4997
5067
  DEFAULT_FIRST_PARTY_MCP_PERMISSIONS,
4998
5068
  DEFAULT_FIRST_PARTY_MCP_TOOLS,
4999
- FIRST_PARTY_MCP_TOOL_NAMES,
5069
+ FIRST_PARTY_MCP_TOOL_NAMES as FIRST_PARTY_MCP_TOOL_NAMES2,
5000
5070
  OPENGENI_SLACK_BOT_SESSION_METADATA_KEY as OPENGENI_SLACK_BOT_SESSION_METADATA_KEY2,
5001
5071
  SessionSpawnDenial,
5002
5072
  ServiceTurnInitiator,
@@ -5013,6 +5083,7 @@ import {
5013
5083
  encryptVariableSetValue as encryptVariableSetValue2,
5014
5084
  getAnySessionInGroup,
5015
5085
  getEnrollment as getEnrollment3,
5086
+ getChannel,
5016
5087
  getRig as getRig3,
5017
5088
  getWorkspaceDefaultRigId,
5018
5089
  listDistinctVariableSetIdsInGroup,
@@ -5024,6 +5095,7 @@ import {
5024
5095
  getWorkspaceControlEvent,
5025
5096
  getSessionLineage,
5026
5097
  getSessionTurn,
5098
+ getSessionTurnXaiProviderAccountAuthoritySnapshot,
5027
5099
  getWorkspaceModelPolicy,
5028
5100
  initializeSessionStartAtomically,
5029
5101
  listSessionTurns,
@@ -5045,7 +5117,7 @@ import {
5045
5117
  publishDurableSessionEvents,
5046
5118
  publishDurableWorkspaceControlEvent
5047
5119
  } from "@opengeni/events";
5048
- import { HTTPException as HTTPException12 } from "hono/http-exception";
5120
+ import { HTTPException as HTTPException11 } from "hono/http-exception";
5049
5121
 
5050
5122
  // src/domain/timeline-annotations.ts
5051
5123
  import {
@@ -5054,11 +5126,11 @@ import {
5054
5126
  numberTimelineAnnotations
5055
5127
  } from "@opengeni/contracts";
5056
5128
  import { getSessionEvent } from "@opengeni/db";
5057
- import { HTTPException as HTTPException10 } from "hono/http-exception";
5129
+ import { HTTPException as HTTPException9 } from "hono/http-exception";
5058
5130
  var SOURCE_CONTEXT_BYTES = 160;
5059
5131
  var ANSI_SEQUENCE = new RegExp("\\u001B\\[[0-?]*[ -/]*[@-~]", "g");
5060
5132
  function invalidAnnotationSource() {
5061
- throw new HTTPException10(422, { message: "Invalid timeline annotation source" });
5133
+ throw new HTTPException9(422, { message: "Invalid timeline annotation source" });
5062
5134
  }
5063
5135
  function safeJson(value) {
5064
5136
  try {
@@ -5208,7 +5280,7 @@ import {
5208
5280
  import {
5209
5281
  getConnectionMetadata as getConnectionMetadata3
5210
5282
  } from "@opengeni/db";
5211
- import { HTTPException as HTTPException11 } from "hono/http-exception";
5283
+ import { HTTPException as HTTPException10 } from "hono/http-exception";
5212
5284
  function openGeniSlackBotMetadata(metadata) {
5213
5285
  const parsed = OpenGeniSlackBotConnectionMetadata.safeParse(metadata);
5214
5286
  return parsed.success ? parsed.data : null;
@@ -5227,12 +5299,12 @@ function hasReservedOpenGeniSlackBotSessionMetadata(metadata) {
5227
5299
  async function requireOpenGeniSlackBotConnection(db, workspaceId, connectionId) {
5228
5300
  const connection = await getConnectionMetadata3(db, workspaceId, connectionId, null);
5229
5301
  if (!connection || !isOpenGeniSlackBotConnection(connection)) {
5230
- throw new HTTPException11(422, {
5302
+ throw new HTTPException10(422, {
5231
5303
  message: "slackBotConnectionId must reference an OpenGeni Slack bot connection"
5232
5304
  });
5233
5305
  }
5234
5306
  if (connection.status !== "active") {
5235
- throw new HTTPException11(422, {
5307
+ throw new HTTPException10(422, {
5236
5308
  message: `OpenGeni Slack bot connection is not active (${connection.status})`
5237
5309
  });
5238
5310
  }
@@ -5269,10 +5341,14 @@ var SessionSpawnDeniedError = class extends Error {
5269
5341
  this.denial = denial;
5270
5342
  }
5271
5343
  };
5272
- function resolveFirstPartyMcpToolsForCreate(requested, parentStored) {
5344
+ function resolveFirstPartyMcpToolsForCreate(requested, parentStored, policy = {
5345
+ default: DEFAULT_FIRST_PARTY_MCP_TOOLS,
5346
+ allowed: FIRST_PARTY_MCP_TOOL_NAMES2
5347
+ }) {
5273
5348
  if (requested !== void 0) return [...requested];
5274
- if (parentStored === void 0) return [...DEFAULT_FIRST_PARTY_MCP_TOOLS];
5275
- return [...parentStored ?? DEFAULT_FIRST_PARTY_MCP_TOOLS];
5349
+ const allowed = new Set(policy.allowed);
5350
+ const inherited = parentStored === void 0 ? policy.default : parentStored ?? policy.default;
5351
+ return [...inherited].filter((tool) => allowed.has(tool));
5276
5352
  }
5277
5353
  function sessionSpawnDeniedMessage(denial) {
5278
5354
  if (denial.code === "nested_agent_depth_override_forbidden") {
@@ -5292,7 +5368,7 @@ function sessionSpawnDenialEnvelope(error) {
5292
5368
  function serviceInitiatorForGrant(grant) {
5293
5369
  if (!grant.serviceInitiator) {
5294
5370
  if (grant.serviceInitiatorContext) {
5295
- throw new HTTPException12(403, {
5371
+ throw new HTTPException11(403, {
5296
5372
  message: "service initiator context requires a signed service initiator"
5297
5373
  });
5298
5374
  }
@@ -5300,13 +5376,13 @@ function serviceInitiatorForGrant(grant) {
5300
5376
  }
5301
5377
  const initiator = ServiceTurnInitiator.safeParse(grant.serviceInitiator);
5302
5378
  if (!initiator.success) {
5303
- throw new HTTPException12(403, {
5379
+ throw new HTTPException11(403, {
5304
5380
  message: "a delegated command initiator must be a bounded service principal"
5305
5381
  });
5306
5382
  }
5307
5383
  const context = ServiceTurnInitiatorContext.safeParse(grant.serviceInitiatorContext ?? {});
5308
5384
  if (!context.success) {
5309
- throw new HTTPException12(403, {
5385
+ throw new HTTPException11(403, {
5310
5386
  message: "delegated service initiator context is invalid or reserved"
5311
5387
  });
5312
5388
  }
@@ -5314,7 +5390,7 @@ function serviceInitiatorForGrant(grant) {
5314
5390
  const callerAttemptId = grant.metadata?.["attemptId"];
5315
5391
  const callerExecutionGeneration = grant.metadata?.["executionGeneration"];
5316
5392
  if (callerTurnId !== void 0 || callerAttemptId !== void 0 || callerExecutionGeneration !== void 0) {
5317
- throw new HTTPException12(403, {
5393
+ throw new HTTPException11(403, {
5318
5394
  message: "a service initiator cannot replace an exact agent-attempt initiator"
5319
5395
  });
5320
5396
  }
@@ -5332,7 +5408,7 @@ function creationInitiatorForGrant(grant) {
5332
5408
  const hasCallerTurnClaim = callerTurnId !== void 0 || callerAttemptId !== void 0 || callerExecutionGeneration !== void 0;
5333
5409
  if (hasCallerTurnClaim) {
5334
5410
  if (typeof callerSessionId !== "string" || typeof callerTurnId !== "string" || typeof callerAttemptId !== "string" || typeof callerExecutionGeneration !== "number" || !Number.isSafeInteger(callerExecutionGeneration) || callerExecutionGeneration < 1) {
5335
- throw new HTTPException12(403, {
5411
+ throw new HTTPException11(403, {
5336
5412
  message: "caller attempt claims are incomplete"
5337
5413
  });
5338
5414
  }
@@ -5363,31 +5439,31 @@ function normalizedSessionMcpCredentialHeaders(headers) {
5363
5439
  }
5364
5440
  const entries = Object.entries(headers).map(([name, value]) => [name.trim(), value]).filter(([name]) => name.length > 0);
5365
5441
  if (entries.length > maxSessionMcpCredentialHeaders) {
5366
- throw new HTTPException12(422, {
5442
+ throw new HTTPException11(422, {
5367
5443
  message: `a session MCP server supports at most ${maxSessionMcpCredentialHeaders} credential headers`
5368
5444
  });
5369
5445
  }
5370
5446
  const seen = /* @__PURE__ */ new Set();
5371
5447
  for (const [name, value] of entries) {
5372
5448
  if (!sessionMcpCredentialHeaderName.test(name)) {
5373
- throw new HTTPException12(422, {
5449
+ throw new HTTPException11(422, {
5374
5450
  message: `invalid credential header name: ${name}`
5375
5451
  });
5376
5452
  }
5377
5453
  const lower = name.toLowerCase();
5378
5454
  if (seen.has(lower)) {
5379
- throw new HTTPException12(422, {
5455
+ throw new HTTPException11(422, {
5380
5456
  message: `duplicate credential header name: ${name}`
5381
5457
  });
5382
5458
  }
5383
5459
  seen.add(lower);
5384
5460
  if (value.length === 0 || value.length > maxSessionMcpCredentialHeaderValueLength) {
5385
- throw new HTTPException12(422, {
5461
+ throw new HTTPException11(422, {
5386
5462
  message: `credential header ${name} must be 1-${maxSessionMcpCredentialHeaderValueLength} characters`
5387
5463
  });
5388
5464
  }
5389
5465
  if (/[\u0000-\u0008\u000A-\u001F\u007F]/.test(value)) {
5390
- throw new HTTPException12(422, {
5466
+ throw new HTTPException11(422, {
5391
5467
  message: `credential header ${name} contains forbidden control characters`
5392
5468
  });
5393
5469
  }
@@ -5454,13 +5530,13 @@ function validateSessionMcpServersForCreate(settings, grant, servers) {
5454
5530
  const metadata = [];
5455
5531
  for (const server of servers) {
5456
5532
  if (seenIds.has(server.id)) {
5457
- throw new HTTPException12(422, {
5533
+ throw new HTTPException11(422, {
5458
5534
  message: `duplicate session MCP server id: ${server.id}`
5459
5535
  });
5460
5536
  }
5461
5537
  seenIds.add(server.id);
5462
5538
  if (reservedSessionMcpServerIds.has(server.id) || existingIds.has(server.id)) {
5463
- throw new HTTPException12(422, {
5539
+ throw new HTTPException11(422, {
5464
5540
  message: `MCP server id already exists: ${server.id}`
5465
5541
  });
5466
5542
  }
@@ -5502,13 +5578,13 @@ function validateInheritedSessionMcpServersForCreate(servers) {
5502
5578
  const seenIds = /* @__PURE__ */ new Set();
5503
5579
  for (const server of servers) {
5504
5580
  if (seenIds.has(server.id)) {
5505
- throw new HTTPException12(422, {
5581
+ throw new HTTPException11(422, {
5506
5582
  message: `duplicate inherited session MCP server id: ${server.id}`
5507
5583
  });
5508
5584
  }
5509
5585
  seenIds.add(server.id);
5510
5586
  if (reservedSessionMcpServerIds.has(server.id)) {
5511
- throw new HTTPException12(422, {
5587
+ throw new HTTPException11(422, {
5512
5588
  message: `reserved inherited session MCP server id: ${server.id}`
5513
5589
  });
5514
5590
  }
@@ -5540,13 +5616,13 @@ function validateSessionMcpCredentialUpdates(input) {
5540
5616
  const seenIds = /* @__PURE__ */ new Set();
5541
5617
  const encryptedUpdates = input.updates.map((update) => {
5542
5618
  if (seenIds.has(update.id)) {
5543
- throw new HTTPException12(422, {
5619
+ throw new HTTPException11(422, {
5544
5620
  message: `duplicate session MCP credential update id: ${update.id}`
5545
5621
  });
5546
5622
  }
5547
5623
  seenIds.add(update.id);
5548
5624
  if (!knownIds.has(update.id)) {
5549
- throw new HTTPException12(422, {
5625
+ throw new HTTPException11(422, {
5550
5626
  message: `unknown session MCP server id: ${update.id}`
5551
5627
  });
5552
5628
  }
@@ -5590,6 +5666,7 @@ async function createAndStartSessionWithOutcome(input) {
5590
5666
  variableSetId: input.variableSet?.id ?? null,
5591
5667
  rigId: input.rigId ?? null,
5592
5668
  rigVersionId: input.rigVersionId ?? null,
5669
+ channelId: input.channelId ?? null,
5593
5670
  firstPartyMcpPermissions: input.firstPartyMcpPermissions ?? null,
5594
5671
  firstPartyMcpTools: input.firstPartyMcpTools,
5595
5672
  instructions: input.instructions ?? null,
@@ -5600,9 +5677,13 @@ async function createAndStartSessionWithOutcome(input) {
5600
5677
  ...input.sandboxOs ? { sandboxOs: input.sandboxOs } : {},
5601
5678
  mcpServers: input.mcpServers ?? [],
5602
5679
  personalConnectionDelegations: input.personalConnectionDelegations ?? [],
5680
+ ...input.xaiProviderAccountAuthoritySnapshot ? {
5681
+ initialXaiProviderAccountAuthoritySnapshot: input.xaiProviderAccountAuthoritySnapshot
5682
+ } : {},
5603
5683
  maxNestedAgentDepthOverride: input.maxNestedAgentDepthOverride ?? null,
5604
5684
  allowNestedAgentDepthIncrease: input.allowNestedAgentDepthIncrease ?? false,
5605
- subjectId: input.subjectId ?? null
5685
+ subjectId: input.subjectId ?? null,
5686
+ ...input.beforeCreateCommit ? { beforeCreateCommit: input.beforeCreateCommit } : {}
5606
5687
  });
5607
5688
  if (keyedResult.denied) {
5608
5689
  throw new SessionSpawnDeniedError(SessionSpawnDenial.parse(keyedResult.denial));
@@ -5649,6 +5730,7 @@ async function createAndStartSessionWithOutcome(input) {
5649
5730
  variableSetId: input.variableSet?.id ?? null,
5650
5731
  rigId: input.rigId ?? null,
5651
5732
  rigVersionId: input.rigVersionId ?? null,
5733
+ channelId: input.channelId ?? null,
5652
5734
  firstPartyMcpPermissions: input.firstPartyMcpPermissions ?? null,
5653
5735
  firstPartyMcpTools: input.firstPartyMcpTools,
5654
5736
  instructions: input.instructions ?? null,
@@ -5658,9 +5740,13 @@ async function createAndStartSessionWithOutcome(input) {
5658
5740
  ...input.sandboxOs ? { sandboxOs: input.sandboxOs } : {},
5659
5741
  mcpServers: input.mcpServers ?? [],
5660
5742
  personalConnectionDelegations: input.personalConnectionDelegations ?? [],
5743
+ ...input.xaiProviderAccountAuthoritySnapshot ? {
5744
+ initialXaiProviderAccountAuthoritySnapshot: input.xaiProviderAccountAuthoritySnapshot
5745
+ } : {},
5661
5746
  maxNestedAgentDepthOverride: input.maxNestedAgentDepthOverride ?? null,
5662
5747
  allowNestedAgentDepthIncrease: input.allowNestedAgentDepthIncrease ?? false,
5663
- subjectId: input.subjectId ?? null
5748
+ subjectId: input.subjectId ?? null,
5749
+ ...input.beforeCreateCommit ? { beforeCreateCommit: input.beforeCreateCommit } : {}
5664
5750
  });
5665
5751
  } catch (error) {
5666
5752
  if (error instanceof SessionSpawnDeniedDbError) {
@@ -5682,7 +5768,7 @@ async function createAndStartSession(input) {
5682
5768
  async function finishStartSession(input, session) {
5683
5769
  if (input.seedTargetSandbox) {
5684
5770
  if (session.sandboxBackend === "none") {
5685
- throw new HTTPException12(422, {
5771
+ throw new HTTPException11(422, {
5686
5772
  message: "cannot target a machine for a session with no sandbox (backend: none)"
5687
5773
  });
5688
5774
  }
@@ -5706,7 +5792,7 @@ async function finishStartSession(input, session) {
5706
5792
  input.seedTargetSandbox.workingDir ?? null
5707
5793
  );
5708
5794
  if (!seeded.swapped) {
5709
- throw new HTTPException12(422, {
5795
+ throw new HTTPException11(422, {
5710
5796
  message: `cannot target sandbox ${input.seedTargetSandbox.sandboxId}: ${seeded.reason ?? "target is not attachable"}`
5711
5797
  });
5712
5798
  }
@@ -5765,10 +5851,13 @@ function canonicalConfiguredModel(settings, model) {
5765
5851
  if (settings.codexSubscriptionEnabled && canonicalModel.startsWith(CODEX_MODEL_ID_PREFIX)) {
5766
5852
  return canonicalModel;
5767
5853
  }
5854
+ if (settings.supergrokSubscriptionEnabled && canonicalModel.startsWith(XAI_SUBSCRIPTION_MODEL_ID_PREFIX)) {
5855
+ return canonicalModel;
5856
+ }
5768
5857
  if (canonicalModel.startsWith(WORKSPACE_GATEWAY_MODEL_ID_PREFIX)) {
5769
5858
  return canonicalModel;
5770
5859
  }
5771
- throw new HTTPException12(422, { message: `model is not available: ${model}` });
5860
+ throw new HTTPException11(422, { message: `model is not available: ${model}` });
5772
5861
  }
5773
5862
  function assertConfiguredModel(settings, model) {
5774
5863
  canonicalConfiguredModel(settings, model);
@@ -5809,7 +5898,7 @@ async function assertWorkspaceModelPolicyAllows(db, settings, workspaceId, model
5809
5898
  modelId: canonicalModel
5810
5899
  });
5811
5900
  if (!verdict.allowed) {
5812
- throw new HTTPException12(422, {
5901
+ throw new HTTPException11(422, {
5813
5902
  message: verdict.reason === "provider" ? `model "${canonicalModel}" is not allowed by this workspace's model policy: provider "${providerId}" is not in the allowed providers` : `model "${canonicalModel}" is not allowed by this workspace's model policy`
5814
5903
  });
5815
5904
  }
@@ -5817,10 +5906,10 @@ async function assertWorkspaceModelPolicyAllows(db, settings, workspaceId, model
5817
5906
  async function requireQueuedTurnForApi(db, workspaceId, sessionId, turnId) {
5818
5907
  const turn = await getSessionTurn(db, workspaceId, turnId);
5819
5908
  if (!turn || turn.sessionId !== sessionId) {
5820
- throw new HTTPException12(404, { message: "session turn not found" });
5909
+ throw new HTTPException11(404, { message: "session turn not found" });
5821
5910
  }
5822
5911
  if (turn.status !== "queued") {
5823
- throw new HTTPException12(409, {
5912
+ throw new HTTPException11(409, {
5824
5913
  message: `turn is ${turn.status}; only queued turns can be changed`
5825
5914
  });
5826
5915
  }
@@ -5844,7 +5933,7 @@ async function postUserMessageTurn(input) {
5844
5933
  assertSessionAllowsProductModel(sessionForModelGate, effectiveModelForGate);
5845
5934
  } catch (error) {
5846
5935
  if (error instanceof CodexCompactionV2ProviderLockedError) {
5847
- throw new HTTPException12(422, { message: error.message, cause: error });
5936
+ throw new HTTPException11(422, { message: error.message, cause: error });
5848
5937
  }
5849
5938
  throw error;
5850
5939
  }
@@ -5893,13 +5982,13 @@ async function postUserMessageTurn(input) {
5893
5982
  );
5894
5983
  } catch (error) {
5895
5984
  if (error instanceof QueueCommandConflictError || error instanceof SessionControlConflictError) {
5896
- throw new HTTPException12(409, { message: error.message });
5985
+ throw new HTTPException11(409, { message: error.message });
5897
5986
  }
5898
5987
  if (error instanceof Error && error.message.includes("cancelled")) {
5899
- throw new HTTPException12(409, { message: error.message });
5988
+ throw new HTTPException11(409, { message: error.message });
5900
5989
  }
5901
5990
  if (error instanceof Error && error.message.startsWith("Unknown session MCP server")) {
5902
- throw new HTTPException12(422, { message: error.message });
5991
+ throw new HTTPException11(422, { message: error.message });
5903
5992
  }
5904
5993
  throw error;
5905
5994
  }
@@ -5966,7 +6055,7 @@ async function createSessionForRequestWithOutcome(deps, grant, workspaceId, rawP
5966
6055
  const { settings, db, bus, workflowClient, objectStorage } = deps;
5967
6056
  const payload = CreateSessionRequest.parse(rawPayload);
5968
6057
  if (hasReservedOpenGeniSlackBotSessionMetadata(payload.metadata)) {
5969
- throw new HTTPException12(422, {
6058
+ throw new HTTPException11(422, {
5970
6059
  message: `${OPENGENI_SLACK_BOT_SESSION_METADATA_KEY2} is reserved for scheduler routing`
5971
6060
  });
5972
6061
  }
@@ -5982,30 +6071,45 @@ async function createSessionForRequestWithOutcome(deps, grant, workspaceId, rawP
5982
6071
  }
5983
6072
  const parentSessionId = typeof grant.metadata?.["sessionId"] === "string" ? grant.metadata["sessionId"] : null;
5984
6073
  if (parentSessionId) {
5985
- await requireSessionAuthorization(deps, grant, {
5986
- sessionId: parentSessionId,
5987
- operation: "session.child.create",
5988
- surface: "core"
5989
- });
6074
+ try {
6075
+ await requireSessionAuthorization(deps, grant, {
6076
+ sessionId: parentSessionId,
6077
+ operation: "session.child.create",
6078
+ surface: "core"
6079
+ });
6080
+ } catch (error) {
6081
+ if (error instanceof SessionAuthorizationDeniedError) {
6082
+ throw new HTTPException11(403, { message: error.message, cause: error });
6083
+ }
6084
+ throw error;
6085
+ }
5990
6086
  }
5991
6087
  const parentSession = parentSessionId ? await getSession2(db, workspaceId, parentSessionId) : null;
5992
6088
  if (parentSessionId && !parentSession) {
5993
- throw new HTTPException12(404, {
6089
+ throw new HTTPException11(404, {
5994
6090
  message: `parent session not found in workspace: ${parentSessionId}`
5995
6091
  });
5996
6092
  }
5997
6093
  const creationInitiator = creationInitiatorForGrant(grant);
5998
6094
  const parentCallingTurn = parentSession && creationInitiator.actor ? await getSessionTurn(db, workspaceId, creationInitiator.actor.turnId) : null;
5999
6095
  if (creationInitiator.actor && (!parentCallingTurn || parentCallingTurn.sessionId !== parentSession?.id)) {
6000
- throw new HTTPException12(403, {
6096
+ throw new HTTPException11(403, {
6001
6097
  message: "caller attempt does not belong to the parent session"
6002
6098
  });
6003
6099
  }
6100
+ const xaiProviderAccountAuthoritySnapshot = parentSession && creationInitiator.actor ? await getSessionTurnXaiProviderAccountAuthoritySnapshot(
6101
+ db,
6102
+ workspaceId,
6103
+ parentSession.id,
6104
+ creationInitiator.actor.turnId
6105
+ ) : void 0;
6004
6106
  const capabilityRuntimeSettings = await settingsWithEnabledCapabilityMcpServers(
6005
6107
  db,
6006
6108
  workspaceId,
6007
6109
  settings,
6008
- { subjectId: grant.subjectId }
6110
+ {
6111
+ subjectId: grant.subjectId
6112
+ }
6009
6113
  );
6010
6114
  const sessionMcpServers = hasOwnProperty(rawPayload, "mcpServers") ? validateSessionMcpServersForCreate(capabilityRuntimeSettings, grant, payload.mcpServers) : parentSession ? validateInheritedSessionMcpServersForCreate(
6011
6115
  await listSessionMcpServersForChildInheritance(db, workspaceId, parentSession.id)
@@ -6074,7 +6178,7 @@ async function createSessionForRequestWithOutcome(deps, grant, workspaceId, rawP
6074
6178
  });
6075
6179
  await validateGitHubRepositorySelection(db, workspaceId, resources);
6076
6180
  if (resources.some((resource) => resource.kind === "file") && !objectStorage) {
6077
- throw new HTTPException12(503, {
6181
+ throw new HTTPException11(503, {
6078
6182
  message: "object storage is not configured"
6079
6183
  });
6080
6184
  }
@@ -6092,7 +6196,7 @@ async function createSessionForRequestWithOutcome(deps, grant, workspaceId, rawP
6092
6196
  const rig = await getRig3(db, workspaceId, requestedRigId);
6093
6197
  if (!rig || !rig.activeVersion) {
6094
6198
  if (payload.rigId) {
6095
- throw new HTTPException12(422, {
6199
+ throw new HTTPException11(422, {
6096
6200
  message: rig ? `rig ${payload.rigId} has no active version to bind` : `unknown rigId: ${payload.rigId}`
6097
6201
  });
6098
6202
  }
@@ -6101,6 +6205,16 @@ async function createSessionForRequestWithOutcome(deps, grant, workspaceId, rawP
6101
6205
  frozenRigVersionId = rig.activeVersion.id;
6102
6206
  }
6103
6207
  }
6208
+ let channelId = null;
6209
+ if (payload.channelId) {
6210
+ const channel = await getChannel(db, workspaceId, payload.channelId);
6211
+ if (!channel) {
6212
+ throw new HTTPException11(422, {
6213
+ message: `unknown channelId: ${payload.channelId}`
6214
+ });
6215
+ }
6216
+ channelId = channel.id;
6217
+ }
6104
6218
  const inheritedModel = parentCallingTurn?.model ?? parentSession?.model ?? settings.openaiModel;
6105
6219
  const model = canonicalConfiguredModel(settings, payload.model ?? inheritedModel);
6106
6220
  if (model === null || model === void 0) {
@@ -6125,7 +6239,7 @@ async function createSessionForRequestWithOutcome(deps, grant, workspaceId, rawP
6125
6239
  if (parentFirstPartyMcpPermissions && payload.firstPartyMcpPermissions?.some(
6126
6240
  (permission) => !hasPermission(parentFirstPartyMcpPermissions, permission)
6127
6241
  )) {
6128
- throw new HTTPException12(403, {
6242
+ throw new HTTPException11(403, {
6129
6243
  message: "child first-party MCP permissions may only narrow the parent session grant"
6130
6244
  });
6131
6245
  }
@@ -6133,32 +6247,42 @@ async function createSessionForRequestWithOutcome(deps, grant, workspaceId, rawP
6133
6247
  (permission) => hasPermission(grant.permissions, permission)
6134
6248
  ) : null);
6135
6249
  if (firstPartyMcpPermissions && firstPartyMcpPermissions.length === 0) {
6136
- throw new HTTPException12(422, {
6250
+ throw new HTTPException11(422, {
6137
6251
  message: "firstPartyMcpPermissions must not be empty; omit it for the default worker permission set"
6138
6252
  });
6139
6253
  }
6140
6254
  for (const permission of firstPartyMcpPermissions ?? []) {
6141
6255
  if (!hasPermission(grant.permissions, permission)) {
6142
- throw new HTTPException12(403, {
6256
+ throw new HTTPException11(403, {
6143
6257
  message: `cannot grant first-party MCP permission beyond the creating grant: ${permission}`
6144
6258
  });
6145
6259
  }
6146
6260
  }
6147
6261
  if (payload.goal && firstPartyMcpPermissions && !firstPartyMcpPermissions.includes("goals:manage")) {
6148
- throw new HTTPException12(422, {
6262
+ throw new HTTPException11(422, {
6149
6263
  message: "goal-bearing sessions require goals:manage in the resulting first-party MCP permission set"
6150
6264
  });
6151
6265
  }
6266
+ const deploymentFirstPartyMcpToolPolicy = resolveFirstPartyMcpToolPolicy(settings);
6267
+ const disallowedFirstPartyMcpTool = payload.firstPartyMcpTools?.find(
6268
+ (tool) => !deploymentFirstPartyMcpToolPolicy.allowed.includes(tool)
6269
+ );
6270
+ if (disallowedFirstPartyMcpTool) {
6271
+ throw new HTTPException11(422, {
6272
+ message: `first-party MCP tool is disabled by deployment policy: ${disallowedFirstPartyMcpTool}`
6273
+ });
6274
+ }
6152
6275
  const firstPartyMcpTools = resolveFirstPartyMcpToolsForCreate(
6153
6276
  payload.firstPartyMcpTools,
6154
- parentSession ? parentSession.firstPartyMcpTools : void 0
6277
+ parentSession ? parentSession.firstPartyMcpTools : void 0,
6278
+ deploymentFirstPartyMcpToolPolicy
6155
6279
  );
6156
6280
  if (payload.goal) {
6157
- const missingGoalTools = ["goal_update", "goal_complete", "goal_pause"].filter(
6281
+ const missingGoalTools = ["goal_update", "goal_progress", "goal_complete", "goal_pause"].filter(
6158
6282
  (name) => !firstPartyMcpTools.includes(name)
6159
6283
  );
6160
6284
  if (missingGoalTools.length > 0) {
6161
- throw new HTTPException12(422, {
6285
+ throw new HTTPException11(422, {
6162
6286
  message: `goal-bearing sessions require first-party MCP tools: ${missingGoalTools.join(", ")}`
6163
6287
  });
6164
6288
  }
@@ -6171,7 +6295,7 @@ async function createSessionForRequestWithOutcome(deps, grant, workspaceId, rawP
6171
6295
  const rigVersionMatchesGroup = (memberRigVersionId) => memberRigVersionId === frozenRigVersionId;
6172
6296
  if (sandboxChoice === "shared") {
6173
6297
  if (!parentSessionId) {
6174
- throw new HTTPException12(422, {
6298
+ throw new HTTPException11(422, {
6175
6299
  message: "sandbox:'shared' requires a parent session (spawn from inside a session); use 'new' for a top-level create."
6176
6300
  });
6177
6301
  }
@@ -6194,7 +6318,7 @@ async function createSessionForRequestWithOutcome(deps, grant, workspaceId, rawP
6194
6318
  }
6195
6319
  if (variableSetMismatch || rigMismatch) {
6196
6320
  if (payload.sandbox === "shared") {
6197
- throw new HTTPException12(422, {
6321
+ throw new HTTPException11(422, {
6198
6322
  message: variableSetMismatch ? "sandbox:'shared' requires the same variableSet / same environment as the creator's box (the box variable set/environment is fixed at creation); omit sandbox or pass 'new' when attaching a different variableSet/environment." : "sandbox:'shared' requires the same rig as the creator's box (the box's rig setup is fixed at creation); omit sandbox or pass 'new' when binding a different rig."
6199
6323
  });
6200
6324
  }
@@ -6205,7 +6329,7 @@ async function createSessionForRequestWithOutcome(deps, grant, workspaceId, rawP
6205
6329
  } else if (typeof sandboxChoice === "object") {
6206
6330
  const member = await getAnySessionInGroup(db, workspaceId, sandboxChoice.groupId);
6207
6331
  if (!member) {
6208
- throw new HTTPException12(404, {
6332
+ throw new HTTPException11(404, {
6209
6333
  message: `sandbox group not found in workspace: ${sandboxChoice.groupId}`
6210
6334
  });
6211
6335
  }
@@ -6218,7 +6342,7 @@ async function createSessionForRequestWithOutcome(deps, grant, workspaceId, rawP
6218
6342
  if (!memberVariableSetIds.every(
6219
6343
  (memberVariableSetId) => variableSetMatchesGroup(memberVariableSetId)
6220
6344
  )) {
6221
- throw new HTTPException12(422, {
6345
+ throw new HTTPException11(422, {
6222
6346
  message: `sandbox group ${sandboxChoice.groupId} runs a different variableSet / different environment (the box variable set/environment is fixed at creation); create with the group's variableSet/environment or omit sandbox for an own box.`
6223
6347
  });
6224
6348
  }
@@ -6230,7 +6354,7 @@ async function createSessionForRequestWithOutcome(deps, grant, workspaceId, rawP
6230
6354
  if (!memberRigVersionIds.every(
6231
6355
  (memberRigVersionId) => rigVersionMatchesGroup(memberRigVersionId)
6232
6356
  )) {
6233
- throw new HTTPException12(422, {
6357
+ throw new HTTPException11(422, {
6234
6358
  message: `sandbox group ${sandboxChoice.groupId} runs a different rig (the box's rig setup is fixed at creation); create with the group's rig or omit sandbox for an own box.`
6235
6359
  });
6236
6360
  }
@@ -6239,7 +6363,7 @@ async function createSessionForRequestWithOutcome(deps, grant, workspaceId, rawP
6239
6363
  inheritedBackend = member.sandboxBackend;
6240
6364
  }
6241
6365
  if (payload.workingDir !== void 0 && !payload.targetSandboxId) {
6242
- throw new HTTPException12(422, {
6366
+ throw new HTTPException11(422, {
6243
6367
  message: "workingDir requires targetSandboxId (it is the targeted machine's working directory)"
6244
6368
  });
6245
6369
  }
@@ -6306,6 +6430,7 @@ async function createSessionForRequestWithOutcome(deps, grant, workspaceId, rawP
6306
6430
  // Frozen rig binding (M3): both null for a rig-less session (today's path).
6307
6431
  rigId: frozenRigId,
6308
6432
  rigVersionId: frozenRigVersionId,
6433
+ channelId,
6309
6434
  goal: payload.goal ?? null,
6310
6435
  // Per-session persona instructions (already trimmed/validated by the
6311
6436
  // contracts schema). Persisted on the row; composed system-level at turn
@@ -6317,6 +6442,7 @@ async function createSessionForRequestWithOutcome(deps, grant, workspaceId, rawP
6317
6442
  mcpServers: sessionMcpServers.dbServers,
6318
6443
  sessionMcpServers: sessionMcpServers.metadata,
6319
6444
  personalConnectionDelegations,
6445
+ ...xaiProviderAccountAuthoritySnapshot ? { xaiProviderAccountAuthoritySnapshot } : {},
6320
6446
  parentSessionId,
6321
6447
  createIdempotencyKey: payload.idempotencyKey ?? null,
6322
6448
  maxNestedAgentDepthOverride: payload.maxNestedAgentDepth ?? null,
@@ -6338,10 +6464,10 @@ async function createSessionForRequestWithOutcome(deps, grant, workspaceId, rawP
6338
6464
  });
6339
6465
  } catch (error) {
6340
6466
  if (error instanceof AgentCommandAuthorityError) {
6341
- throw new HTTPException12(403, { message: error.message });
6467
+ throw new HTTPException11(403, { message: error.message });
6342
6468
  }
6343
6469
  if (error instanceof SessionIdConflictError) {
6344
- throw new HTTPException12(409, {
6470
+ throw new HTTPException11(409, {
6345
6471
  message: "requested session id is already in use"
6346
6472
  });
6347
6473
  }
@@ -6387,6 +6513,7 @@ async function createSessionForRequest(deps, grant, workspaceId, rawPayload) {
6387
6513
  }
6388
6514
  async function acceptSessionUserMessageWithOutcome(deps, grant, workspaceId, sessionId, input) {
6389
6515
  const { settings, db, bus, workflowClient, objectStorage } = deps;
6516
+ const delegatedServiceInitiator = serviceInitiatorForGrant(grant);
6390
6517
  await requireSessionAuthorization(deps, grant, {
6391
6518
  sessionId,
6392
6519
  operation: input.delivery === "steer" ? "session.steer" : "session.append",
@@ -6402,7 +6529,7 @@ async function acceptSessionUserMessageWithOutcome(deps, grant, workspaceId, ses
6402
6529
  assertSessionAllowsProductModel(existingSession, effectiveModel);
6403
6530
  } catch (error) {
6404
6531
  if (error instanceof CodexCompactionV2ProviderLockedError) {
6405
- throw new HTTPException12(422, { message: error.message, cause: error });
6532
+ throw new HTTPException11(422, { message: error.message, cause: error });
6406
6533
  }
6407
6534
  throw error;
6408
6535
  }
@@ -6437,7 +6564,7 @@ async function acceptSessionUserMessageWithOutcome(deps, grant, workspaceId, ses
6437
6564
  model: effectiveModel
6438
6565
  });
6439
6566
  if (requestedResources.some((resource) => resource.kind === "file") && !objectStorage) {
6440
- throw new HTTPException12(503, {
6567
+ throw new HTTPException11(503, {
6441
6568
  message: "object storage is not configured"
6442
6569
  });
6443
6570
  }
@@ -6462,7 +6589,6 @@ async function acceptSessionUserMessageWithOutcome(deps, grant, workspaceId, ses
6462
6589
  tools: existingSession.tools,
6463
6590
  source: personalConnectionDelegationSourceForGrant(grant)
6464
6591
  });
6465
- const delegatedServiceInitiator = serviceInitiatorForGrant(grant);
6466
6592
  const { accepted, turn, interruptionCount, replay } = await postUserMessageTurn({
6467
6593
  db,
6468
6594
  bus,
@@ -6558,7 +6684,7 @@ async function updateSessionMcpApprovalPolicy(deps, grant, sessionId, serverId,
6558
6684
  async (_session, context) => {
6559
6685
  const result = await context.updateSessionMcpApprovalPolicy(serverId, normalizedPolicy);
6560
6686
  if (!result.server) {
6561
- throw new HTTPException12(404, {
6687
+ throw new HTTPException11(404, {
6562
6688
  message: "session MCP server not found"
6563
6689
  });
6564
6690
  }
@@ -6638,18 +6764,27 @@ async function updateSessionToolPolicy(deps, grant, sessionId, request) {
6638
6764
  (tool) => !validatedIds.has(`${tool.kind}:${tool.id}`)
6639
6765
  );
6640
6766
  if (unknown) {
6641
- throw new HTTPException12(422, {
6767
+ throw new HTTPException11(422, {
6642
6768
  message: `unknown MCP server id: ${unknown.id}`
6643
6769
  });
6644
6770
  }
6645
6771
  return withFirstPartyTools(validatedTools, runtimeSettings);
6646
6772
  })() : null;
6647
6773
  const explicitRequestedFirstPartyTools = explicitRequest ? [...explicitRequest.firstPartyMcpTools] : null;
6774
+ const deploymentFirstPartyMcpToolPolicy = resolveFirstPartyMcpToolPolicy(deps.settings);
6775
+ const disallowedFirstPartyMcpTool = explicitRequestedFirstPartyTools?.find(
6776
+ (tool) => !deploymentFirstPartyMcpToolPolicy.allowed.includes(tool)
6777
+ );
6778
+ if (disallowedFirstPartyMcpTool) {
6779
+ throw new HTTPException11(422, {
6780
+ message: `first-party MCP tool is disabled by deployment policy: ${disallowedFirstPartyMcpTool}`
6781
+ });
6782
+ }
6648
6783
  const workspaceDefaultTools = withFirstPartyTools(
6649
6784
  withDefaultEnabledCapabilityMcpTools([], deps.settings, capabilityRuntimeSettings),
6650
6785
  runtimeSettings
6651
6786
  );
6652
- const workspaceDefaultFirstPartyTools = [...FIRST_PARTY_MCP_TOOL_NAMES];
6787
+ const workspaceDefaultFirstPartyTools = [...deploymentFirstPartyMcpToolPolicy.default];
6653
6788
  const events = await appendSessionEventsWithLockedSessionUpdate(
6654
6789
  deps.db,
6655
6790
  grant.workspaceId,
@@ -6665,7 +6800,7 @@ async function updateSessionToolPolicy(deps, grant, sessionId, request) {
6665
6800
  if (session.parentSessionId) {
6666
6801
  const parent = await context.getLockedSession(session.parentSessionId);
6667
6802
  if (!parent) {
6668
- throw new HTTPException12(409, {
6803
+ throw new HTTPException11(409, {
6669
6804
  message: "parent session is no longer available"
6670
6805
  });
6671
6806
  }
@@ -6678,12 +6813,15 @@ async function updateSessionToolPolicy(deps, grant, sessionId, request) {
6678
6813
  ) : parent.tools,
6679
6814
  runtimeSettings
6680
6815
  );
6816
+ const deploymentAllowedFirstPartyMcpTools = new Set(
6817
+ deploymentFirstPartyMcpToolPolicy.allowed
6818
+ );
6681
6819
  const parentFirstPartyMcpTools = [
6682
- ...parent.firstPartyMcpTools ?? DEFAULT_FIRST_PARTY_MCP_TOOLS
6683
- ];
6820
+ ...parent.firstPartyMcpTools ?? deploymentFirstPartyMcpToolPolicy.default
6821
+ ].filter((tool) => deploymentAllowedFirstPartyMcpTools.has(tool));
6684
6822
  if (requestedMode === "workspace_default") {
6685
6823
  if (!parentTracksWorkspaceDefaults) {
6686
- throw new HTTPException12(403, {
6824
+ throw new HTTPException11(403, {
6687
6825
  message: "a child may adopt workspace defaults only while its parent tracks workspace defaults"
6688
6826
  });
6689
6827
  }
@@ -6705,7 +6843,7 @@ async function updateSessionToolPolicy(deps, grant, sessionId, request) {
6705
6843
  (tool) => !parentFirstPartySet.has(tool)
6706
6844
  );
6707
6845
  if (widenedFirstPartyTool) {
6708
- throw new HTTPException12(403, {
6846
+ throw new HTTPException11(403, {
6709
6847
  message: `session OpenGeni tools may only narrow the parent policy: ${widenedFirstPartyTool}`
6710
6848
  });
6711
6849
  }
@@ -6723,7 +6861,7 @@ async function updateSessionToolPolicy(deps, grant, sessionId, request) {
6723
6861
  const currentPolicy = session.toolPolicy;
6724
6862
  const unchanged = stableJson4({
6725
6863
  tools: session.tools,
6726
- firstPartyMcpTools: session.firstPartyMcpTools ?? DEFAULT_FIRST_PARTY_MCP_TOOLS,
6864
+ firstPartyMcpTools: session.firstPartyMcpTools ?? deploymentFirstPartyMcpToolPolicy.default,
6727
6865
  policy: currentPolicy
6728
6866
  }) === stableJson4({
6729
6867
  tools: nextTools,
@@ -6742,7 +6880,7 @@ async function updateSessionToolPolicy(deps, grant, sessionId, request) {
6742
6880
  before: toolPolicyAuditSnapshot(
6743
6881
  session,
6744
6882
  session.tools,
6745
- [...session.firstPartyMcpTools ?? DEFAULT_FIRST_PARTY_MCP_TOOLS],
6883
+ [...session.firstPartyMcpTools ?? deploymentFirstPartyMcpToolPolicy.default],
6746
6884
  currentPolicy
6747
6885
  ),
6748
6886
  after: toolPolicyAuditSnapshot(
@@ -6781,13 +6919,13 @@ async function readSessionLineage(deps, grant, sessionId) {
6781
6919
  if (authorization?.relatedSessionAccess === "target") {
6782
6920
  const session = await getSession2(deps.db, grant.workspaceId, sessionId);
6783
6921
  if (!session) {
6784
- throw new HTTPException12(404, { message: "session not found" });
6922
+ throw new HTTPException11(404, { message: "session not found" });
6785
6923
  }
6786
6924
  return { ancestors: [], children: [], truncated: false };
6787
6925
  }
6788
6926
  const lineage = await getSessionLineage(deps.db, grant.workspaceId, sessionId);
6789
6927
  if (!lineage) {
6790
- throw new HTTPException12(404, { message: "session not found" });
6928
+ throw new HTTPException11(404, { message: "session not found" });
6791
6929
  }
6792
6930
  return lineage;
6793
6931
  }
@@ -6865,6 +7003,15 @@ async function createValidatedScheduledTask(input) {
6865
7003
  source: personalConnectionDelegationSourceForGrant(input.grant)
6866
7004
  });
6867
7005
  const creationInitiator = creationInitiatorForGrant(input.grant);
7006
+ const xaiProviderAccountAuthoritySnapshot = creationInitiator.actor ? await getSessionTurnXaiProviderAccountAuthoritySnapshot2(
7007
+ input.db,
7008
+ input.grant.workspaceId,
7009
+ creationInitiator.actor.sessionId,
7010
+ creationInitiator.actor.turnId
7011
+ ) : await resolveXaiProviderAccountAuthoritySnapshotForAcceptance(input.db, {
7012
+ workspaceId: input.grant.workspaceId,
7013
+ subjectId: input.grant.subjectId
7014
+ });
6868
7015
  return await createScheduledTask(input.db, {
6869
7016
  id,
6870
7017
  accountId: input.grant.accountId,
@@ -6881,6 +7028,7 @@ async function createValidatedScheduledTask(input) {
6881
7028
  ...creationInitiator.context ? { createdByContext: creationInitiator.context } : {},
6882
7029
  createdByActor: creationInitiator.actor ?? null,
6883
7030
  personalConnectionDelegations,
7031
+ xaiProviderAccountAuthoritySnapshot,
6884
7032
  targetSessionId: target?.id ?? null,
6885
7033
  variableSetId: input.payload.variableSetId ?? null,
6886
7034
  rigId: input.payload.rigId ?? null,
@@ -6890,20 +7038,20 @@ async function createValidatedScheduledTask(input) {
6890
7038
  async function validateScheduledTaskTarget(input) {
6891
7039
  if (input.runMode !== "existing_session") {
6892
7040
  if (input.targetSessionId) {
6893
- throw new HTTPException13(422, {
7041
+ throw new HTTPException12(422, {
6894
7042
  message: "targetSessionId requires runMode=existing_session"
6895
7043
  });
6896
7044
  }
6897
7045
  return null;
6898
7046
  }
6899
7047
  if (!input.targetSessionId) {
6900
- throw new HTTPException13(input.missingTargetStatus ?? 422, {
7048
+ throw new HTTPException12(input.missingTargetStatus ?? 422, {
6901
7049
  message: input.missingTargetStatus === 404 ? "target session not found" : "targetSessionId is required when runMode=existing_session"
6902
7050
  });
6903
7051
  }
6904
7052
  requirePermission(input.grant, "sessions:control");
6905
7053
  if (input.agentConfig.goal) {
6906
- throw new HTTPException13(422, {
7054
+ throw new HTTPException12(422, {
6907
7055
  message: "agentConfig.goal cannot be used with an existing-session target"
6908
7056
  });
6909
7057
  }
@@ -6922,39 +7070,39 @@ async function validateScheduledTaskTarget(input) {
6922
7070
  );
6923
7071
  } catch (error) {
6924
7072
  if (error instanceof SessionAuthorizationDeniedError) {
6925
- throw new HTTPException13(404, { message: "target session not found" });
7073
+ throw new HTTPException12(404, { message: "target session not found" });
6926
7074
  }
6927
7075
  if (error instanceof SessionAuthorizationUnavailableError) {
6928
- throw new HTTPException13(503, { message: "session authorization is unavailable" });
7076
+ throw new HTTPException12(503, { message: "session authorization is unavailable" });
6929
7077
  }
6930
7078
  throw error;
6931
7079
  }
6932
7080
  const session = await getSession3(input.db, input.grant.workspaceId, input.targetSessionId);
6933
7081
  if (!session || session.accountId !== input.grant.accountId) {
6934
- throw new HTTPException13(404, { message: "target session not found" });
7082
+ throw new HTTPException12(404, { message: "target session not found" });
6935
7083
  }
6936
7084
  if (session.status === "cancelled") {
6937
- throw new HTTPException13(409, {
7085
+ throw new HTTPException12(409, {
6938
7086
  message: "target session is cancelled; choose a revivable session"
6939
7087
  });
6940
7088
  }
6941
7089
  if ((session.variableSetId ?? null) !== (input.variableSetId ?? null)) {
6942
- throw new HTTPException13(422, {
7090
+ throw new HTTPException12(422, {
6943
7091
  message: "target session variableSet attachment does not match the scheduled task"
6944
7092
  });
6945
7093
  }
6946
7094
  if (input.rigId && input.rigId !== session.rigId) {
6947
- throw new HTTPException13(422, {
7095
+ throw new HTTPException12(422, {
6948
7096
  message: "target session rig does not match the scheduled task"
6949
7097
  });
6950
7098
  }
6951
7099
  if (input.agentConfig.sandboxBackend !== void 0 && input.agentConfig.sandboxBackend !== session.sandboxBackend) {
6952
- throw new HTTPException13(422, {
7100
+ throw new HTTPException12(422, {
6953
7101
  message: "target session sandbox backend does not match the scheduled task"
6954
7102
  });
6955
7103
  }
6956
7104
  if (scheduledSlackBotConnectionId(session.metadata) !== (input.agentConfig.slackBotConnectionId ?? null)) {
6957
- throw new HTTPException13(422, {
7105
+ throw new HTTPException12(422, {
6958
7106
  message: "target session OpenGeni Slack bot binding does not match the scheduled task"
6959
7107
  });
6960
7108
  }
@@ -6975,25 +7123,25 @@ function scheduledTaskRunForGrant(run, grant) {
6975
7123
  async function requireScheduledTaskRig(db, workspaceId, rigId) {
6976
7124
  const rig = await getRig4(db, workspaceId, rigId);
6977
7125
  if (!rig) {
6978
- throw new HTTPException13(422, { message: `unknown rigId: ${rigId}` });
7126
+ throw new HTTPException12(422, { message: `unknown rigId: ${rigId}` });
6979
7127
  }
6980
7128
  }
6981
7129
  async function validatedScheduledTaskUpdate(input) {
6982
7130
  const update = {};
6983
7131
  const existingKnowledge = input.existing.action.kind === "knowledge_source_sync";
6984
7132
  if (input.payload.action && input.payload.action.kind !== input.existing.action.kind) {
6985
- throw new HTTPException13(409, {
7133
+ throw new HTTPException12(409, {
6986
7134
  message: "scheduled task action kind is immutable; create a new schedule"
6987
7135
  });
6988
7136
  }
6989
7137
  if (existingKnowledge) {
6990
7138
  if (input.payload.agentConfig !== void 0 || input.payload.runMode !== void 0 || input.payload.targetSessionId !== void 0 || input.payload.variableSetId !== void 0 || input.payload.rigId !== void 0) {
6991
- throw new HTTPException13(422, {
7139
+ throw new HTTPException12(422, {
6992
7140
  message: "knowledge source schedules do not accept agent/session configuration"
6993
7141
  });
6994
7142
  }
6995
7143
  if (input.payload.overlapPolicy === "allow_concurrent") {
6996
- throw new HTTPException13(422, {
7144
+ throw new HTTPException12(422, {
6997
7145
  message: "knowledge source schedules require skip or buffer_one overlap"
6998
7146
  });
6999
7147
  }
@@ -7022,7 +7170,7 @@ async function validatedScheduledTaskUpdate(input) {
7022
7170
  const nextRunMode = input.payload.runMode ?? input.existing.runMode;
7023
7171
  const nextTargetSessionId = input.payload.targetSessionId !== void 0 ? input.payload.targetSessionId : nextRunMode === "existing_session" ? existingTarget : null;
7024
7172
  if (input.existing.runMode === "reusable_session" && input.existing.reusableSessionId && nextRunMode === "existing_session") {
7025
- throw new HTTPException13(409, {
7173
+ throw new HTTPException12(409, {
7026
7174
  message: "cannot target an existing session after this task created its reusable session; create a new task"
7027
7175
  });
7028
7176
  }
@@ -7048,7 +7196,7 @@ async function validatedScheduledTaskUpdate(input) {
7048
7196
  if (input.payload.variableSetId !== void 0) {
7049
7197
  const nextVariableSetId = input.payload.variableSetId;
7050
7198
  if ((input.existing.variableSetId ?? null) !== (nextVariableSetId ?? null) && input.existing.runMode === "reusable_session" && input.existing.reusableSessionId) {
7051
- throw new HTTPException13(409, {
7199
+ throw new HTTPException12(409, {
7052
7200
  message: "cannot change variableSet of a task with a live reusable session; recreate the task"
7053
7201
  });
7054
7202
  }
@@ -7088,7 +7236,7 @@ async function validatedScheduledTaskUpdate(input) {
7088
7236
  ...input.toolsProvided !== void 0 ? { toolsProvided: input.toolsProvided } : {}
7089
7237
  });
7090
7238
  if (input.existing.reusableSessionId && input.existing.runMode === "reusable_session" && (input.existing.agentConfig.slackBotConnectionId ?? null) !== (nextAgentConfig.slackBotConnectionId ?? null)) {
7091
- throw new HTTPException13(409, {
7239
+ throw new HTTPException12(409, {
7092
7240
  message: "cannot change the OpenGeni Slack bot connection of a task with a live reusable session; recreate the task"
7093
7241
  });
7094
7242
  }
@@ -7113,7 +7261,7 @@ async function validatedScheduledTaskUpdate(input) {
7113
7261
  input.existing.id
7114
7262
  );
7115
7263
  if (!personalConnectionDelegationsEqual(existingDelegations, personalConnectionDelegations)) {
7116
- throw new HTTPException13(409, {
7264
+ throw new HTTPException12(409, {
7117
7265
  message: "cannot change personal MCP connections of a task with a live reusable session; recreate the task"
7118
7266
  });
7119
7267
  }
@@ -7152,7 +7300,7 @@ async function validatedScheduledTaskUpdate(input) {
7152
7300
  async function requireScheduledTaskForApi(db, workspaceId, taskId) {
7153
7301
  const task = await getScheduledTask(db, workspaceId, taskId);
7154
7302
  if (!task) {
7155
- throw new HTTPException13(404, { message: "scheduled task not found" });
7303
+ throw new HTTPException12(404, { message: "scheduled task not found" });
7156
7304
  }
7157
7305
  return task;
7158
7306
  }
@@ -7185,12 +7333,12 @@ async function restoreScheduledTask(db, previous) {
7185
7333
  }
7186
7334
  async function validateKnowledgeSourceSyncAction(input) {
7187
7335
  if (input.action.initiatingSubjectId !== input.grant.subjectId) {
7188
- throw new HTTPException13(403, {
7336
+ throw new HTTPException12(403, {
7189
7337
  message: "knowledge source sync must preserve the exact initiating subject"
7190
7338
  });
7191
7339
  }
7192
7340
  if (input.action.connection.ownerSubjectId !== input.grant.subjectId) {
7193
- throw new HTTPException13(403, {
7341
+ throw new HTTPException12(403, {
7194
7342
  message: "knowledge source connection must belong to the initiating subject"
7195
7343
  });
7196
7344
  }
@@ -7201,10 +7349,10 @@ async function validateKnowledgeSourceSyncAction(input) {
7201
7349
  initiatingSubjectId: input.grant.subjectId
7202
7350
  });
7203
7351
  if (!resolved || resolved.source.lifecycleState !== "active") {
7204
- throw new HTTPException13(404, { message: "knowledge source not found" });
7352
+ throw new HTTPException12(404, { message: "knowledge source not found" });
7205
7353
  }
7206
7354
  if (resolved.source.syncGeneration !== input.action.sourceGeneration || resolved.source.lifecycleGeneration !== input.action.sourceLifecycleGeneration || scopedKnowledgeScopeKey(resolved.source.scope) !== scopedKnowledgeScopeKey(input.action.destination)) {
7207
- throw new HTTPException13(409, {
7355
+ throw new HTTPException12(409, {
7208
7356
  message: "knowledge source authority or generation changed"
7209
7357
  });
7210
7358
  }
@@ -7215,7 +7363,7 @@ async function validateKnowledgeSourceSyncAction(input) {
7215
7363
  input.grant.subjectId
7216
7364
  );
7217
7365
  if (!connection || connection.accountId !== input.grant.accountId || connection.workspaceId !== input.grant.workspaceId || connection.subjectId !== input.action.connection.ownerSubjectId || connection.version !== input.action.connection.connectionVersion || connection.providerDomain.toLowerCase() !== input.action.connection.providerDomain.toLowerCase() || connection.kind !== input.action.connection.kind || connection.status !== "active") {
7218
- throw new HTTPException13(409, {
7366
+ throw new HTTPException12(409, {
7219
7367
  message: "knowledge source connection authority changed or requires reconnect"
7220
7368
  });
7221
7369
  }
@@ -7285,16 +7433,16 @@ async function validateScheduledTaskAgentConfig(input) {
7285
7433
  const tools = input.toolsProvided ?? true ? requestedTools : withDefaultEnabledCapabilityMcpTools(requestedTools, input.settings, runtimeSettings);
7286
7434
  const prompt = input.payload.agentConfig.prompt.trim();
7287
7435
  if (!prompt) {
7288
- throw new HTTPException13(422, { message: "scheduled task prompt is required" });
7436
+ throw new HTTPException12(422, { message: "scheduled task prompt is required" });
7289
7437
  }
7290
7438
  if (hasReservedOpenGeniSlackBotSessionMetadata(input.payload.agentConfig.metadata)) {
7291
- throw new HTTPException13(422, {
7439
+ throw new HTTPException12(422, {
7292
7440
  message: `${OPENGENI_SLACK_BOT_SESSION_METADATA_KEY3} is reserved for scheduler routing`
7293
7441
  });
7294
7442
  }
7295
7443
  await validateGitHubRepositorySelection(input.db, input.workspaceId, resources);
7296
7444
  if (resources.some((resource) => resource.kind === "file") && !input.objectStorage) {
7297
- throw new HTTPException13(503, { message: "object storage is not configured" });
7445
+ throw new HTTPException12(503, { message: "object storage is not configured" });
7298
7446
  }
7299
7447
  await validateFileResources(input.db, input.workspaceId, resources);
7300
7448
  if (input.payload.agentConfig.slackBotConnectionId) {
@@ -7312,7 +7460,7 @@ async function validateScheduledTaskAgentConfig(input) {
7312
7460
  const deploymentPolicy = await getNestedAgentDepthDeploymentPolicy(input.db);
7313
7461
  const inheritedMaxDepth = typeof workspaceMaxDepth === "number" ? workspaceMaxDepth : deploymentPolicy.maxNestedAgentDepth;
7314
7462
  if (requestedMaxDepth > inheritedMaxDepth && !hasPermission(input.grant.permissions, "workspace:admin")) {
7315
- throw new HTTPException13(403, {
7463
+ throw new HTTPException12(403, {
7316
7464
  message: `scheduled task maxNestedAgentDepth ${requestedMaxDepth} exceeds inherited limit ${inheritedMaxDepth}; workspace:admin is required to increase it`
7317
7465
  });
7318
7466
  }
@@ -7330,13 +7478,13 @@ function validateScheduledTaskSchedule(schedule) {
7330
7478
  return;
7331
7479
  }
7332
7480
  if (new Date(schedule.startAt).getTime() >= new Date(schedule.endAt).getTime()) {
7333
- throw new HTTPException13(422, { message: "interval schedule endAt must be after startAt" });
7481
+ throw new HTTPException12(422, { message: "interval schedule endAt must be after startAt" });
7334
7482
  }
7335
7483
  }
7336
7484
  function trimmedScheduledTaskName(name) {
7337
7485
  const trimmed = name.trim();
7338
7486
  if (!trimmed) {
7339
- throw new HTTPException13(422, { message: "scheduled task name is required" });
7487
+ throw new HTTPException12(422, { message: "scheduled task name is required" });
7340
7488
  }
7341
7489
  return trimmed;
7342
7490
  }
@@ -7349,6 +7497,7 @@ import {
7349
7497
  import {
7350
7498
  aggregateModelCallFacts,
7351
7499
  aggregateModelCallFactsByDay,
7500
+ aggregateModelCallFactsByHour,
7352
7501
  aggregateRootSessionDrivers,
7353
7502
  aggregateScheduleFacts,
7354
7503
  aggregateSessionDepth,
@@ -7357,6 +7506,7 @@ import {
7357
7506
  countScheduledTaskFires,
7358
7507
  countSessionsAttachedToGroups,
7359
7508
  enumerateUtcDays,
7509
+ enumerateUtcHours,
7360
7510
  listFloorSessions,
7361
7511
  listLiveWarmLeases,
7362
7512
  listModelCallFacets,
@@ -7365,6 +7515,7 @@ import {
7365
7515
  requireWorkspace as requireWorkspace3,
7366
7516
  sumUsageQuantity as sumUsageQuantity2,
7367
7517
  sumUsageQuantityByDay,
7518
+ sumUsageQuantityByHour,
7368
7519
  sumUsageQuantityInRange
7369
7520
  } from "@opengeni/db";
7370
7521
  var MACHINE_HEARTBEAT_FRESH_MS = 12e4;
@@ -7396,29 +7547,29 @@ function resolveRangeWindow(range, now) {
7396
7547
  since = startOfUtcDay(now);
7397
7548
  rangeLabel = "Today (UTC)";
7398
7549
  priorLabel = "Prior equal window";
7399
- seriesLabel = "Credit $ (UTC day)";
7400
- cacheSeriesLabel = "Cache hit %";
7550
+ seriesLabel = "Credit $ / UTC hour";
7551
+ cacheSeriesLabel = "Cache hit % / UTC hour";
7401
7552
  break;
7402
7553
  case "week":
7403
7554
  since = new Date(startOfUtcDay(now).getTime() - 6 * 24 * 60 * 60 * 1e3);
7404
7555
  rangeLabel = "Last 7 days (UTC)";
7405
7556
  priorLabel = "Prior 7 days";
7406
- seriesLabel = "Credit $ / day";
7407
- cacheSeriesLabel = "Cache hit % / day";
7557
+ seriesLabel = "Credit $ / UTC day";
7558
+ cacheSeriesLabel = "Cache hit % / UTC day";
7408
7559
  break;
7409
7560
  case "month":
7410
7561
  since = startOfUtcMonth2(now);
7411
7562
  rangeLabel = "This month (UTC)";
7412
7563
  priorLabel = "Prior equal window";
7413
- seriesLabel = "Credit $ / day";
7414
- cacheSeriesLabel = "Cache hit % / day";
7564
+ seriesLabel = "Credit $ / UTC day";
7565
+ cacheSeriesLabel = "Cache hit % / UTC day";
7415
7566
  break;
7416
7567
  case "ytd":
7417
7568
  since = startOfUtcYear(now);
7418
7569
  rangeLabel = "Year to date (UTC)";
7419
7570
  priorLabel = "Prior equal window";
7420
- seriesLabel = "Credit $ / day";
7421
- cacheSeriesLabel = "Cache hit % / day";
7571
+ seriesLabel = "Credit $ / UTC day";
7572
+ cacheSeriesLabel = "Cache hit % / UTC day";
7422
7573
  break;
7423
7574
  default: {
7424
7575
  const _exhaustive = range;
@@ -7482,6 +7633,8 @@ async function getWorkspaceInsights(db, settings, input) {
7482
7633
  const model = input.model?.trim() || null;
7483
7634
  const modelFilterActive = Boolean(provider || model);
7484
7635
  const filter = { provider, model };
7636
+ const aggregateFactsForSeries = input.range === "today" ? aggregateModelCallFactsByHour : aggregateModelCallFactsByDay;
7637
+ const sumUsageForSeries = input.range === "today" ? sumUsageQuantityByHour : sumUsageQuantityByDay;
7485
7638
  const [
7486
7639
  workspaceCreditMicros,
7487
7640
  priorWorkspaceCreditMicros,
@@ -7541,19 +7694,19 @@ async function getWorkspaceInsights(db, settings, input) {
7541
7694
  until: window.priorUntil,
7542
7695
  ...filter
7543
7696
  }),
7544
- aggregateModelCallFactsByDay(db, {
7697
+ aggregateFactsForSeries(db, {
7545
7698
  workspaceId: input.workspaceId,
7546
7699
  since: window.since,
7547
7700
  until: window.until,
7548
7701
  ...filter
7549
7702
  }),
7550
- sumUsageQuantityByDay(db, {
7703
+ sumUsageForSeries(db, {
7551
7704
  workspaceId: input.workspaceId,
7552
7705
  eventType: "sandbox.warm_seconds",
7553
7706
  since: window.since,
7554
7707
  until: window.until
7555
7708
  }),
7556
- sumUsageQuantityByDay(db, {
7709
+ sumUsageForSeries(db, {
7557
7710
  workspaceId: input.workspaceId,
7558
7711
  eventType: "model.cost",
7559
7712
  since: window.since,
@@ -7673,9 +7826,9 @@ async function getWorkspaceInsights(db, settings, input) {
7673
7826
  const priorCachedTokens = priorModelRows.reduce((sum, row) => sum + row.cachedTokens, 0);
7674
7827
  const priorCacheInputTokens = priorModelRows.reduce((sum, row) => sum + row.cacheInputTokens, 0);
7675
7828
  const priorCalls = priorModelRows.reduce((sum, row) => sum + row.calls, 0);
7676
- const days = enumerateUtcDays(window.since, window.until);
7677
- const series = days.map((day) => {
7678
- const facts = factDays.get(day) ?? {
7829
+ const buckets = input.range === "today" ? enumerateUtcHours(window.since, window.until) : enumerateUtcDays(window.since, window.until);
7830
+ const series = buckets.map((bucket) => {
7831
+ const facts = factDays.get(bucket) ?? {
7679
7832
  costMicros: 0,
7680
7833
  estimatedProviderCostMicros: 0,
7681
7834
  estimatedProviderCostKnownCalls: 0,
@@ -7690,13 +7843,13 @@ async function getWorkspaceInsights(db, settings, input) {
7690
7843
  cacheKnownCalls: 0,
7691
7844
  calls: 0
7692
7845
  };
7693
- const modelCostMicros = modelFilterActive ? facts.costMicros : costDays.get(day) ?? facts.costMicros;
7846
+ const modelCostMicros = modelFilterActive ? facts.costMicros : costDays.get(bucket) ?? facts.costMicros;
7694
7847
  return {
7695
- label: day.slice(5),
7848
+ label: input.range === "today" ? bucket.slice(11) : bucket.slice(5),
7696
7849
  modelCostUsd: microsToUsd(modelCostMicros),
7697
7850
  estimatedProviderUsd: microsToUsd(facts.estimatedProviderCostMicros),
7698
7851
  estimatedProviderCostKnownCalls: facts.estimatedProviderCostKnownCalls,
7699
- warmSeconds: warmDays.get(day) ?? 0,
7852
+ warmSeconds: warmDays.get(bucket) ?? 0,
7700
7853
  inputTokens: facts.inputTokens,
7701
7854
  outputTokens: facts.outputTokens,
7702
7855
  cachedTokens: facts.cachedTokens,
@@ -8488,8 +8641,797 @@ function createCompanyProfileDurableLearningAdapter(options) {
8488
8641
  };
8489
8642
  }
8490
8643
 
8644
+ // src/domain/conversation-integrations.ts
8645
+ import { createHash as createHash5 } from "crypto";
8646
+ var CONVERSATION_INTEGRATION_SCHEMA_VERSION = 1;
8647
+ var CONVERSATION_PROVIDER_NAMESPACE_MAX_UTF8_BYTES = 128;
8648
+ var CONVERSATION_PROVIDER_OPAQUE_ID_MAX_UTF8_BYTES = 512;
8649
+ var CONVERSATION_TEXT_MAX_UTF8_BYTES = 32 * 1024;
8650
+ var CONVERSATION_ATTACHMENT_MAX_COUNT = 16;
8651
+ var CONVERSATION_ATTACHMENT_NAME_MAX_UTF8_BYTES = 1024;
8652
+ var CONVERSATION_ATTACHMENT_MEDIA_TYPE_MAX_UTF8_BYTES = 128;
8653
+ var CONVERSATION_OPERATION_KEY_MAX_UTF8_BYTES = 512;
8654
+ var CONVERSATION_OUTCOME_CODE_MAX_UTF8_BYTES = 128;
8655
+ var PROVIDER_NAMESPACE_PATTERN = /^[a-z0-9]+(?:[.-][a-z0-9]+)*$/u;
8656
+ var OUTCOME_CODE_PATTERN = /^[a-z0-9]+(?:[._-][a-z0-9]+)*$/u;
8657
+ var MEDIA_TYPE_PATTERN = /^[a-z0-9][a-z0-9!#$&^_.+-]*\/[a-z0-9][a-z0-9!#$&^_.+-]*$/iu;
8658
+ var SHA256_PATTERN = /^[0-9a-f]{64}$/u;
8659
+ var ABSOLUTE_URL_PATTERN = /^[a-z][a-z0-9+.-]*:\/\//iu;
8660
+ var textEncoder = new TextEncoder();
8661
+ function normalizeConversationInboundEnvelope(input) {
8662
+ assertExactObject(input, "inbound envelope", [
8663
+ "provider",
8664
+ "installationId",
8665
+ "actor",
8666
+ "route",
8667
+ "providerEventId",
8668
+ "providerMessageId",
8669
+ "occurredAt",
8670
+ "signal",
8671
+ "text",
8672
+ "attachments"
8673
+ ]);
8674
+ const provider = normalizeConversationProviderNamespace(input.provider);
8675
+ const installation = normalizeConversationInstallationIdentity({
8676
+ provider,
8677
+ providerInstallationId: input.installationId
8678
+ });
8679
+ const actor = normalizeConversationActor(installation, input.actor);
8680
+ const route = normalizeConversationRoute(installation, input.route);
8681
+ const providerEventId = providerOpaqueId(provider, "event", input.providerEventId);
8682
+ const providerMessageId = providerOpaqueId(provider, "message", input.providerMessageId);
8683
+ const occurredAt = canonicalTimestamp(input.occurredAt, "inbound occurredAt");
8684
+ const signal = normalizeSignal(input.signal);
8685
+ const text = boundedExactText(input.text, "inbound text", true);
8686
+ const rawAttachments = input.attachments ?? [];
8687
+ if (!Array.isArray(rawAttachments)) throw new TypeError("inbound attachments must be an array");
8688
+ if (rawAttachments.length > CONVERSATION_ATTACHMENT_MAX_COUNT) {
8689
+ throw new RangeError(
8690
+ `inbound attachments must contain at most ${CONVERSATION_ATTACHMENT_MAX_COUNT} entries`
8691
+ );
8692
+ }
8693
+ const attachments = Object.freeze(
8694
+ rawAttachments.map((attachment) => normalizeAttachment(provider, attachment))
8695
+ );
8696
+ if ((signal.kind === "start" || signal.kind === "continue") && !text && !attachments.length) {
8697
+ throw new TypeError("start and continue signals require text or an attachment reference");
8698
+ }
8699
+ return Object.freeze({
8700
+ schemaVersion: CONVERSATION_INTEGRATION_SCHEMA_VERSION,
8701
+ installation,
8702
+ actor,
8703
+ route,
8704
+ providerEventId,
8705
+ providerMessageId,
8706
+ eventIdentity: deriveConversationEventIdentity(installation, providerEventId),
8707
+ occurredAt,
8708
+ signal,
8709
+ text,
8710
+ attachments
8711
+ });
8712
+ }
8713
+ function normalizeConversationProviderNamespace(value) {
8714
+ return providerNamespace(value);
8715
+ }
8716
+ function normalizeConversationInstallationIdentity(input) {
8717
+ assertExactObject(input, "installation identity", ["provider", "providerInstallationId"]);
8718
+ return providerOpaqueId(
8719
+ providerNamespace(input.provider),
8720
+ "installation",
8721
+ input.providerInstallationId
8722
+ );
8723
+ }
8724
+ function normalizeConversationActor(installation, input) {
8725
+ assertConversationInstallationIdentity(installation);
8726
+ assertExactObject(input, "inbound actor", ["providerActorId", "kind"]);
8727
+ if (input.kind !== "human" && input.kind !== "bot" && input.kind !== "system") {
8728
+ throw new TypeError("inbound actor kind is unsupported");
8729
+ }
8730
+ const providerActorId = providerOpaqueId(installation.provider, "actor", input.providerActorId);
8731
+ return Object.freeze({
8732
+ installation,
8733
+ providerActorId,
8734
+ kind: input.kind,
8735
+ identity: deriveConversationActorIdentity(installation, providerActorId)
8736
+ });
8737
+ }
8738
+ function normalizeConversationRoute(installation, input) {
8739
+ assertConversationInstallationIdentity(installation);
8740
+ assertExactObject(input, "inbound route", ["providerConversationId", "providerThreadId"]);
8741
+ const providerConversationId = providerOpaqueId(
8742
+ installation.provider,
8743
+ "conversation",
8744
+ input.providerConversationId
8745
+ );
8746
+ const providerThreadId = input.providerThreadId === void 0 || input.providerThreadId === null ? null : providerOpaqueId(installation.provider, "thread", input.providerThreadId);
8747
+ const identity = deriveConversationRouteIdentity(
8748
+ installation,
8749
+ providerConversationId,
8750
+ providerThreadId
8751
+ );
8752
+ return Object.freeze({ installation, providerConversationId, providerThreadId, identity });
8753
+ }
8754
+ function normalizeConversationDeliveryCommand(input) {
8755
+ const keys = input.kind === "post" ? ["kind", "logicalOperationKey", "installation", "route", "text"] : input.kind === "update" ? [
8756
+ "kind",
8757
+ "logicalOperationKey",
8758
+ "installation",
8759
+ "route",
8760
+ "targetProviderMessageId",
8761
+ "text"
8762
+ ] : ["kind", "logicalOperationKey", "installation", "route", "targetProviderMessageId"];
8763
+ assertExactObject(input, "delivery command", keys);
8764
+ if (input.kind !== "post" && input.kind !== "update" && input.kind !== "delete") {
8765
+ throw new TypeError("delivery command kind is unsupported");
8766
+ }
8767
+ assertConversationInstallationIdentity(input.installation);
8768
+ assertConversationRoute(input.route);
8769
+ assertSameInstallation(input.installation, input.route.installation, "delivery route");
8770
+ const logicalOperationKey = boundedOpaqueString(
8771
+ input.logicalOperationKey,
8772
+ "logical operation key",
8773
+ CONVERSATION_OPERATION_KEY_MAX_UTF8_BYTES
8774
+ );
8775
+ const operationId = deriveConversationOperationIdentity(input.installation, logicalOperationKey);
8776
+ const targetProviderMessageId = input.kind === "post" ? null : providerOpaqueId(input.installation.provider, "message", input.targetProviderMessageId);
8777
+ const text = input.kind === "delete" ? null : boundedExactText(input.text, "delivery text", false);
8778
+ const requestDigest = deliveryRequestDigest({
8779
+ kind: input.kind,
8780
+ installation: input.installation,
8781
+ route: input.route,
8782
+ targetProviderMessageId,
8783
+ text
8784
+ });
8785
+ return Object.freeze({
8786
+ schemaVersion: CONVERSATION_INTEGRATION_SCHEMA_VERSION,
8787
+ operationId,
8788
+ logicalOperationKey,
8789
+ requestDigest,
8790
+ installation: input.installation,
8791
+ route: input.route,
8792
+ kind: input.kind,
8793
+ targetProviderMessageId,
8794
+ text
8795
+ });
8796
+ }
8797
+ function normalizeConversationProviderReceipt(input) {
8798
+ assertExactObject(input, "provider receipt", [
8799
+ "command",
8800
+ "providerMessageId",
8801
+ "providerReceiptId",
8802
+ "observedAt"
8803
+ ]);
8804
+ assertConversationDeliveryCommand(input.command);
8805
+ const providerMessageId = providerOpaqueId(
8806
+ input.command.installation.provider,
8807
+ "message",
8808
+ input.providerMessageId
8809
+ );
8810
+ if (input.command.targetProviderMessageId && providerMessageId.value !== input.command.targetProviderMessageId.value) {
8811
+ throw new TypeError("provider receipt message does not match the mutation target");
8812
+ }
8813
+ const providerReceiptId = input.providerReceiptId === void 0 || input.providerReceiptId === null ? null : providerOpaqueId(input.command.installation.provider, "receipt", input.providerReceiptId);
8814
+ return Object.freeze({
8815
+ schemaVersion: CONVERSATION_INTEGRATION_SCHEMA_VERSION,
8816
+ operationId: input.command.operationId,
8817
+ requestDigest: input.command.requestDigest,
8818
+ installation: input.command.installation,
8819
+ providerMessageId,
8820
+ providerReceiptId,
8821
+ observedAt: canonicalTimestamp(input.observedAt, "provider receipt observedAt")
8822
+ });
8823
+ }
8824
+ function normalizeConversationDeliveryOutcome(input) {
8825
+ if (input.status === "success") {
8826
+ assertExactObject(input, "delivery success", ["status", "receipt"]);
8827
+ assertConversationProviderReceipt(input.receipt);
8828
+ return Object.freeze({ status: "success", receipt: input.receipt });
8829
+ }
8830
+ if (input.status === "retryable_failure") {
8831
+ assertExactObject(input, "retryable delivery failure", ["status", "code", "retryAfterMs"]);
8832
+ const retryAfterMs = input.retryAfterMs ?? null;
8833
+ if (retryAfterMs !== null && (!Number.isSafeInteger(retryAfterMs) || retryAfterMs < 0)) {
8834
+ throw new RangeError("retryAfterMs must be a non-negative safe integer or null");
8835
+ }
8836
+ return Object.freeze({
8837
+ status: "retryable_failure",
8838
+ code: outcomeCode(input.code),
8839
+ retryAfterMs
8840
+ });
8841
+ }
8842
+ if (input.status !== "not_started" && input.status !== "unknown" && input.status !== "permanent_failure") {
8843
+ throw new TypeError("delivery outcome status is unsupported");
8844
+ }
8845
+ assertExactObject(input, "delivery outcome", ["status", "code"]);
8846
+ return Object.freeze({ status: input.status, code: outcomeCode(input.code) });
8847
+ }
8848
+ function conversationDeliveryNextAction(outcome) {
8849
+ assertConversationDeliveryOutcome(outcome);
8850
+ if (outcome.status === "unknown") return "reconcile";
8851
+ if (outcome.status === "not_started" || outcome.status === "retryable_failure") {
8852
+ return "retry_same_operation";
8853
+ }
8854
+ return outcome.status === "success" ? "complete" : "stop";
8855
+ }
8856
+ function assertConversationDeliveryMayRetry(outcome) {
8857
+ const action = conversationDeliveryNextAction(outcome);
8858
+ if (action === "retry_same_operation") return;
8859
+ if (action === "reconcile") {
8860
+ throw new Error("unknown delivery outcome must be reconciled before retry");
8861
+ }
8862
+ throw new Error(`delivery outcome ${outcome.status} may not be retried`);
8863
+ }
8864
+ function assertConversationDeliveryCommandMatches(expected, candidate) {
8865
+ assertConversationDeliveryCommand(expected);
8866
+ assertConversationDeliveryCommand(candidate);
8867
+ if (expected.operationId !== candidate.operationId) {
8868
+ throw new Error("delivery operation identity does not match");
8869
+ }
8870
+ if (expected.requestDigest !== candidate.requestDigest) {
8871
+ throw new Error("delivery operation identity was reused for different request content");
8872
+ }
8873
+ }
8874
+ function assertConversationInboundEnvelope(value) {
8875
+ assertExactObject(value, "normalized inbound envelope", [
8876
+ "schemaVersion",
8877
+ "installation",
8878
+ "actor",
8879
+ "route",
8880
+ "providerEventId",
8881
+ "providerMessageId",
8882
+ "eventIdentity",
8883
+ "occurredAt",
8884
+ "signal",
8885
+ "text",
8886
+ "attachments"
8887
+ ]);
8888
+ if (value.schemaVersion !== CONVERSATION_INTEGRATION_SCHEMA_VERSION) {
8889
+ throw new TypeError("inbound envelope schema version is unsupported");
8890
+ }
8891
+ assertConversationInstallationIdentity(value.installation);
8892
+ assertConversationActor(value.actor);
8893
+ assertConversationRoute(value.route);
8894
+ assertProviderOpaqueId(value.providerEventId, "event");
8895
+ assertProviderOpaqueId(value.providerMessageId, "message");
8896
+ assertSameInstallation(value.installation, value.actor.installation, "inbound actor");
8897
+ assertSameInstallation(value.installation, value.route.installation, "inbound route");
8898
+ assertSameProvider(value.installation.provider, value.providerEventId.provider, "event id");
8899
+ assertSameProvider(value.installation.provider, value.providerMessageId.provider, "message id");
8900
+ const expectedEventIdentity = deriveConversationEventIdentity(
8901
+ value.installation,
8902
+ value.providerEventId
8903
+ );
8904
+ if (value.eventIdentity !== expectedEventIdentity) {
8905
+ throw new TypeError("inbound event identity is not canonical");
8906
+ }
8907
+ canonicalTimestamp(value.occurredAt, "inbound occurredAt");
8908
+ const signal = normalizeSignal(value.signal);
8909
+ const text = boundedExactText(value.text, "inbound text", true);
8910
+ if (!Array.isArray(value.attachments))
8911
+ throw new TypeError("inbound attachments must be an array");
8912
+ if (value.attachments.length > CONVERSATION_ATTACHMENT_MAX_COUNT) {
8913
+ throw new RangeError("inbound attachment count exceeds the contract bound");
8914
+ }
8915
+ for (const attachment of value.attachments) {
8916
+ assertConversationAttachmentReference(attachment);
8917
+ assertSameProvider(
8918
+ value.installation.provider,
8919
+ attachment.providerAttachmentId.provider,
8920
+ "attachment id"
8921
+ );
8922
+ }
8923
+ if ((signal.kind === "start" || signal.kind === "continue") && !text && !value.attachments.length) {
8924
+ throw new TypeError("start and continue signals require text or an attachment reference");
8925
+ }
8926
+ }
8927
+ function assertConversationDeliveryCommand(value) {
8928
+ assertExactObject(value, "normalized delivery command", [
8929
+ "schemaVersion",
8930
+ "operationId",
8931
+ "logicalOperationKey",
8932
+ "requestDigest",
8933
+ "installation",
8934
+ "route",
8935
+ "kind",
8936
+ "targetProviderMessageId",
8937
+ "text"
8938
+ ]);
8939
+ if (value.schemaVersion !== CONVERSATION_INTEGRATION_SCHEMA_VERSION) {
8940
+ throw new TypeError("delivery command schema version is unsupported");
8941
+ }
8942
+ if (value.kind !== "post" && value.kind !== "update" && value.kind !== "delete") {
8943
+ throw new TypeError("delivery command kind is unsupported");
8944
+ }
8945
+ assertConversationInstallationIdentity(value.installation);
8946
+ assertConversationRoute(value.route);
8947
+ assertSameInstallation(value.installation, value.route.installation, "delivery route");
8948
+ const logicalOperationKey = boundedOpaqueString(
8949
+ value.logicalOperationKey,
8950
+ "logical operation key",
8951
+ CONVERSATION_OPERATION_KEY_MAX_UTF8_BYTES
8952
+ );
8953
+ if (value.operationId !== deriveConversationOperationIdentity(value.installation, logicalOperationKey)) {
8954
+ throw new TypeError("delivery operation identity is not canonical");
8955
+ }
8956
+ let target;
8957
+ let text;
8958
+ if (value.kind === "post") {
8959
+ if (value.targetProviderMessageId !== null) {
8960
+ throw new TypeError("post command cannot carry a target message");
8961
+ }
8962
+ target = null;
8963
+ text = boundedExactText(value.text, "delivery text", false);
8964
+ } else {
8965
+ assertProviderOpaqueId(value.targetProviderMessageId, "message");
8966
+ target = value.targetProviderMessageId;
8967
+ assertSameProvider(value.installation.provider, target.provider, "delivery target");
8968
+ if (value.kind === "delete") {
8969
+ if (value.text !== null) throw new TypeError("delete command cannot carry text");
8970
+ text = null;
8971
+ } else {
8972
+ text = boundedExactText(value.text, "delivery text", false);
8973
+ }
8974
+ }
8975
+ const expectedDigest = deliveryRequestDigest({
8976
+ kind: value.kind,
8977
+ installation: value.installation,
8978
+ route: value.route,
8979
+ targetProviderMessageId: target,
8980
+ text
8981
+ });
8982
+ if (value.requestDigest !== expectedDigest) {
8983
+ throw new TypeError("delivery request digest is not canonical");
8984
+ }
8985
+ }
8986
+ function assertConversationProviderReceipt(value) {
8987
+ assertExactObject(value, "normalized provider receipt", [
8988
+ "schemaVersion",
8989
+ "operationId",
8990
+ "requestDigest",
8991
+ "installation",
8992
+ "providerMessageId",
8993
+ "providerReceiptId",
8994
+ "observedAt"
8995
+ ]);
8996
+ if (value.schemaVersion !== CONVERSATION_INTEGRATION_SCHEMA_VERSION) {
8997
+ throw new TypeError("provider receipt schema version is unsupported");
8998
+ }
8999
+ assertIdentity(value.operationId, /^ciop1_[0-9a-f]{64}$/u, "provider receipt operation id");
9000
+ assertIdentity(value.requestDigest, /^sha256:[0-9a-f]{64}$/u, "provider receipt request digest");
9001
+ assertConversationInstallationIdentity(value.installation);
9002
+ assertProviderOpaqueId(value.providerMessageId, "message");
9003
+ assertSameProvider(
9004
+ value.installation.provider,
9005
+ value.providerMessageId.provider,
9006
+ "receipt message"
9007
+ );
9008
+ if (value.providerReceiptId !== null) {
9009
+ assertProviderOpaqueId(value.providerReceiptId, "receipt");
9010
+ assertSameProvider(
9011
+ value.installation.provider,
9012
+ value.providerReceiptId.provider,
9013
+ "provider receipt id"
9014
+ );
9015
+ }
9016
+ canonicalTimestamp(value.observedAt, "provider receipt observedAt");
9017
+ }
9018
+ function assertConversationDeliveryOutcome(value) {
9019
+ normalizeConversationDeliveryOutcome(value);
9020
+ }
9021
+ function canonicalConversationInboundEnvelopeJson(envelope) {
9022
+ assertConversationInboundEnvelope(envelope);
9023
+ return JSON.stringify({
9024
+ schemaVersion: envelope.schemaVersion,
9025
+ installation: providerIdWire(envelope.installation),
9026
+ actor: actorWire(envelope.actor),
9027
+ route: routeWire(envelope.route),
9028
+ providerEventId: providerIdWire(envelope.providerEventId),
9029
+ providerMessageId: providerIdWire(envelope.providerMessageId),
9030
+ eventIdentity: envelope.eventIdentity,
9031
+ occurredAt: envelope.occurredAt,
9032
+ signal: signalWire(envelope.signal),
9033
+ text: envelope.text,
9034
+ attachments: envelope.attachments.map(attachmentWire)
9035
+ });
9036
+ }
9037
+ function canonicalConversationDeliveryCommandJson(command) {
9038
+ assertConversationDeliveryCommand(command);
9039
+ return JSON.stringify(deliveryCommandWire(command));
9040
+ }
9041
+ function canonicalConversationProviderReceiptJson(receipt2) {
9042
+ assertConversationProviderReceipt(receipt2);
9043
+ return JSON.stringify(providerReceiptWire(receipt2));
9044
+ }
9045
+ function canonicalConversationDeliveryOutcomeJson(outcome) {
9046
+ assertConversationDeliveryOutcome(outcome);
9047
+ if (outcome.status === "success") {
9048
+ return JSON.stringify({ status: "success", receipt: providerReceiptWire(outcome.receipt) });
9049
+ }
9050
+ if (outcome.status === "retryable_failure") {
9051
+ return JSON.stringify({
9052
+ status: outcome.status,
9053
+ code: outcome.code,
9054
+ retryAfterMs: outcome.retryAfterMs
9055
+ });
9056
+ }
9057
+ return JSON.stringify({ status: outcome.status, code: outcome.code });
9058
+ }
9059
+ function normalizeSignal(input) {
9060
+ assertExactObject(input, "conversation signal", ["kind", "control"]);
9061
+ if (input.kind === "start" || input.kind === "continue") {
9062
+ assertExactObject(input, "conversation signal", ["kind"]);
9063
+ return Object.freeze({ kind: input.kind });
9064
+ }
9065
+ if (input.kind === "control") {
9066
+ assertExactObject(input, "conversation control signal", ["kind", "control"]);
9067
+ if (input.control !== "stop" && input.control !== "resume") {
9068
+ throw new TypeError("conversation control signal is unsupported");
9069
+ }
9070
+ return Object.freeze({ kind: "control", control: input.control });
9071
+ }
9072
+ throw new TypeError("conversation signal is unsupported");
9073
+ }
9074
+ function normalizeAttachment(provider, input) {
9075
+ assertExactObject(input, "attachment reference", [
9076
+ "providerAttachmentId",
9077
+ "fileName",
9078
+ "mediaType",
9079
+ "byteSize",
9080
+ "contentSha256"
9081
+ ]);
9082
+ if (ABSOLUTE_URL_PATTERN.test(input.providerAttachmentId) || input.providerAttachmentId.startsWith("//")) {
9083
+ throw new TypeError("attachment references cannot contain provider URLs");
9084
+ }
9085
+ const providerAttachmentId = providerOpaqueId(provider, "attachment", input.providerAttachmentId);
9086
+ const fileName = nullableBoundedString(
9087
+ input.fileName,
9088
+ "attachment fileName",
9089
+ CONVERSATION_ATTACHMENT_NAME_MAX_UTF8_BYTES
9090
+ );
9091
+ if (fileName?.includes("/") || fileName?.includes("\\")) {
9092
+ throw new TypeError("attachment fileName must not contain a path");
9093
+ }
9094
+ const mediaType = nullableBoundedString(
9095
+ input.mediaType,
9096
+ "attachment mediaType",
9097
+ CONVERSATION_ATTACHMENT_MEDIA_TYPE_MAX_UTF8_BYTES
9098
+ );
9099
+ if (mediaType !== null && !MEDIA_TYPE_PATTERN.test(mediaType)) {
9100
+ throw new TypeError("attachment mediaType is invalid");
9101
+ }
9102
+ const byteSize = input.byteSize ?? null;
9103
+ if (byteSize !== null && (!Number.isSafeInteger(byteSize) || byteSize < 0)) {
9104
+ throw new RangeError("attachment byteSize must be a non-negative safe integer or null");
9105
+ }
9106
+ const contentSha256 = input.contentSha256 ?? null;
9107
+ if (contentSha256 !== null && !SHA256_PATTERN.test(contentSha256)) {
9108
+ throw new TypeError("attachment contentSha256 must be 64 lowercase hexadecimal characters");
9109
+ }
9110
+ return Object.freeze({
9111
+ providerAttachmentId,
9112
+ fileName,
9113
+ mediaType,
9114
+ byteSize,
9115
+ contentSha256
9116
+ });
9117
+ }
9118
+ function providerNamespace(value) {
9119
+ const bounded = boundedOpaqueString(
9120
+ value,
9121
+ "provider namespace",
9122
+ CONVERSATION_PROVIDER_NAMESPACE_MAX_UTF8_BYTES
9123
+ );
9124
+ if (!PROVIDER_NAMESPACE_PATTERN.test(bounded)) {
9125
+ throw new TypeError("provider namespace must be canonical lowercase segments");
9126
+ }
9127
+ return bounded;
9128
+ }
9129
+ function providerOpaqueId(provider, kind, value) {
9130
+ return Object.freeze({
9131
+ provider,
9132
+ kind,
9133
+ value: boundedOpaqueString(
9134
+ value,
9135
+ `provider ${kind} id`,
9136
+ CONVERSATION_PROVIDER_OPAQUE_ID_MAX_UTF8_BYTES
9137
+ )
9138
+ });
9139
+ }
9140
+ function deriveConversationEventIdentity(installation, providerEventId) {
9141
+ return `ciev1_${hashParts("opengeni:conversation-event:v1", [
9142
+ installation.provider,
9143
+ installation.value,
9144
+ providerEventId.value
9145
+ ])}`;
9146
+ }
9147
+ function deriveConversationRouteIdentity(installation, conversationId, threadId) {
9148
+ return `cirt1_${hashParts("opengeni:conversation-route:v1", [
9149
+ installation.provider,
9150
+ installation.value,
9151
+ conversationId.value,
9152
+ threadId?.value ?? null
9153
+ ])}`;
9154
+ }
9155
+ function deriveConversationActorIdentity(installation, actorId) {
9156
+ return `ciac1_${hashParts("opengeni:conversation-actor:v1", [
9157
+ installation.provider,
9158
+ installation.value,
9159
+ actorId.value
9160
+ ])}`;
9161
+ }
9162
+ function deriveConversationOperationIdentity(installation, logicalOperationKey) {
9163
+ return `ciop1_${hashParts("opengeni:conversation-operation:v1", [
9164
+ installation.provider,
9165
+ installation.value,
9166
+ logicalOperationKey
9167
+ ])}`;
9168
+ }
9169
+ function deliveryRequestDigest(input) {
9170
+ return `sha256:${createHash5("sha256").update("opengeni:conversation-delivery-request:v1\0", "utf8").update(
9171
+ JSON.stringify({
9172
+ kind: input.kind,
9173
+ installation: providerIdWire(input.installation),
9174
+ route: routeWire(input.route),
9175
+ targetProviderMessageId: input.targetProviderMessageId ? providerIdWire(input.targetProviderMessageId) : null,
9176
+ text: input.text
9177
+ }),
9178
+ "utf8"
9179
+ ).digest("hex")}`;
9180
+ }
9181
+ function hashParts(domain, parts) {
9182
+ const hash = createHash5("sha256");
9183
+ hash.update(domain, "utf8").update("\0", "utf8");
9184
+ for (const part of parts) {
9185
+ if (part === null) {
9186
+ hash.update("n:", "utf8");
9187
+ continue;
9188
+ }
9189
+ const bytes = textEncoder.encode(part);
9190
+ hash.update(`s:${bytes.byteLength}:`, "utf8").update(bytes);
9191
+ }
9192
+ return hash.digest("hex");
9193
+ }
9194
+ function assertConversationInstallationIdentity(value) {
9195
+ assertProviderOpaqueId(value, "installation");
9196
+ }
9197
+ function assertConversationActor(value) {
9198
+ assertExactObject(value, "normalized conversation actor", [
9199
+ "installation",
9200
+ "providerActorId",
9201
+ "kind",
9202
+ "identity"
9203
+ ]);
9204
+ assertConversationInstallationIdentity(value.installation);
9205
+ assertProviderOpaqueId(value.providerActorId, "actor");
9206
+ if (value.kind !== "human" && value.kind !== "bot" && value.kind !== "system") {
9207
+ throw new TypeError("conversation actor kind is unsupported");
9208
+ }
9209
+ assertSameProvider(value.installation.provider, value.providerActorId.provider, "actor id");
9210
+ if (value.identity !== deriveConversationActorIdentity(value.installation, value.providerActorId)) {
9211
+ throw new TypeError("conversation actor identity is not canonical");
9212
+ }
9213
+ }
9214
+ function assertConversationRoute(value) {
9215
+ assertExactObject(value, "normalized conversation route", [
9216
+ "installation",
9217
+ "providerConversationId",
9218
+ "providerThreadId",
9219
+ "identity"
9220
+ ]);
9221
+ assertConversationInstallationIdentity(value.installation);
9222
+ assertProviderOpaqueId(value.providerConversationId, "conversation");
9223
+ if (value.providerThreadId !== null) assertProviderOpaqueId(value.providerThreadId, "thread");
9224
+ assertSameProvider(
9225
+ value.installation.provider,
9226
+ value.providerConversationId.provider,
9227
+ "conversation id"
9228
+ );
9229
+ if (value.providerThreadId) {
9230
+ assertSameProvider(value.installation.provider, value.providerThreadId.provider, "thread id");
9231
+ }
9232
+ if (value.identity !== deriveConversationRouteIdentity(
9233
+ value.installation,
9234
+ value.providerConversationId,
9235
+ value.providerThreadId
9236
+ )) {
9237
+ throw new TypeError("conversation route identity is not canonical");
9238
+ }
9239
+ }
9240
+ function assertConversationAttachmentReference(value) {
9241
+ assertExactObject(value, "normalized attachment reference", [
9242
+ "providerAttachmentId",
9243
+ "fileName",
9244
+ "mediaType",
9245
+ "byteSize",
9246
+ "contentSha256"
9247
+ ]);
9248
+ assertProviderOpaqueId(value.providerAttachmentId, "attachment");
9249
+ if (ABSOLUTE_URL_PATTERN.test(value.providerAttachmentId.value) || value.providerAttachmentId.value.startsWith("//")) {
9250
+ throw new TypeError("attachment references cannot contain provider URLs");
9251
+ }
9252
+ const fileName = nullableBoundedString(
9253
+ value.fileName,
9254
+ "attachment fileName",
9255
+ CONVERSATION_ATTACHMENT_NAME_MAX_UTF8_BYTES
9256
+ );
9257
+ if (fileName?.includes("/") || fileName?.includes("\\")) {
9258
+ throw new TypeError("attachment fileName must not contain a path");
9259
+ }
9260
+ const mediaType = nullableBoundedString(
9261
+ value.mediaType,
9262
+ "attachment mediaType",
9263
+ CONVERSATION_ATTACHMENT_MEDIA_TYPE_MAX_UTF8_BYTES
9264
+ );
9265
+ if (mediaType !== null && !MEDIA_TYPE_PATTERN.test(mediaType)) {
9266
+ throw new TypeError("attachment mediaType is invalid");
9267
+ }
9268
+ const byteSize = value.byteSize;
9269
+ if (byteSize !== null) {
9270
+ if (typeof byteSize !== "number" || !Number.isSafeInteger(byteSize) || byteSize < 0) {
9271
+ throw new RangeError("attachment byteSize must be a non-negative safe integer or null");
9272
+ }
9273
+ }
9274
+ const contentSha256 = value.contentSha256;
9275
+ if (contentSha256 !== null) {
9276
+ if (typeof contentSha256 !== "string" || !SHA256_PATTERN.test(contentSha256)) {
9277
+ throw new TypeError("attachment contentSha256 must be 64 lowercase hexadecimal characters");
9278
+ }
9279
+ }
9280
+ }
9281
+ function assertProviderOpaqueId(value, kind) {
9282
+ assertExactObject(value, `provider ${kind} id`, ["provider", "kind", "value"]);
9283
+ providerNamespace(value.provider);
9284
+ if (value.kind !== kind) throw new TypeError(`provider id must have kind ${kind}`);
9285
+ boundedOpaqueString(
9286
+ value.value,
9287
+ `provider ${kind} id`,
9288
+ CONVERSATION_PROVIDER_OPAQUE_ID_MAX_UTF8_BYTES
9289
+ );
9290
+ }
9291
+ function assertSameProvider(expected, actual, label) {
9292
+ if (expected !== actual) throw new TypeError(`${label} provider namespace does not match`);
9293
+ }
9294
+ function assertSameInstallation(expected, actual, label) {
9295
+ if (expected.provider !== actual.provider || expected.value !== actual.value) {
9296
+ throw new TypeError(`${label} installation identity does not match`);
9297
+ }
9298
+ }
9299
+ function boundedExactText(value, label, allowEmpty) {
9300
+ return boundedString(value, label, allowEmpty ? 0 : 1, CONVERSATION_TEXT_MAX_UTF8_BYTES, true);
9301
+ }
9302
+ function boundedOpaqueString(value, label, maxBytes) {
9303
+ const result = boundedString(value, label, 1, maxBytes, false);
9304
+ if (result !== result.trim())
9305
+ throw new TypeError(`${label} must not have surrounding whitespace`);
9306
+ return result;
9307
+ }
9308
+ function nullableBoundedString(value, label, maxBytes) {
9309
+ if (value === void 0 || value === null) return null;
9310
+ return boundedString(value, label, 1, maxBytes, false);
9311
+ }
9312
+ function boundedString(value, label, minBytes, maxBytes, allowNewlines) {
9313
+ if (typeof value !== "string") throw new TypeError(`${label} must be a string`);
9314
+ if (!isWellFormedUnicode(value)) throw new TypeError(`${label} must be well-formed Unicode`);
9315
+ if (value.includes("\0")) throw new TypeError(`${label} must not contain NUL`);
9316
+ if (!allowNewlines && /[\u0001-\u001f\u007f]/u.test(value)) {
9317
+ throw new TypeError(`${label} must not contain control characters`);
9318
+ }
9319
+ const byteLength = textEncoder.encode(value).byteLength;
9320
+ if (byteLength < minBytes || byteLength > maxBytes) {
9321
+ throw new RangeError(`${label} must contain ${minBytes}-${maxBytes} UTF-8 bytes`);
9322
+ }
9323
+ return value;
9324
+ }
9325
+ function isWellFormedUnicode(value) {
9326
+ for (let index = 0; index < value.length; index += 1) {
9327
+ const code = value.charCodeAt(index);
9328
+ if (code >= 55296 && code <= 56319) {
9329
+ const next = value.charCodeAt(index + 1);
9330
+ if (!(next >= 56320 && next <= 57343)) return false;
9331
+ index += 1;
9332
+ } else if (code >= 56320 && code <= 57343) {
9333
+ return false;
9334
+ }
9335
+ }
9336
+ return true;
9337
+ }
9338
+ function canonicalTimestamp(value, label) {
9339
+ const timestamp = boundedString(value, label, 20, 32, false);
9340
+ const milliseconds = Date.parse(timestamp);
9341
+ if (!Number.isFinite(milliseconds) || new Date(milliseconds).toISOString() !== timestamp) {
9342
+ throw new TypeError(`${label} must be a canonical UTC timestamp`);
9343
+ }
9344
+ return timestamp;
9345
+ }
9346
+ function outcomeCode(value) {
9347
+ const code = boundedOpaqueString(
9348
+ value,
9349
+ "delivery outcome code",
9350
+ CONVERSATION_OUTCOME_CODE_MAX_UTF8_BYTES
9351
+ );
9352
+ if (!OUTCOME_CODE_PATTERN.test(code)) {
9353
+ throw new TypeError("delivery outcome code must use canonical lowercase segments");
9354
+ }
9355
+ return code;
9356
+ }
9357
+ function assertExactObject(value, label, allowedKeys) {
9358
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
9359
+ throw new TypeError(`${label} must be an object`);
9360
+ }
9361
+ const prototype = Object.getPrototypeOf(value);
9362
+ if (prototype !== Object.prototype && prototype !== null) {
9363
+ throw new TypeError(`${label} must be a plain object`);
9364
+ }
9365
+ const allowed = new Set(allowedKeys);
9366
+ for (const key of Reflect.ownKeys(value)) {
9367
+ if (typeof key !== "string" || !allowed.has(key)) {
9368
+ throw new TypeError(`${label} contains an unsupported field`);
9369
+ }
9370
+ }
9371
+ }
9372
+ function assertIdentity(value, pattern, label) {
9373
+ if (typeof value !== "string" || !pattern.test(value)) {
9374
+ throw new TypeError(`${label} is invalid`);
9375
+ }
9376
+ }
9377
+ function providerIdWire(id) {
9378
+ return { provider: id.provider, kind: id.kind, value: id.value };
9379
+ }
9380
+ function actorWire(actor) {
9381
+ return {
9382
+ installation: providerIdWire(actor.installation),
9383
+ providerActorId: providerIdWire(actor.providerActorId),
9384
+ kind: actor.kind,
9385
+ identity: actor.identity
9386
+ };
9387
+ }
9388
+ function routeWire(route) {
9389
+ return {
9390
+ installation: providerIdWire(route.installation),
9391
+ providerConversationId: providerIdWire(route.providerConversationId),
9392
+ providerThreadId: route.providerThreadId ? providerIdWire(route.providerThreadId) : null,
9393
+ identity: route.identity
9394
+ };
9395
+ }
9396
+ function signalWire(signal) {
9397
+ return signal.kind === "control" ? { kind: "control", control: signal.control } : { kind: signal.kind };
9398
+ }
9399
+ function attachmentWire(attachment) {
9400
+ return {
9401
+ providerAttachmentId: providerIdWire(attachment.providerAttachmentId),
9402
+ fileName: attachment.fileName,
9403
+ mediaType: attachment.mediaType,
9404
+ byteSize: attachment.byteSize,
9405
+ contentSha256: attachment.contentSha256
9406
+ };
9407
+ }
9408
+ function deliveryCommandWire(command) {
9409
+ return {
9410
+ schemaVersion: command.schemaVersion,
9411
+ operationId: command.operationId,
9412
+ logicalOperationKey: command.logicalOperationKey,
9413
+ requestDigest: command.requestDigest,
9414
+ installation: providerIdWire(command.installation),
9415
+ route: routeWire(command.route),
9416
+ kind: command.kind,
9417
+ targetProviderMessageId: command.targetProviderMessageId ? providerIdWire(command.targetProviderMessageId) : null,
9418
+ text: command.text
9419
+ };
9420
+ }
9421
+ function providerReceiptWire(receipt2) {
9422
+ return {
9423
+ schemaVersion: receipt2.schemaVersion,
9424
+ operationId: receipt2.operationId,
9425
+ requestDigest: receipt2.requestDigest,
9426
+ installation: providerIdWire(receipt2.installation),
9427
+ providerMessageId: providerIdWire(receipt2.providerMessageId),
9428
+ providerReceiptId: receipt2.providerReceiptId ? providerIdWire(receipt2.providerReceiptId) : null,
9429
+ observedAt: receipt2.observedAt
9430
+ };
9431
+ }
9432
+
8491
9433
  // src/domain/workspace-members.ts
8492
- import { HTTPException as HTTPException14 } from "hono/http-exception";
9434
+ import { HTTPException as HTTPException13 } from "hono/http-exception";
8493
9435
  var MEMBER_ADMIN_PERMISSIONS = ["workspace:admin", "members:manage"];
8494
9436
  function memberCanAdminister(member) {
8495
9437
  return member.permissions.some((permission) => MEMBER_ADMIN_PERMISSIONS.includes(permission));
@@ -8499,25 +9441,25 @@ function isUserMember(member) {
8499
9441
  }
8500
9442
  function resolveMemberSubjectId(userId) {
8501
9443
  if (!userId) {
8502
- throw new HTTPException14(404, { message: "user is not registered" });
9444
+ throw new HTTPException13(404, { message: "user is not registered" });
8503
9445
  }
8504
9446
  return `user:${userId}`;
8505
9447
  }
8506
9448
  function assertWorkspaceMemberRemovable(input) {
8507
9449
  const { members, subjectId, callerSubjectId } = input;
8508
9450
  if (subjectId === callerSubjectId) {
8509
- throw new HTTPException14(409, { message: "you cannot remove your own membership" });
9451
+ throw new HTTPException13(409, { message: "you cannot remove your own membership" });
8510
9452
  }
8511
9453
  const target = members.find((member) => member.subjectId === subjectId);
8512
9454
  if (!target) {
8513
- throw new HTTPException14(404, { message: "member not found" });
9455
+ throw new HTTPException13(404, { message: "member not found" });
8514
9456
  }
8515
9457
  if (memberCanAdminister(target)) {
8516
9458
  const remainingAdmins = members.filter(
8517
9459
  (member) => member.subjectId !== subjectId && memberCanAdminister(member)
8518
9460
  );
8519
9461
  if (remainingAdmins.length === 0) {
8520
- throw new HTTPException14(409, {
9462
+ throw new HTTPException13(409, {
8521
9463
  message: "cannot remove the last member who can manage this workspace"
8522
9464
  });
8523
9465
  }
@@ -8525,10 +9467,10 @@ function assertWorkspaceMemberRemovable(input) {
8525
9467
  }
8526
9468
  function assertWorkspaceDeletable(input) {
8527
9469
  if (input.workspaceCountForAccount <= 1) {
8528
- throw new HTTPException14(409, { message: "cannot delete the account's only workspace" });
9470
+ throw new HTTPException13(409, { message: "cannot delete the account's only workspace" });
8529
9471
  }
8530
9472
  if (input.activeSessionCount > 0) {
8531
- throw new HTTPException14(409, {
9473
+ throw new HTTPException13(409, {
8532
9474
  message: "stop the workspace's running sessions before deleting it"
8533
9475
  });
8534
9476
  }
@@ -8538,7 +9480,7 @@ function assertWorkspaceDeletable(input) {
8538
9480
  import {
8539
9481
  GenerateVideoToolInput
8540
9482
  } from "@opengeni/contracts";
8541
- import { createHash as createHash5 } from "crypto";
9483
+ import { createHash as createHash6 } from "crypto";
8542
9484
  var VIDEO_GENERATION_ADAPTER_VERSION = "gateway-video-v4/1";
8543
9485
  var TERMINAL_STATES = /* @__PURE__ */ new Set([
8544
9486
  "completed",
@@ -8677,13 +9619,14 @@ function assertReferenceRoles(sourceMode, references) {
8677
9619
  }
8678
9620
  }
8679
9621
  function sha2562(prefix, ...parts) {
8680
- const hash = createHash5("sha256").update(prefix);
9622
+ const hash = createHash6("sha256").update(prefix);
8681
9623
  for (const part of parts) hash.update(part).update("\0");
8682
9624
  return hash.digest("hex");
8683
9625
  }
8684
9626
 
8685
9627
  // src/domain/video-generation-capabilities.ts
8686
9628
  import {
9629
+ GROK_IMAGINE_VIDEO_1_5_MODEL_ID,
8687
9630
  SEEDANCE_2_5_MODEL_ID,
8688
9631
  VideoGenerationCapabilities,
8689
9632
  VideoGenerationPolicy
@@ -8702,10 +9645,32 @@ var VIDEO_GENERATION_MODEL_CATALOG = Object.freeze([
8702
9645
  ]),
8703
9646
  resolutions: Object.freeze(["480p", "720p"]),
8704
9647
  aspectRatios: Object.freeze(["16:9", "4:3", "1:1", "3:4", "9:16", "21:9", "adaptive"]),
8705
- duration: Object.freeze({ minSeconds: 4, maxSeconds: 30, stepSeconds: 1 }),
9648
+ duration: Object.freeze({
9649
+ minSeconds: 4,
9650
+ maxSeconds: 30,
9651
+ stepSeconds: 1
9652
+ }),
9653
+ supportsAudio: true
9654
+ }),
9655
+ Object.freeze({
9656
+ modelId: GROK_IMAGINE_VIDEO_1_5_MODEL_ID,
9657
+ label: "Grok Imagine Video 1.5",
9658
+ providerLabel: "Connected SuperGrok",
9659
+ sourceModes: Object.freeze(["text", "first_frame", "image_reference"]),
9660
+ resolutions: Object.freeze(["480p", "720p"]),
9661
+ aspectRatios: Object.freeze(["16:9", "4:3", "1:1", "3:4", "9:16"]),
9662
+ duration: Object.freeze({
9663
+ minSeconds: 4,
9664
+ maxSeconds: 15,
9665
+ stepSeconds: 1
9666
+ }),
9667
+ // xAI emits audio but exposes no separate audio wire option.
8706
9668
  supportsAudio: true
8707
9669
  })
8708
9670
  ]);
9671
+ function videoGenerationModelSupportsFundingSource(modelId, fundingSource) {
9672
+ return modelId === GROK_IMAGINE_VIDEO_1_5_MODEL_ID ? fundingSource === "supergrok_subscription" : modelId === SEEDANCE_2_5_MODEL_ID ? fundingSource === "opengeni_credits" || fundingSource === "workspace_gateway" : false;
9673
+ }
8709
9674
  function defaultVideoGenerationPolicy() {
8710
9675
  return VideoGenerationPolicy.parse({
8711
9676
  schemaVersion: 1,
@@ -8718,7 +9683,9 @@ function defaultVideoGenerationPolicy() {
8718
9683
  function videoGenerationCapabilitiesForPolicy(input) {
8719
9684
  const policy = VideoGenerationPolicy.parse(input.policy);
8720
9685
  const enabled = new Set(policy.enabledModelIds);
8721
- const models = VIDEO_GENERATION_MODEL_CATALOG.filter((model) => enabled.has(model.modelId));
9686
+ const models = VIDEO_GENERATION_MODEL_CATALOG.filter(
9687
+ (model) => enabled.has(model.modelId) && videoGenerationModelSupportsFundingSource(model.modelId, policy.fundingSource)
9688
+ );
8722
9689
  if (models.length === 0 || policy.defaultModelId === null) {
8723
9690
  throw new Error("Video generation is disabled for this workspace");
8724
9691
  }
@@ -8751,7 +9718,7 @@ import {
8751
9718
  getEnrollment as getEnrollment4,
8752
9719
  getRig as getRig5,
8753
9720
  getSandbox as getSandbox4,
8754
- getVariableSet as getVariableSet5,
9721
+ getVariableSet as getVariableSet4,
8755
9722
  NewSessionDraftAccessError,
8756
9723
  newSessionDraftToolsProvided,
8757
9724
  publicNewSessionDraftOptions,
@@ -8759,7 +9726,7 @@ import {
8759
9726
  saveNewSessionDraftInTransaction,
8760
9727
  withWorkspaceSubjectRls
8761
9728
  } from "@opengeni/db";
8762
- import { HTTPException as HTTPException15 } from "hono/http-exception";
9729
+ import { HTTPException as HTTPException14 } from "hono/http-exception";
8763
9730
  function hasOwn(value, key) {
8764
9731
  return typeof value === "object" && value !== null && Object.hasOwn(value, key);
8765
9732
  }
@@ -8810,7 +9777,7 @@ async function hydrateNewSessionDraft(deps, grant, workspaceId, row) {
8810
9777
  }
8811
9778
  const options = { ...mapped.options };
8812
9779
  if (options.variableSetId) {
8813
- if (!hasPermission(grant.permissions, "variable-sets:use") || !await getVariableSet5(deps.db, workspaceId, options.variableSetId)) {
9780
+ if (!hasPermission(grant.permissions, "variable-sets:use") || !await getVariableSet4(deps.db, workspaceId, options.variableSetId)) {
8814
9781
  delete options.variableSetId;
8815
9782
  }
8816
9783
  }
@@ -8885,7 +9852,7 @@ async function saveActorNewSessionDraft(deps, grant, workspaceId, rawInput) {
8885
9852
  const tools = toolsProvided ? validateToolRefs(input.tools, runtimeSettings) : [];
8886
9853
  await validateGitHubRepositorySelection(deps.db, workspaceId, resources);
8887
9854
  if (resources.some((resource) => resource.kind === "file") && !deps.objectStorage) {
8888
- throw new HTTPException15(503, { message: "object storage is not configured" });
9855
+ throw new HTTPException14(503, { message: "object storage is not configured" });
8889
9856
  }
8890
9857
  await validateFileResources(deps.db, workspaceId, resources);
8891
9858
  assertConfiguredModel(deps.settings, input.model);
@@ -8920,7 +9887,7 @@ async function saveActorNewSessionDraft(deps, grant, workspaceId, rawInput) {
8920
9887
  return mapNewSessionDraft(saved);
8921
9888
  } catch (error) {
8922
9889
  if (error instanceof NewSessionDraftAccessError) {
8923
- throw new HTTPException15(403, { message: error.message });
9890
+ throw new HTTPException14(403, { message: error.message });
8924
9891
  }
8925
9892
  throw error;
8926
9893
  }
@@ -9465,6 +10432,15 @@ async function saveHumanComposerDraft(deps, context, input) {
9465
10432
  }
9466
10433
  export {
9467
10434
  CODEX_COMPACTION_V2_PROVIDER_LOCKED,
10435
+ CONVERSATION_ATTACHMENT_MAX_COUNT,
10436
+ CONVERSATION_ATTACHMENT_MEDIA_TYPE_MAX_UTF8_BYTES,
10437
+ CONVERSATION_ATTACHMENT_NAME_MAX_UTF8_BYTES,
10438
+ CONVERSATION_INTEGRATION_SCHEMA_VERSION,
10439
+ CONVERSATION_OPERATION_KEY_MAX_UTF8_BYTES,
10440
+ CONVERSATION_OUTCOME_CODE_MAX_UTF8_BYTES,
10441
+ CONVERSATION_PROVIDER_NAMESPACE_MAX_UTF8_BYTES,
10442
+ CONVERSATION_PROVIDER_OPAQUE_ID_MAX_UTF8_BYTES,
10443
+ CONVERSATION_TEXT_MAX_UTF8_BYTES,
9468
10444
  CodexCompactionV2ProviderLockedError,
9469
10445
  DEFAULT_MEMORY_SLACK_PUBLICATION_POLICY,
9470
10446
  EDITABLE_ARTIFACT_COMPACTION_BYTE_THRESHOLD,
@@ -9570,6 +10546,12 @@ export {
9570
10546
  assertBoundedKernelVersion,
9571
10547
  assertBoundedOpaqueReference,
9572
10548
  assertConfiguredModel,
10549
+ assertConversationDeliveryCommand,
10550
+ assertConversationDeliveryCommandMatches,
10551
+ assertConversationDeliveryMayRetry,
10552
+ assertConversationDeliveryOutcome,
10553
+ assertConversationInboundEnvelope,
10554
+ assertConversationProviderReceipt,
9573
10555
  assertIsoTimestamp,
9574
10556
  assertKnownVideoGenerationModelIds,
9575
10557
  assertNonnegativeSafeInteger,
@@ -9588,6 +10570,10 @@ export {
9588
10570
  buildFleetContextForSession,
9589
10571
  buildMarketingDailyAnalysisAgentConfig,
9590
10572
  canonicalConfiguredModel,
10573
+ canonicalConversationDeliveryCommandJson,
10574
+ canonicalConversationDeliveryOutcomeJson,
10575
+ canonicalConversationInboundEnvelopeJson,
10576
+ canonicalConversationProviderReceiptJson,
9591
10577
  canonicalVideoGenerationRequestJson,
9592
10578
  capabilityPackManifestDigest,
9593
10579
  capabilityPackRequiresInstallationPlan,
@@ -9603,6 +10589,7 @@ export {
9603
10589
  controlHumanSessionWorkstream,
9604
10590
  controlHumanSessionWorkstreamWithOutcome,
9605
10591
  controlHumanWorkspace,
10592
+ conversationDeliveryNextAction,
9606
10593
  correctWorkspaceMemoryWithSlackPublication,
9607
10594
  createAndStartSession,
9608
10595
  createAndStartSessionWithOutcome,
@@ -9660,6 +10647,7 @@ export {
9660
10647
  encodeEditableArtifactLiveServerWireFrame,
9661
10648
  evaluateMemorySlackPublication,
9662
10649
  executeRunOnSelfhostedMachine,
10650
+ fikenConnectionMetadata,
9663
10651
  filenameForMimeType,
9664
10652
  freezePersonalConnectionDelegations,
9665
10653
  getActorNewSessionDraft,
@@ -9669,6 +10657,7 @@ export {
9669
10657
  getWorkspaceInsights,
9670
10658
  hasLiteralPermission,
9671
10659
  hasPermission,
10660
+ hasReservedFikenMetadata,
9672
10661
  hasReservedOpenGeniSlackBotMetadata,
9673
10662
  hasReservedOpenGeniSlackBotSessionMetadata,
9674
10663
  hashEditableArtifactCreateRequest,
@@ -9678,6 +10667,7 @@ export {
9678
10667
  isAcceptedMimeType,
9679
10668
  isAuthoritativeGitHubRepositorySelectionError,
9680
10669
  isBuiltInCapabilityPack,
10670
+ isFikenConnection,
9681
10671
  isOpenGeniSlackBotConnection,
9682
10672
  isTerminalVideoGenerationState,
9683
10673
  isTrustedScheduledSlackBotSession,
@@ -9696,6 +10686,14 @@ export {
9696
10686
  mergeToolRefs,
9697
10687
  moveHumanQueuePrompt,
9698
10688
  nativeConnectionCapabilityRecommendations,
10689
+ normalizeConversationActor,
10690
+ normalizeConversationDeliveryCommand,
10691
+ normalizeConversationDeliveryOutcome,
10692
+ normalizeConversationInboundEnvelope,
10693
+ normalizeConversationInstallationIdentity,
10694
+ normalizeConversationProviderNamespace,
10695
+ normalizeConversationProviderReceipt,
10696
+ normalizeConversationRoute,
9699
10697
  normalizeMimeType,
9700
10698
  normalizeResources,
9701
10699
  normalizeVideoGenerationRequest,
@@ -9711,6 +10709,7 @@ export {
9711
10709
  portableSkillCapabilityId,
9712
10710
  portableSkillPluginKey,
9713
10711
  postUserMessageTurn,
10712
+ preferredFikenConnection,
9714
10713
  previewCapabilityPackInstallation,
9715
10714
  promoteSetupAppendChange,
9716
10715
  promoteVerifiedDefinitionEditChangeForApi,
@@ -9744,6 +10743,7 @@ export {
9744
10743
  requireVariableSetForApi,
9745
10744
  resolveCapabilityPack,
9746
10745
  resolveCodexAppsCredentialIdForRun,
10746
+ resolveFikenDefaultCompanySlug,
9747
10747
  resolveFirstPartyMcpToolsForCreate,
9748
10748
  resolveMemberSubjectId,
9749
10749
  resolveSessionToolPolicy,
@@ -9771,6 +10771,7 @@ export {
9771
10771
  searchCapabilityCatalogItems,
9772
10772
  selectedPersonalConnectionServers,
9773
10773
  sendAgentSessionMessage,
10774
+ sessionRlsActorForAuthorization,
9774
10775
  sessionSpawnDenialEnvelope,
9775
10776
  sessionWithEffectiveToolPolicy,
9776
10777
  settingsWithApiIntegrationServers,
@@ -9806,10 +10807,12 @@ export {
9806
10807
  videoGenerationAdmissionKey,
9807
10808
  videoGenerationCapabilitiesForPolicy,
9808
10809
  videoGenerationCapabilityRevision,
10810
+ videoGenerationModelSupportsFundingSource,
9809
10811
  videoGenerationProviderIdempotencyKey,
9810
10812
  videoGenerationRequestDigest,
9811
10813
  withDefaultEnabledCapabilityMcpTools,
9812
10814
  withFrozenPersonalConnectionDelegations,
10815
+ withResolvedSessionAuthorization,
9813
10816
  workflowIdForSession,
9814
10817
  workspaceSessionToolPolicyDefaultServerIds,
9815
10818
  workspaceSessionToolPolicyServerIds,