@opengeni/core 0.11.8 → 0.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -83,7 +83,8 @@ function routingEnabled(settings) {
83
83
  }
84
84
  function wrapChannelABoxWithRouting(services, ids, established) {
85
85
  const { db, settings, bus } = services;
86
- const beforeMutation = async ({
86
+ const homeLease = ids.homeLease;
87
+ const beforeMutation = homeLease ? async ({
87
88
  op,
88
89
  backend
89
90
  }) => {
@@ -99,15 +100,15 @@ function wrapChannelABoxWithRouting(services, ids, established) {
99
100
  sessionId: ids.sessionId,
100
101
  requestId: ids.directRequest.requestId,
101
102
  holderId: ids.directRequest.holderId,
102
- sandboxGroupId: ids.homeLease.sandboxGroupId,
103
+ sandboxGroupId: homeLease.sandboxGroupId,
103
104
  expectedEpoch: backend.leaseEpoch,
104
105
  expectedInstanceId: backend.providerInstanceId,
105
106
  routeTargetId: backend.sandboxId,
106
107
  routeEpoch: backend.activeEpoch,
107
108
  operation: op
108
109
  });
109
- };
110
- const afterMutation = async ({
110
+ } : void 0;
111
+ const afterMutation = homeLease ? async ({
111
112
  op,
112
113
  backend,
113
114
  admission,
@@ -133,7 +134,7 @@ function wrapChannelABoxWithRouting(services, ids, established) {
133
134
  kind: "direct",
134
135
  requestId: ids.directRequest.requestId,
135
136
  holderId: ids.directRequest.holderId,
136
- sandboxGroupId: ids.homeLease.sandboxGroupId,
137
+ sandboxGroupId: homeLease.sandboxGroupId,
137
138
  expectedEpoch: backend.leaseEpoch,
138
139
  expectedInstanceId: backend.providerInstanceId,
139
140
  routeTargetId: exactAdmission.routeTargetId,
@@ -148,7 +149,7 @@ function wrapChannelABoxWithRouting(services, ids, established) {
148
149
  sessionId: ids.sessionId,
149
150
  requestId: ids.directRequest.requestId,
150
151
  holderId: ids.directRequest.holderId,
151
- sandboxGroupId: ids.homeLease.sandboxGroupId,
152
+ sandboxGroupId: homeLease.sandboxGroupId,
152
153
  expectedEpoch: backend.leaseEpoch,
153
154
  expectedInstanceId: backend.providerInstanceId,
154
155
  routeTargetId: exactAdmission.routeTargetId,
@@ -157,8 +158,8 @@ function wrapChannelABoxWithRouting(services, ids, established) {
157
158
  operation: op,
158
159
  outcome
159
160
  });
160
- };
161
- const beforeProcessMutation = async ({
161
+ } : void 0;
162
+ const beforeProcessMutation = homeLease ? async ({
162
163
  op,
163
164
  process
164
165
  }) => await advanceWorkspaceGenerationForRetainedProcess(db, {
@@ -167,8 +168,8 @@ function wrapChannelABoxWithRouting(services, ids, established) {
167
168
  sessionId: ids.sessionId,
168
169
  processId: process.id,
169
170
  operation: op
170
- });
171
- const afterProcessMutation = async ({
171
+ }) : void 0;
172
+ const afterProcessMutation = homeLease ? async ({
172
173
  op,
173
174
  process,
174
175
  admission,
@@ -186,8 +187,8 @@ function wrapChannelABoxWithRouting(services, ids, established) {
186
187
  operation: op,
187
188
  outcome
188
189
  });
189
- };
190
- const settleProcess = async ({
190
+ } : void 0;
191
+ const settleProcess = homeLease ? async ({
191
192
  backend,
192
193
  process,
193
194
  proof
@@ -201,7 +202,9 @@ function wrapChannelABoxWithRouting(services, ids, established) {
201
202
  processId: process.id
202
203
  });
203
204
  if (!durable || durable.providerSessionId !== process.providerSessionId || durable.providerBackend !== backend.kind || durable.providerInstanceId !== backend.providerInstanceId || durable.leaseEpoch !== backend.leaseEpoch || durable.routeKind !== (backend.sandboxId === null ? "home" : "active") || durable.routeTargetId !== backend.sandboxId || durable.routeEpoch !== backend.activeEpoch) {
204
- throw new Error("API retained-process settlement lost its exact durable backend identity");
205
+ throw new Error(
206
+ "API retained-process settlement lost its exact durable backend identity"
207
+ );
205
208
  }
206
209
  await settleRetainedProcess(db, {
207
210
  accountId: ids.accountId,
@@ -214,7 +217,7 @@ function wrapChannelABoxWithRouting(services, ids, established) {
214
217
  reason: proof.reason,
215
218
  idleGraceMs: settings.sandboxIdleGraceMs
216
219
  });
217
- };
220
+ } : void 0;
218
221
  const resolver = makeActiveBackendResolver({
219
222
  workspaceId: ids.workspaceId,
220
223
  defaultBackend: established.session,
@@ -230,21 +233,34 @@ function wrapChannelABoxWithRouting(services, ids, established) {
230
233
  },
231
234
  controlRpcFactory: controlRpcFactory(bus),
232
235
  relay: relayConfigFromSettings(settings),
233
- resolveDefaultBackend: async () => ({
234
- session: established.session,
235
- sandboxId: null,
236
- kind: established.backendId,
237
- leaseEpoch: ids.homeLease.leaseEpoch,
238
- providerInstanceId: ids.homeLease.instanceId
239
- })
236
+ selfhostedTimeoutMs: settings.sandboxSelfhostedControlTimeoutMs,
237
+ selfhostedExecTimeoutMs: settings.sandboxSelfhostedExecTimeoutMs,
238
+ ...ids.pinnedSelfhosted ? {
239
+ pinnedSelfhosted: {
240
+ sandboxId: ids.pinnedSelfhosted.sandboxId,
241
+ epoch: ids.pinnedSelfhosted.epoch,
242
+ session: established.session
243
+ }
244
+ } : {},
245
+ ...homeLease ? {
246
+ resolveDefaultBackend: async () => ({
247
+ session: established.session,
248
+ sandboxId: null,
249
+ kind: established.backendId,
250
+ leaseEpoch: homeLease.leaseEpoch,
251
+ providerInstanceId: homeLease.instanceId
252
+ })
253
+ } : {}
240
254
  });
241
255
  const proxy = new RoutingSandboxSession({
242
256
  defaultResolved: {
243
257
  session: established.session,
244
258
  sandboxId: null,
245
259
  kind: established.backendId,
246
- leaseEpoch: ids.homeLease.leaseEpoch,
247
- providerInstanceId: ids.homeLease.instanceId
260
+ ...homeLease ? {
261
+ leaseEpoch: homeLease.leaseEpoch,
262
+ providerInstanceId: homeLease.instanceId
263
+ } : {}
248
264
  },
249
265
  readPointer: async () => {
250
266
  if (!routingEnabled(settings)) {
@@ -254,36 +270,38 @@ function wrapChannelABoxWithRouting(services, ids, established) {
254
270
  return pointer ?? { activeSandboxId: null, activeEpoch: 0 };
255
271
  },
256
272
  resolveActiveBackend: resolver,
257
- beforeMutation,
258
- afterMutation,
259
- beforeProcessMutation,
260
- afterProcessMutation,
261
- settleProcess,
262
- onDefaultBackendError: async ({ error }) => {
263
- if (!isProviderSandboxGoneDuringRoutedOperation(ids.homeLease.backend, error)) return null;
264
- const marked = await markWarmLeaseInstanceLost(db, {
265
- accountId: ids.accountId,
266
- workspaceId: ids.workspaceId,
267
- sandboxGroupId: ids.homeLease.sandboxGroupId,
268
- expectedEpoch: ids.homeLease.leaseEpoch,
269
- expectedInstanceId: ids.homeLease.instanceId,
270
- diagnostic: "provider_not_found_during_routed_operation"
271
- });
272
- if (marked.status === "marked" && bus) {
273
- await appendAndPublishEvents(db, bus, ids.workspaceId, ids.sessionId, [
274
- {
275
- type: "sandbox.box.lost",
276
- payload: { sandboxId: ids.homeLease.instanceId }
277
- }
278
- ]).catch(() => void 0);
273
+ ...beforeMutation ? { beforeMutation } : {},
274
+ ...afterMutation ? { afterMutation } : {},
275
+ ...beforeProcessMutation ? { beforeProcessMutation } : {},
276
+ ...afterProcessMutation ? { afterProcessMutation } : {},
277
+ ...settleProcess ? { settleProcess } : {},
278
+ ...homeLease ? {
279
+ onDefaultBackendError: async ({ error }) => {
280
+ if (!isProviderSandboxGoneDuringRoutedOperation(homeLease.backend, error)) return null;
281
+ const marked = await markWarmLeaseInstanceLost(db, {
282
+ accountId: ids.accountId,
283
+ workspaceId: ids.workspaceId,
284
+ sandboxGroupId: homeLease.sandboxGroupId,
285
+ expectedEpoch: homeLease.leaseEpoch,
286
+ expectedInstanceId: homeLease.instanceId,
287
+ diagnostic: "provider_not_found_during_routed_operation"
288
+ });
289
+ if (marked.status === "marked" && bus) {
290
+ await appendAndPublishEvents(db, bus, ids.workspaceId, ids.sessionId, [
291
+ {
292
+ type: "sandbox.box.lost",
293
+ payload: { sandboxId: homeLease.instanceId }
294
+ }
295
+ ]).catch(() => void 0);
296
+ }
297
+ const lease = marked.lease;
298
+ const restore = lease?.recovery.restore.status;
299
+ return {
300
+ leaseEpoch: lease?.leaseEpoch ?? homeLease.leaseEpoch,
301
+ recovery: marked.status === "stale" ? "superseded" : restore === "pending" ? "pending" : restore === "degraded" ? "degraded" : "unrecoverable"
302
+ };
279
303
  }
280
- const lease = marked.lease;
281
- const restore = lease?.recovery.restore.status;
282
- return {
283
- leaseEpoch: lease?.leaseEpoch ?? ids.homeLease.leaseEpoch,
284
- recovery: marked.status === "stale" ? "superseded" : restore === "pending" ? "pending" : restore === "degraded" ? "degraded" : "unrecoverable"
285
- };
286
- }
304
+ } : {}
287
305
  });
288
306
  return { ...established, session: proxy };
289
307
  }
@@ -3291,6 +3309,7 @@ function sessionWithEffectiveToolPolicy(session, workspaceServerIds, workspaceDe
3291
3309
  }
3292
3310
 
3293
3311
  // src/domain/scheduled-tasks.ts
3312
+ import { OPENGENI_SLACK_BOT_SESSION_METADATA_KEY as OPENGENI_SLACK_BOT_SESSION_METADATA_KEY3 } from "@opengeni/contracts";
3294
3313
  import {
3295
3314
  createScheduledTask,
3296
3315
  deleteScheduledTask,
@@ -3300,7 +3319,7 @@ import {
3300
3319
  requireWorkspace as requireWorkspace2,
3301
3320
  updateScheduledTask
3302
3321
  } from "@opengeni/db";
3303
- import { HTTPException as HTTPException10 } from "hono/http-exception";
3322
+ import { HTTPException as HTTPException11 } from "hono/http-exception";
3304
3323
 
3305
3324
  // src/domain/sessions.ts
3306
3325
  import { CODEX_MODEL_ID_PREFIX } from "@opengeni/codex";
@@ -3313,6 +3332,7 @@ import {
3313
3332
  import {
3314
3333
  CreateSessionRequest,
3315
3334
  DEFAULT_FIRST_PARTY_MCP_PERMISSIONS,
3335
+ OPENGENI_SLACK_BOT_SESSION_METADATA_KEY as OPENGENI_SLACK_BOT_SESSION_METADATA_KEY2,
3316
3336
  SessionSpawnDenial,
3317
3337
  ServiceTurnInitiator,
3318
3338
  ServiceTurnInitiatorContext,
@@ -3359,7 +3379,68 @@ import {
3359
3379
  publishDurableSessionEvents,
3360
3380
  publishDurableWorkspaceControlEvent
3361
3381
  } from "@opengeni/events";
3382
+ import { HTTPException as HTTPException10 } from "hono/http-exception";
3383
+
3384
+ // src/domain/slack-bot.ts
3385
+ import {
3386
+ OPENGENI_SLACK_BOT_CREDENTIAL_LABEL,
3387
+ OPENGENI_SLACK_BOT_REQUIRED_SCOPES,
3388
+ OPENGENI_SLACK_BOT_CREDENTIAL_ROLE,
3389
+ OPENGENI_SLACK_BOT_SESSION_METADATA_KEY,
3390
+ OpenGeniSlackBotConnectionMetadata
3391
+ } from "@opengeni/contracts";
3392
+ import {
3393
+ getConnectionMetadata as getConnectionMetadata2
3394
+ } from "@opengeni/db";
3362
3395
  import { HTTPException as HTTPException9 } from "hono/http-exception";
3396
+ function openGeniSlackBotMetadata(metadata) {
3397
+ const parsed = OpenGeniSlackBotConnectionMetadata.safeParse(metadata);
3398
+ return parsed.success ? parsed.data : null;
3399
+ }
3400
+ function isOpenGeniSlackBotConnection(connection) {
3401
+ const granted = new Set(connection.grantedScopes);
3402
+ return connection.verifiedInstallAt != null && connection.verifiedInstallVersion === connection.version && connection.subjectId === null && connection.providerDomain === "slack.com" && connection.kind === "app_install" && granted.size === OPENGENI_SLACK_BOT_REQUIRED_SCOPES.length && OPENGENI_SLACK_BOT_REQUIRED_SCOPES.every((scope) => granted.has(scope)) && openGeniSlackBotMetadata(connection.metadata)?.credentialRole === OPENGENI_SLACK_BOT_CREDENTIAL_ROLE;
3403
+ }
3404
+ function hasReservedOpenGeniSlackBotMetadata(metadata) {
3405
+ return metadata?.credentialRole === OPENGENI_SLACK_BOT_CREDENTIAL_ROLE || metadata?.credentialLabel === OPENGENI_SLACK_BOT_CREDENTIAL_LABEL;
3406
+ }
3407
+ function hasReservedOpenGeniSlackBotSessionMetadata(metadata) {
3408
+ return Boolean(
3409
+ metadata && Object.prototype.hasOwnProperty.call(metadata, OPENGENI_SLACK_BOT_SESSION_METADATA_KEY)
3410
+ );
3411
+ }
3412
+ async function requireOpenGeniSlackBotConnection(db, workspaceId, connectionId) {
3413
+ const connection = await getConnectionMetadata2(db, workspaceId, connectionId, null);
3414
+ if (!connection || !isOpenGeniSlackBotConnection(connection)) {
3415
+ throw new HTTPException9(422, {
3416
+ message: "slackBotConnectionId must reference an OpenGeni Slack bot connection"
3417
+ });
3418
+ }
3419
+ if (connection.status !== "active") {
3420
+ throw new HTTPException9(422, {
3421
+ message: `OpenGeni Slack bot connection is not active (${connection.status})`
3422
+ });
3423
+ }
3424
+ return connection;
3425
+ }
3426
+ async function validateOpenGeniSlackBotConnectionSelection(db, grant, workspaceId, connectionId) {
3427
+ requirePermission(grant, "connections:read");
3428
+ return await requireOpenGeniSlackBotConnection(db, workspaceId, connectionId);
3429
+ }
3430
+ function scheduledSlackBotConnectionId(metadata) {
3431
+ const value = metadata?.[OPENGENI_SLACK_BOT_SESSION_METADATA_KEY];
3432
+ return typeof value === "string" && zUuid(value) ? value : null;
3433
+ }
3434
+ function isTrustedScheduledSlackBotSession(session) {
3435
+ const scheduledTaskId = session.metadata.scheduledTaskId;
3436
+ const scheduledTaskRunId = session.metadata.scheduledTaskRunId;
3437
+ return session.createdBy?.kind === "service" && session.createdBy.subjectId === "scheduler" && typeof scheduledTaskId === "string" && zUuid(scheduledTaskId) && typeof scheduledTaskRunId === "string" && zUuid(scheduledTaskRunId) && session.createdByContext.scheduledTaskId === scheduledTaskId && session.createdByContext.scheduledTaskRunId === scheduledTaskRunId && scheduledSlackBotConnectionId(session.metadata) !== null;
3438
+ }
3439
+ function zUuid(value) {
3440
+ return /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(value);
3441
+ }
3442
+
3443
+ // src/domain/sessions.ts
3363
3444
  var reservedSessionMcpServerIds = /* @__PURE__ */ new Set(["opengeni", "files", "docs", "codex_apps"]);
3364
3445
  var maxSessionMcpCredentialHeaders = 16;
3365
3446
  var maxSessionMcpCredentialHeaderValueLength = 4096;
@@ -3391,7 +3472,7 @@ function sessionSpawnDenialEnvelope(error) {
3391
3472
  function serviceInitiatorForGrant(grant) {
3392
3473
  if (!grant.serviceInitiator) {
3393
3474
  if (grant.serviceInitiatorContext) {
3394
- throw new HTTPException9(403, {
3475
+ throw new HTTPException10(403, {
3395
3476
  message: "service initiator context requires a signed service initiator"
3396
3477
  });
3397
3478
  }
@@ -3399,13 +3480,13 @@ function serviceInitiatorForGrant(grant) {
3399
3480
  }
3400
3481
  const initiator = ServiceTurnInitiator.safeParse(grant.serviceInitiator);
3401
3482
  if (!initiator.success) {
3402
- throw new HTTPException9(403, {
3483
+ throw new HTTPException10(403, {
3403
3484
  message: "a delegated command initiator must be a bounded service principal"
3404
3485
  });
3405
3486
  }
3406
3487
  const context = ServiceTurnInitiatorContext.safeParse(grant.serviceInitiatorContext ?? {});
3407
3488
  if (!context.success) {
3408
- throw new HTTPException9(403, {
3489
+ throw new HTTPException10(403, {
3409
3490
  message: "delegated service initiator context is invalid or reserved"
3410
3491
  });
3411
3492
  }
@@ -3413,7 +3494,7 @@ function serviceInitiatorForGrant(grant) {
3413
3494
  const callerAttemptId = grant.metadata?.["attemptId"];
3414
3495
  const callerExecutionGeneration = grant.metadata?.["executionGeneration"];
3415
3496
  if (callerTurnId !== void 0 || callerAttemptId !== void 0 || callerExecutionGeneration !== void 0) {
3416
- throw new HTTPException9(403, {
3497
+ throw new HTTPException10(403, {
3417
3498
  message: "a service initiator cannot replace an exact agent-attempt initiator"
3418
3499
  });
3419
3500
  }
@@ -3431,7 +3512,7 @@ function creationInitiatorForGrant(grant) {
3431
3512
  const hasCallerTurnClaim = callerTurnId !== void 0 || callerAttemptId !== void 0 || callerExecutionGeneration !== void 0;
3432
3513
  if (hasCallerTurnClaim) {
3433
3514
  if (typeof callerSessionId !== "string" || typeof callerTurnId !== "string" || typeof callerAttemptId !== "string" || typeof callerExecutionGeneration !== "number" || !Number.isSafeInteger(callerExecutionGeneration) || callerExecutionGeneration < 1) {
3434
- throw new HTTPException9(403, { message: "caller attempt claims are incomplete" });
3515
+ throw new HTTPException10(403, { message: "caller attempt claims are incomplete" });
3435
3516
  }
3436
3517
  const actor = {
3437
3518
  type: "agent_attempt",
@@ -3460,27 +3541,27 @@ function normalizedSessionMcpCredentialHeaders(headers) {
3460
3541
  }
3461
3542
  const entries = Object.entries(headers).map(([name, value]) => [name.trim(), value]).filter(([name]) => name.length > 0);
3462
3543
  if (entries.length > maxSessionMcpCredentialHeaders) {
3463
- throw new HTTPException9(422, {
3544
+ throw new HTTPException10(422, {
3464
3545
  message: `a session MCP server supports at most ${maxSessionMcpCredentialHeaders} credential headers`
3465
3546
  });
3466
3547
  }
3467
3548
  const seen = /* @__PURE__ */ new Set();
3468
3549
  for (const [name, value] of entries) {
3469
3550
  if (!sessionMcpCredentialHeaderName.test(name)) {
3470
- throw new HTTPException9(422, { message: `invalid credential header name: ${name}` });
3551
+ throw new HTTPException10(422, { message: `invalid credential header name: ${name}` });
3471
3552
  }
3472
3553
  const lower = name.toLowerCase();
3473
3554
  if (seen.has(lower)) {
3474
- throw new HTTPException9(422, { message: `duplicate credential header name: ${name}` });
3555
+ throw new HTTPException10(422, { message: `duplicate credential header name: ${name}` });
3475
3556
  }
3476
3557
  seen.add(lower);
3477
3558
  if (value.length === 0 || value.length > maxSessionMcpCredentialHeaderValueLength) {
3478
- throw new HTTPException9(422, {
3559
+ throw new HTTPException10(422, {
3479
3560
  message: `credential header ${name} must be 1-${maxSessionMcpCredentialHeaderValueLength} characters`
3480
3561
  });
3481
3562
  }
3482
3563
  if (/[\u0000-\u0008\u000A-\u001F\u007F]/.test(value)) {
3483
- throw new HTTPException9(422, {
3564
+ throw new HTTPException10(422, {
3484
3565
  message: `credential header ${name} contains forbidden control characters`
3485
3566
  });
3486
3567
  }
@@ -3547,11 +3628,11 @@ function validateSessionMcpServersForCreate(settings, grant, servers) {
3547
3628
  const metadata = [];
3548
3629
  for (const server of servers) {
3549
3630
  if (seenIds.has(server.id)) {
3550
- throw new HTTPException9(422, { message: `duplicate session MCP server id: ${server.id}` });
3631
+ throw new HTTPException10(422, { message: `duplicate session MCP server id: ${server.id}` });
3551
3632
  }
3552
3633
  seenIds.add(server.id);
3553
3634
  if (reservedSessionMcpServerIds.has(server.id) || existingIds.has(server.id)) {
3554
- throw new HTTPException9(422, { message: `MCP server id already exists: ${server.id}` });
3635
+ throw new HTTPException10(422, { message: `MCP server id already exists: ${server.id}` });
3555
3636
  }
3556
3637
  const headers = normalizedSessionMcpCredentialHeaders(server.headers);
3557
3638
  const headersEncrypted = Object.fromEntries(
@@ -3591,13 +3672,13 @@ function validateInheritedSessionMcpServersForCreate(servers) {
3591
3672
  const seenIds = /* @__PURE__ */ new Set();
3592
3673
  for (const server of servers) {
3593
3674
  if (seenIds.has(server.id)) {
3594
- throw new HTTPException9(422, {
3675
+ throw new HTTPException10(422, {
3595
3676
  message: `duplicate inherited session MCP server id: ${server.id}`
3596
3677
  });
3597
3678
  }
3598
3679
  seenIds.add(server.id);
3599
3680
  if (reservedSessionMcpServerIds.has(server.id)) {
3600
- throw new HTTPException9(422, {
3681
+ throw new HTTPException10(422, {
3601
3682
  message: `reserved inherited session MCP server id: ${server.id}`
3602
3683
  });
3603
3684
  }
@@ -3629,13 +3710,13 @@ function validateSessionMcpCredentialUpdates(input) {
3629
3710
  const seenIds = /* @__PURE__ */ new Set();
3630
3711
  const encryptedUpdates = input.updates.map((update) => {
3631
3712
  if (seenIds.has(update.id)) {
3632
- throw new HTTPException9(422, {
3713
+ throw new HTTPException10(422, {
3633
3714
  message: `duplicate session MCP credential update id: ${update.id}`
3634
3715
  });
3635
3716
  }
3636
3717
  seenIds.add(update.id);
3637
3718
  if (!knownIds.has(update.id)) {
3638
- throw new HTTPException9(422, { message: `unknown session MCP server id: ${update.id}` });
3719
+ throw new HTTPException10(422, { message: `unknown session MCP server id: ${update.id}` });
3639
3720
  }
3640
3721
  const headers = normalizedSessionMcpCredentialHeaders(update.headers);
3641
3722
  return {
@@ -3739,7 +3820,7 @@ async function createAndStartSession(input) {
3739
3820
  async function finishStartSession(input, session) {
3740
3821
  if (input.seedTargetSandbox) {
3741
3822
  if (session.sandboxBackend === "none") {
3742
- throw new HTTPException9(422, {
3823
+ throw new HTTPException10(422, {
3743
3824
  message: "cannot target a machine for a session with no sandbox (backend: none)"
3744
3825
  });
3745
3826
  }
@@ -3759,7 +3840,7 @@ async function finishStartSession(input, session) {
3759
3840
  input.seedTargetSandbox.workingDir ?? null
3760
3841
  );
3761
3842
  if (!seeded.swapped) {
3762
- throw new HTTPException9(422, {
3843
+ throw new HTTPException10(422, {
3763
3844
  message: `cannot target sandbox ${input.seedTargetSandbox.sandboxId}: ${seeded.reason ?? "target is not attachable"}`
3764
3845
  });
3765
3846
  }
@@ -3811,7 +3892,7 @@ function canonicalConfiguredModel(settings, model) {
3811
3892
  if (settings.codexSubscriptionEnabled && canonicalModel.startsWith(CODEX_MODEL_ID_PREFIX)) {
3812
3893
  return canonicalModel;
3813
3894
  }
3814
- throw new HTTPException9(422, { message: `model is not available: ${model}` });
3895
+ throw new HTTPException10(422, { message: `model is not available: ${model}` });
3815
3896
  }
3816
3897
  function assertConfiguredModel(settings, model) {
3817
3898
  canonicalConfiguredModel(settings, model);
@@ -3834,7 +3915,7 @@ async function assertWorkspaceModelPolicyAllows(db, settings, workspaceId, model
3834
3915
  modelId: canonicalModel
3835
3916
  });
3836
3917
  if (!verdict.allowed) {
3837
- throw new HTTPException9(422, {
3918
+ throw new HTTPException10(422, {
3838
3919
  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`
3839
3920
  });
3840
3921
  }
@@ -3842,10 +3923,10 @@ async function assertWorkspaceModelPolicyAllows(db, settings, workspaceId, model
3842
3923
  async function requireQueuedTurnForApi(db, workspaceId, sessionId, turnId) {
3843
3924
  const turn = await getSessionTurn(db, workspaceId, turnId);
3844
3925
  if (!turn || turn.sessionId !== sessionId) {
3845
- throw new HTTPException9(404, { message: "session turn not found" });
3926
+ throw new HTTPException10(404, { message: "session turn not found" });
3846
3927
  }
3847
3928
  if (turn.status !== "queued") {
3848
- throw new HTTPException9(409, {
3929
+ throw new HTTPException10(409, {
3849
3930
  message: `turn is ${turn.status}; only queued turns can be changed`
3850
3931
  });
3851
3932
  }
@@ -3898,13 +3979,13 @@ async function postUserMessageTurn(input) {
3898
3979
  );
3899
3980
  } catch (error) {
3900
3981
  if (error instanceof QueueCommandConflictError || error instanceof SessionControlConflictError) {
3901
- throw new HTTPException9(409, { message: error.message });
3982
+ throw new HTTPException10(409, { message: error.message });
3902
3983
  }
3903
3984
  if (error instanceof Error && error.message.includes("cancelled")) {
3904
- throw new HTTPException9(409, { message: error.message });
3985
+ throw new HTTPException10(409, { message: error.message });
3905
3986
  }
3906
3987
  if (error instanceof Error && error.message.startsWith("Unknown session MCP server")) {
3907
- throw new HTTPException9(422, { message: error.message });
3988
+ throw new HTTPException10(422, { message: error.message });
3908
3989
  }
3909
3990
  throw error;
3910
3991
  }
@@ -3957,6 +4038,11 @@ async function postUserMessageTurn(input) {
3957
4038
  async function createSessionForRequest(deps, grant, workspaceId, rawPayload) {
3958
4039
  const { settings, db, bus, workflowClient, objectStorage } = deps;
3959
4040
  const payload = CreateSessionRequest.parse(rawPayload);
4041
+ if (hasReservedOpenGeniSlackBotSessionMetadata(payload.metadata)) {
4042
+ throw new HTTPException10(422, {
4043
+ message: `${OPENGENI_SLACK_BOT_SESSION_METADATA_KEY2} is reserved for scheduler routing`
4044
+ });
4045
+ }
3960
4046
  if (payload.idempotencyKey) {
3961
4047
  const denial = await getSessionSpawnDenialByIdempotencyKey(
3962
4048
  db,
@@ -3977,7 +4063,7 @@ async function createSessionForRequest(deps, grant, workspaceId, rawPayload) {
3977
4063
  }
3978
4064
  const parentSession = parentSessionId ? await getSession2(db, workspaceId, parentSessionId) : null;
3979
4065
  if (parentSessionId && !parentSession) {
3980
- throw new HTTPException9(404, {
4066
+ throw new HTTPException10(404, {
3981
4067
  message: `parent session not found in workspace: ${parentSessionId}`
3982
4068
  });
3983
4069
  }
@@ -4042,7 +4128,7 @@ async function createSessionForRequest(deps, grant, workspaceId, rawPayload) {
4042
4128
  const tools = withFirstPartyTools(selectedTools, runtimeSettings);
4043
4129
  await validateGitHubRepositorySelection(db, workspaceId, resources);
4044
4130
  if (resources.some((resource) => resource.kind === "file") && !objectStorage) {
4045
- throw new HTTPException9(503, { message: "object storage is not configured" });
4131
+ throw new HTTPException10(503, { message: "object storage is not configured" });
4046
4132
  }
4047
4133
  await validateFileResources(db, workspaceId, resources);
4048
4134
  const variableSet = payload.variableSetId ? await validateVariableSetAttachment(
@@ -4058,7 +4144,7 @@ async function createSessionForRequest(deps, grant, workspaceId, rawPayload) {
4058
4144
  const rig = await getRig2(db, workspaceId, requestedRigId);
4059
4145
  if (!rig || !rig.activeVersion) {
4060
4146
  if (payload.rigId) {
4061
- throw new HTTPException9(422, {
4147
+ throw new HTTPException10(422, {
4062
4148
  message: rig ? `rig ${payload.rigId} has no active version to bind` : `unknown rigId: ${payload.rigId}`
4063
4149
  });
4064
4150
  }
@@ -4084,7 +4170,7 @@ async function createSessionForRequest(deps, grant, workspaceId, rawPayload) {
4084
4170
  if (parentFirstPartyMcpPermissions && payload.firstPartyMcpPermissions?.some(
4085
4171
  (permission) => !hasPermission(parentFirstPartyMcpPermissions, permission)
4086
4172
  )) {
4087
- throw new HTTPException9(403, {
4173
+ throw new HTTPException10(403, {
4088
4174
  message: "child first-party MCP permissions may only narrow the parent session grant"
4089
4175
  });
4090
4176
  }
@@ -4092,19 +4178,19 @@ async function createSessionForRequest(deps, grant, workspaceId, rawPayload) {
4092
4178
  (permission) => hasPermission(grant.permissions, permission)
4093
4179
  ) : null);
4094
4180
  if (firstPartyMcpPermissions && firstPartyMcpPermissions.length === 0) {
4095
- throw new HTTPException9(422, {
4181
+ throw new HTTPException10(422, {
4096
4182
  message: "firstPartyMcpPermissions must not be empty; omit it for the default worker permission set"
4097
4183
  });
4098
4184
  }
4099
4185
  for (const permission of firstPartyMcpPermissions ?? []) {
4100
4186
  if (!hasPermission(grant.permissions, permission)) {
4101
- throw new HTTPException9(403, {
4187
+ throw new HTTPException10(403, {
4102
4188
  message: `cannot grant first-party MCP permission beyond the creating grant: ${permission}`
4103
4189
  });
4104
4190
  }
4105
4191
  }
4106
4192
  if (payload.goal && firstPartyMcpPermissions && !firstPartyMcpPermissions.includes("goals:manage")) {
4107
- throw new HTTPException9(422, {
4193
+ throw new HTTPException10(422, {
4108
4194
  message: "goal-bearing sessions require goals:manage in the resulting first-party MCP permission set"
4109
4195
  });
4110
4196
  }
@@ -4116,7 +4202,7 @@ async function createSessionForRequest(deps, grant, workspaceId, rawPayload) {
4116
4202
  const rigVersionMatchesGroup = (memberRigVersionId) => memberRigVersionId === frozenRigVersionId;
4117
4203
  if (sandboxChoice === "shared") {
4118
4204
  if (!parentSessionId) {
4119
- throw new HTTPException9(422, {
4205
+ throw new HTTPException10(422, {
4120
4206
  message: "sandbox:'shared' requires a parent session (spawn from inside a session); use 'new' for a top-level create."
4121
4207
  });
4122
4208
  }
@@ -4139,7 +4225,7 @@ async function createSessionForRequest(deps, grant, workspaceId, rawPayload) {
4139
4225
  }
4140
4226
  if (variableSetMismatch || rigMismatch) {
4141
4227
  if (payload.sandbox === "shared") {
4142
- throw new HTTPException9(422, {
4228
+ throw new HTTPException10(422, {
4143
4229
  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."
4144
4230
  });
4145
4231
  }
@@ -4150,7 +4236,7 @@ async function createSessionForRequest(deps, grant, workspaceId, rawPayload) {
4150
4236
  } else if (typeof sandboxChoice === "object") {
4151
4237
  const member = await getAnySessionInGroup(db, workspaceId, sandboxChoice.groupId);
4152
4238
  if (!member) {
4153
- throw new HTTPException9(404, {
4239
+ throw new HTTPException10(404, {
4154
4240
  message: `sandbox group not found in workspace: ${sandboxChoice.groupId}`
4155
4241
  });
4156
4242
  }
@@ -4163,7 +4249,7 @@ async function createSessionForRequest(deps, grant, workspaceId, rawPayload) {
4163
4249
  if (!memberVariableSetIds.every(
4164
4250
  (memberVariableSetId) => variableSetMatchesGroup(memberVariableSetId)
4165
4251
  )) {
4166
- throw new HTTPException9(422, {
4252
+ throw new HTTPException10(422, {
4167
4253
  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.`
4168
4254
  });
4169
4255
  }
@@ -4175,7 +4261,7 @@ async function createSessionForRequest(deps, grant, workspaceId, rawPayload) {
4175
4261
  if (!memberRigVersionIds.every(
4176
4262
  (memberRigVersionId) => rigVersionMatchesGroup(memberRigVersionId)
4177
4263
  )) {
4178
- throw new HTTPException9(422, {
4264
+ throw new HTTPException10(422, {
4179
4265
  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.`
4180
4266
  });
4181
4267
  }
@@ -4184,7 +4270,7 @@ async function createSessionForRequest(deps, grant, workspaceId, rawPayload) {
4184
4270
  inheritedBackend = member.sandboxBackend;
4185
4271
  }
4186
4272
  if (payload.workingDir !== void 0 && !payload.targetSandboxId) {
4187
- throw new HTTPException9(422, {
4273
+ throw new HTTPException10(422, {
4188
4274
  message: "workingDir requires targetSandboxId (it is the targeted machine's working directory)"
4189
4275
  });
4190
4276
  }
@@ -4272,10 +4358,10 @@ async function createSessionForRequest(deps, grant, workspaceId, rawPayload) {
4272
4358
  });
4273
4359
  } catch (error) {
4274
4360
  if (error instanceof AgentCommandAuthorityError) {
4275
- throw new HTTPException9(403, { message: error.message });
4361
+ throw new HTTPException10(403, { message: error.message });
4276
4362
  }
4277
4363
  if (error instanceof SessionIdConflictError) {
4278
- throw new HTTPException9(409, {
4364
+ throw new HTTPException10(409, {
4279
4365
  message: "requested session id is already in use"
4280
4366
  });
4281
4367
  }
@@ -4300,7 +4386,7 @@ async function createSessionForRequest(deps, grant, workspaceId, rawPayload) {
4300
4386
  }
4301
4387
  async function acceptSessionUserMessage(deps, grant, workspaceId, sessionId, input) {
4302
4388
  if (input.toolsProvided && !deps.settings.sessionTurnToolReplacementEnabled) {
4303
- throw new HTTPException9(503, {
4389
+ throw new HTTPException10(503, {
4304
4390
  message: "explicit follow-up tool replacement is temporarily unavailable until provenance-aware turn workers finish rolling out; omit tools to inherit the session policy and retry"
4305
4391
  });
4306
4392
  }
@@ -4362,7 +4448,7 @@ async function acceptSessionUserMessage(deps, grant, workspaceId, sessionId, inp
4362
4448
  model: effectiveModel
4363
4449
  });
4364
4450
  if (requestedResources.some((resource) => resource.kind === "file") && !objectStorage) {
4365
- throw new HTTPException9(503, { message: "object storage is not configured" });
4451
+ throw new HTTPException10(503, { message: "object storage is not configured" });
4366
4452
  }
4367
4453
  await validateFileResources(db, workspaceId, requestedResources);
4368
4454
  await validateGitHubRepositorySelection(db, workspaceId, [
@@ -4469,7 +4555,7 @@ async function updateSessionMcpApprovalPolicy(deps, grant, sessionId, serverId,
4469
4555
  async (_session, context) => {
4470
4556
  const result = await context.updateSessionMcpApprovalPolicy(serverId, normalizedPolicy);
4471
4557
  if (!result.server) {
4472
- throw new HTTPException9(404, { message: "session MCP server not found" });
4558
+ throw new HTTPException10(404, { message: "session MCP server not found" });
4473
4559
  }
4474
4560
  outcome.server = result.server;
4475
4561
  return {
@@ -4543,7 +4629,7 @@ async function updateSessionToolPolicy(deps, grant, sessionId, request) {
4543
4629
  (tool) => !validatedIds.has(`${tool.kind}:${tool.id}`)
4544
4630
  );
4545
4631
  if (unknown) {
4546
- throw new HTTPException9(422, { message: `unknown MCP server id: ${unknown.id}` });
4632
+ throw new HTTPException10(422, { message: `unknown MCP server id: ${unknown.id}` });
4547
4633
  }
4548
4634
  return withFirstPartyTools(validatedTools, runtimeSettings);
4549
4635
  })() : null;
@@ -4565,7 +4651,7 @@ async function updateSessionToolPolicy(deps, grant, sessionId, request) {
4565
4651
  if (session.parentSessionId) {
4566
4652
  const parent = await context.getLockedSession(session.parentSessionId);
4567
4653
  if (!parent) {
4568
- throw new HTTPException9(409, { message: "parent session is no longer available" });
4654
+ throw new HTTPException10(409, { message: "parent session is no longer available" });
4569
4655
  }
4570
4656
  const parentTracksWorkspaceDefaults = parent.toolPolicy?.mode === "workspace_default";
4571
4657
  const parentEffective = withFirstPartyTools(
@@ -4578,7 +4664,7 @@ async function updateSessionToolPolicy(deps, grant, sessionId, request) {
4578
4664
  );
4579
4665
  if (requestedMode === "workspace_default") {
4580
4666
  if (!parentTracksWorkspaceDefaults) {
4581
- throw new HTTPException9(403, {
4667
+ throw new HTTPException10(403, {
4582
4668
  message: "a child may adopt workspace defaults only while its parent tracks workspace defaults"
4583
4669
  });
4584
4670
  }
@@ -4648,13 +4734,13 @@ async function readSessionLineage(deps, grant, sessionId) {
4648
4734
  if (authorization?.relatedSessionAccess === "target") {
4649
4735
  const session = await getSession2(deps.db, grant.workspaceId, sessionId);
4650
4736
  if (!session) {
4651
- throw new HTTPException9(404, { message: "session not found" });
4737
+ throw new HTTPException10(404, { message: "session not found" });
4652
4738
  }
4653
4739
  return { ancestors: [], children: [], truncated: false };
4654
4740
  }
4655
4741
  const lineage = await getSessionLineage(deps.db, grant.workspaceId, sessionId);
4656
4742
  if (!lineage) {
4657
- throw new HTTPException9(404, { message: "session not found" });
4743
+ throw new HTTPException10(404, { message: "session not found" });
4658
4744
  }
4659
4745
  return lineage;
4660
4746
  }
@@ -4718,7 +4804,7 @@ async function createValidatedScheduledTask(input) {
4718
4804
  async function requireScheduledTaskRig(db, workspaceId, rigId) {
4719
4805
  const rig = await getRig3(db, workspaceId, rigId);
4720
4806
  if (!rig) {
4721
- throw new HTTPException10(422, { message: `unknown rigId: ${rigId}` });
4807
+ throw new HTTPException11(422, { message: `unknown rigId: ${rigId}` });
4722
4808
  }
4723
4809
  }
4724
4810
  async function validatedScheduledTaskUpdate(input) {
@@ -4745,7 +4831,7 @@ async function validatedScheduledTaskUpdate(input) {
4745
4831
  if (input.payload.variableSetId !== void 0) {
4746
4832
  const nextVariableSetId = input.payload.variableSetId;
4747
4833
  if ((input.existing.variableSetId ?? null) !== (nextVariableSetId ?? null) && input.existing.runMode === "reusable_session" && input.existing.reusableSessionId) {
4748
- throw new HTTPException10(409, {
4834
+ throw new HTTPException11(409, {
4749
4835
  message: "cannot change variableSet of a task with a live reusable session; recreate the task"
4750
4836
  });
4751
4837
  }
@@ -4775,7 +4861,7 @@ async function validatedScheduledTaskUpdate(input) {
4775
4861
  if (willHaveVariableSet) {
4776
4862
  requirePermission(input.grant, "variable-sets:use");
4777
4863
  }
4778
- update.agentConfig = await validateScheduledTaskAgentConfig({
4864
+ const nextAgentConfig = await validateScheduledTaskAgentConfig({
4779
4865
  settings: input.settings,
4780
4866
  db: input.db,
4781
4867
  objectStorage: input.objectStorage,
@@ -4784,13 +4870,19 @@ async function validatedScheduledTaskUpdate(input) {
4784
4870
  payload: { agentConfig: input.payload.agentConfig },
4785
4871
  ...input.toolsProvided !== void 0 ? { toolsProvided: input.toolsProvided } : {}
4786
4872
  });
4873
+ if (input.existing.reusableSessionId && input.existing.runMode === "reusable_session" && (input.existing.agentConfig.slackBotConnectionId ?? null) !== (nextAgentConfig.slackBotConnectionId ?? null)) {
4874
+ throw new HTTPException11(409, {
4875
+ message: "cannot change the OpenGeni Slack bot connection of a task with a live reusable session; recreate the task"
4876
+ });
4877
+ }
4878
+ update.agentConfig = nextAgentConfig;
4787
4879
  }
4788
4880
  return update;
4789
4881
  }
4790
4882
  async function requireScheduledTaskForApi(db, workspaceId, taskId) {
4791
4883
  const task = await getScheduledTask(db, workspaceId, taskId);
4792
4884
  if (!task) {
4793
- throw new HTTPException10(404, { message: "scheduled task not found" });
4885
+ throw new HTTPException11(404, { message: "scheduled task not found" });
4794
4886
  }
4795
4887
  return task;
4796
4888
  }
@@ -4855,13 +4947,26 @@ async function validateScheduledTaskAgentConfig(input) {
4855
4947
  const tools = input.toolsProvided ?? true ? requestedTools : withDefaultEnabledCapabilityMcpTools(requestedTools, input.settings, runtimeSettings);
4856
4948
  const prompt = input.payload.agentConfig.prompt.trim();
4857
4949
  if (!prompt) {
4858
- throw new HTTPException10(422, { message: "scheduled task prompt is required" });
4950
+ throw new HTTPException11(422, { message: "scheduled task prompt is required" });
4951
+ }
4952
+ if (hasReservedOpenGeniSlackBotSessionMetadata(input.payload.agentConfig.metadata)) {
4953
+ throw new HTTPException11(422, {
4954
+ message: `${OPENGENI_SLACK_BOT_SESSION_METADATA_KEY3} is reserved for scheduler routing`
4955
+ });
4859
4956
  }
4860
4957
  await validateGitHubRepositorySelection(input.db, input.workspaceId, resources);
4861
4958
  if (resources.some((resource) => resource.kind === "file") && !input.objectStorage) {
4862
- throw new HTTPException10(503, { message: "object storage is not configured" });
4959
+ throw new HTTPException11(503, { message: "object storage is not configured" });
4863
4960
  }
4864
4961
  await validateFileResources(input.db, input.workspaceId, resources);
4962
+ if (input.payload.agentConfig.slackBotConnectionId) {
4963
+ await validateOpenGeniSlackBotConnectionSelection(
4964
+ input.db,
4965
+ input.grant,
4966
+ input.workspaceId,
4967
+ input.payload.agentConfig.slackBotConnectionId
4968
+ );
4969
+ }
4865
4970
  const requestedMaxDepth = input.payload.agentConfig.maxNestedAgentDepth;
4866
4971
  if (requestedMaxDepth !== void 0) {
4867
4972
  const workspace = await requireWorkspace2(input.db, input.workspaceId);
@@ -4869,7 +4974,7 @@ async function validateScheduledTaskAgentConfig(input) {
4869
4974
  const deploymentPolicy = await getNestedAgentDepthDeploymentPolicy(input.db);
4870
4975
  const inheritedMaxDepth = typeof workspaceMaxDepth === "number" ? workspaceMaxDepth : deploymentPolicy.maxNestedAgentDepth;
4871
4976
  if (requestedMaxDepth > inheritedMaxDepth && !hasPermission(input.grant.permissions, "workspace:admin")) {
4872
- throw new HTTPException10(403, {
4977
+ throw new HTTPException11(403, {
4873
4978
  message: `scheduled task maxNestedAgentDepth ${requestedMaxDepth} exceeds inherited limit ${inheritedMaxDepth}; workspace:admin is required to increase it`
4874
4979
  });
4875
4980
  }
@@ -4887,19 +4992,19 @@ function validateScheduledTaskSchedule(schedule) {
4887
4992
  return;
4888
4993
  }
4889
4994
  if (new Date(schedule.startAt).getTime() >= new Date(schedule.endAt).getTime()) {
4890
- throw new HTTPException10(422, { message: "interval schedule endAt must be after startAt" });
4995
+ throw new HTTPException11(422, { message: "interval schedule endAt must be after startAt" });
4891
4996
  }
4892
4997
  }
4893
4998
  function trimmedScheduledTaskName(name) {
4894
4999
  const trimmed = name.trim();
4895
5000
  if (!trimmed) {
4896
- throw new HTTPException10(422, { message: "scheduled task name is required" });
5001
+ throw new HTTPException11(422, { message: "scheduled task name is required" });
4897
5002
  }
4898
5003
  return trimmed;
4899
5004
  }
4900
5005
 
4901
5006
  // src/domain/workspace-members.ts
4902
- import { HTTPException as HTTPException11 } from "hono/http-exception";
5007
+ import { HTTPException as HTTPException12 } from "hono/http-exception";
4903
5008
  var MEMBER_ADMIN_PERMISSIONS = ["workspace:admin", "members:manage"];
4904
5009
  function memberCanAdminister(member) {
4905
5010
  return member.permissions.some((permission) => MEMBER_ADMIN_PERMISSIONS.includes(permission));
@@ -4909,25 +5014,25 @@ function isUserMember(member) {
4909
5014
  }
4910
5015
  function resolveMemberSubjectId(userId) {
4911
5016
  if (!userId) {
4912
- throw new HTTPException11(404, { message: "user is not registered" });
5017
+ throw new HTTPException12(404, { message: "user is not registered" });
4913
5018
  }
4914
5019
  return `user:${userId}`;
4915
5020
  }
4916
5021
  function assertWorkspaceMemberRemovable(input) {
4917
5022
  const { members, subjectId, callerSubjectId } = input;
4918
5023
  if (subjectId === callerSubjectId) {
4919
- throw new HTTPException11(409, { message: "you cannot remove your own membership" });
5024
+ throw new HTTPException12(409, { message: "you cannot remove your own membership" });
4920
5025
  }
4921
5026
  const target = members.find((member) => member.subjectId === subjectId);
4922
5027
  if (!target) {
4923
- throw new HTTPException11(404, { message: "member not found" });
5028
+ throw new HTTPException12(404, { message: "member not found" });
4924
5029
  }
4925
5030
  if (memberCanAdminister(target)) {
4926
5031
  const remainingAdmins = members.filter(
4927
5032
  (member) => member.subjectId !== subjectId && memberCanAdminister(member)
4928
5033
  );
4929
5034
  if (remainingAdmins.length === 0) {
4930
- throw new HTTPException11(409, {
5035
+ throw new HTTPException12(409, {
4931
5036
  message: "cannot remove the last member who can manage this workspace"
4932
5037
  });
4933
5038
  }
@@ -4935,10 +5040,10 @@ function assertWorkspaceMemberRemovable(input) {
4935
5040
  }
4936
5041
  function assertWorkspaceDeletable(input) {
4937
5042
  if (input.workspaceCountForAccount <= 1) {
4938
- throw new HTTPException11(409, { message: "cannot delete the account's only workspace" });
5043
+ throw new HTTPException12(409, { message: "cannot delete the account's only workspace" });
4939
5044
  }
4940
5045
  if (input.activeSessionCount > 0) {
4941
- throw new HTTPException11(409, {
5046
+ throw new HTTPException12(409, {
4942
5047
  message: "stop the workspace's running sessions before deleting it"
4943
5048
  });
4944
5049
  }
@@ -4962,7 +5067,7 @@ import {
4962
5067
  saveNewSessionDraftInTransaction,
4963
5068
  withWorkspaceSubjectRls as withWorkspaceSubjectRls2
4964
5069
  } from "@opengeni/db";
4965
- import { HTTPException as HTTPException12 } from "hono/http-exception";
5070
+ import { HTTPException as HTTPException13 } from "hono/http-exception";
4966
5071
  function hasOwn(value, key) {
4967
5072
  return typeof value === "object" && value !== null && Object.hasOwn(value, key);
4968
5073
  }
@@ -5084,7 +5189,7 @@ async function saveActorNewSessionDraft(deps, grant, workspaceId, rawInput) {
5084
5189
  const tools = toolsProvided ? validateToolRefs(input.tools, runtimeSettings) : [];
5085
5190
  await validateGitHubRepositorySelection(deps.db, workspaceId, resources);
5086
5191
  if (resources.some((resource) => resource.kind === "file") && !deps.objectStorage) {
5087
- throw new HTTPException12(503, { message: "object storage is not configured" });
5192
+ throw new HTTPException13(503, { message: "object storage is not configured" });
5088
5193
  }
5089
5194
  await validateFileResources(deps.db, workspaceId, resources);
5090
5195
  assertConfiguredModel(deps.settings, input.model);
@@ -5118,7 +5223,7 @@ async function saveActorNewSessionDraft(deps, grant, workspaceId, rawInput) {
5118
5223
  return mapNewSessionDraft(saved);
5119
5224
  } catch (error) {
5120
5225
  if (error instanceof NewSessionDraftAccessError) {
5121
- throw new HTTPException12(403, { message: error.message });
5226
+ throw new HTTPException13(403, { message: error.message });
5122
5227
  }
5123
5228
  throw error;
5124
5229
  }
@@ -5656,8 +5761,12 @@ export {
5656
5761
  getCapabilityPack,
5657
5762
  getHumanComposerDraft,
5658
5763
  hasPermission,
5764
+ hasReservedOpenGeniSlackBotMetadata,
5765
+ hasReservedOpenGeniSlackBotSessionMetadata,
5659
5766
  isAuthoritativeGitHubRepositorySelectionError,
5660
5767
  isBuiltInCapabilityPack,
5768
+ isOpenGeniSlackBotConnection,
5769
+ isTrustedScheduledSlackBotSession,
5661
5770
  isUserMember,
5662
5771
  listCapabilityPacks,
5663
5772
  listFleet,
@@ -5672,6 +5781,7 @@ export {
5672
5781
  moveHumanQueuePrompt,
5673
5782
  normalizeResources,
5674
5783
  officialMcpRegistryUrl,
5784
+ openGeniSlackBotMetadata,
5675
5785
  postUserMessageTurn,
5676
5786
  promoteSetupAppendChange,
5677
5787
  promoteVerifiedDefinitionEditChangeForApi,
@@ -5688,6 +5798,7 @@ export {
5688
5798
  requireAccessGrant,
5689
5799
  requireEnvironmentEncryption,
5690
5800
  requireLimit,
5801
+ requireOpenGeniSlackBotConnection,
5691
5802
  requirePermission,
5692
5803
  requireQueuedTurnForApi,
5693
5804
  requireRigChangeForApi,
@@ -5706,6 +5817,7 @@ export {
5706
5817
  runOnSandbox,
5707
5818
  saveActorNewSessionDraft,
5708
5819
  saveHumanComposerDraft,
5820
+ scheduledSlackBotConnectionId,
5709
5821
  scheduledTaskTemporalScheduleId,
5710
5822
  scheduledTaskToolsProvided,
5711
5823
  scheduledTaskTriggerToken,
@@ -5731,6 +5843,7 @@ export {
5731
5843
  validateGitHubRepositorySelectionShape,
5732
5844
  validateGitHubRepositorySelectionShapes,
5733
5845
  validateMcpCapabilityConnection,
5846
+ validateOpenGeniSlackBotConnectionSelection,
5734
5847
  validateToolRefs,
5735
5848
  validateToolRefsForSessionPolicy,
5736
5849
  validateVariableSetAttachment,