@opengeni/api-router 0.14.3 → 0.15.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.
@@ -26,11 +26,11 @@ import { WebStandardStreamableHTTPServerTransport as WebStandardStreamableHTTPSe
26
26
  import { Hono } from "hono";
27
27
  import { bodyLimit } from "hono/body-limit";
28
28
  import { cors } from "hono/cors";
29
- import { HTTPException as HTTPException30 } from "hono/http-exception";
29
+ import { HTTPException as HTTPException32 } from "hono/http-exception";
30
30
  import {
31
31
  CodexCompactionV2ProviderLockedError,
32
- hasPermission as hasPermission13,
33
- requireAccessGrant as requireAccessGrant22,
32
+ hasPermission as hasPermission14,
33
+ requireAccessGrant as requireAccessGrant24,
34
34
  requirePermission,
35
35
  requireSessionAuthorization as requireSessionAuthorization3,
36
36
  SessionAuthorizationDeniedError as SessionAuthorizationDeniedError2,
@@ -291,6 +291,7 @@ function makeResumeBoxById(client) {
291
291
  import { requireLimit as requireLimit8 } from "@opengeni/core";
292
292
 
293
293
  // src/mcp/server.ts
294
+ import { createHash as createHash2 } from "crypto";
294
295
  import {
295
296
  CreateScheduledTaskRequest,
296
297
  DEFAULT_FIRST_PARTY_MCP_TOOLS,
@@ -308,7 +309,9 @@ import {
308
309
  sessionEventLatestClassToSemanticClass,
309
310
  SessionMcpCredentialUpdateInput,
310
311
  VariableSetVariableName,
311
- UpdateScheduledTaskRequest
312
+ UpdateScheduledTaskRequest,
313
+ normalizeWorkspaceArtifactSlug,
314
+ WORKSPACE_ARTIFACT_HTML_MAX_UTF8_BYTES
312
315
  } from "@opengeni/contracts";
313
316
  import {
314
317
  correctWorkspaceMemory,
@@ -352,7 +355,13 @@ import {
352
355
  updateSessionGoalWithEvent,
353
356
  upsertSessionGoalWithEvent,
354
357
  RigChangeAlreadyVerifyingError,
355
- RigChangeTransitionError
358
+ RigChangeTransitionError,
359
+ createWorkspaceArtifact,
360
+ getWorkspaceArtifact,
361
+ getWorkspaceArtifactContentRef,
362
+ listWorkspaceArtifacts,
363
+ publishWorkspaceArtifactVersion,
364
+ rollbackWorkspaceArtifact
356
365
  } from "@opengeni/db";
357
366
  import { appendAndPublishEvents as appendAndPublishEvents2, publishDurableSessionEvents } from "@opengeni/events";
358
367
  import {
@@ -2156,7 +2165,7 @@ var MAX_PROJECTED_TEXT = 4e3;
2156
2165
  var SLACK_POST_CLAIM_LEASE_MS = 3e4;
2157
2166
  var SLACK_DELETE_CLAIM_LEASE_MS = 3e4;
2158
2167
  async function exchangeOpenGeniSlackAuthorizationCode(input, fetchImpl = fetch) {
2159
- const body = new URLSearchParams({
2168
+ const body2 = new URLSearchParams({
2160
2169
  code: input.code,
2161
2170
  client_id: input.clientId,
2162
2171
  client_secret: input.clientSecret,
@@ -2170,28 +2179,34 @@ async function exchangeOpenGeniSlackAuthorizationCode(input, fetchImpl = fetch)
2170
2179
  accept: "application/json",
2171
2180
  "content-type": "application/x-www-form-urlencoded"
2172
2181
  },
2173
- body: body.toString(),
2182
+ body: body2.toString(),
2174
2183
  redirect: "error",
2175
2184
  signal: AbortSignal.timeout(SLACK_TIMEOUT_MS)
2176
2185
  });
2177
2186
  } catch {
2178
- throw new HTTPException2(502, { message: "Slack installation token exchange failed" });
2187
+ throw new HTTPException2(502, {
2188
+ message: "Slack installation token exchange failed"
2189
+ });
2179
2190
  }
2180
2191
  if (!response.ok) {
2181
- throw new HTTPException2(502, { message: "Slack installation token exchange failed" });
2192
+ throw new HTTPException2(502, {
2193
+ message: "Slack installation token exchange failed"
2194
+ });
2182
2195
  }
2183
2196
  const payload = await readResponseJsonBounded(
2184
2197
  response,
2185
2198
  SLACK_RESPONSE_MAX_BYTES,
2186
2199
  "Slack OAuth response"
2187
2200
  );
2188
- const record3 = slackRecord(payload);
2189
- if (!record3 || record3.ok !== true) {
2190
- throw new SlackBotProviderError(slackString(record3?.error) || "oauth_exchange_failed");
2201
+ const record4 = slackRecord(payload);
2202
+ if (!record4 || record4.ok !== true) {
2203
+ throw new SlackBotProviderError(slackString(record4?.error) || "oauth_exchange_failed");
2191
2204
  }
2192
- const accessToken = slackString(record3.access_token);
2205
+ const accessToken = slackString(record4.access_token);
2193
2206
  if (!accessToken?.startsWith("xoxb-")) {
2194
- throw new HTTPException2(502, { message: "Slack installation did not return a bot token" });
2207
+ throw new HTTPException2(502, {
2208
+ message: "Slack installation did not return a bot token"
2209
+ });
2195
2210
  }
2196
2211
  return accessToken;
2197
2212
  }
@@ -2344,6 +2359,10 @@ var OpenGeniSlackBotClient = class {
2344
2359
  };
2345
2360
  });
2346
2361
  }
2362
+ async verifyChannelAccess(channelId) {
2363
+ const headers = await this.headersFor("channel_history.read");
2364
+ return await this.requireMemberChannel(headers, channelId);
2365
+ }
2347
2366
  async channelHistory(input) {
2348
2367
  return await this.withAudit("channel_history.read", async (headers) => {
2349
2368
  const info = await this.requireMemberChannel(headers, input.channelId);
@@ -2453,7 +2472,9 @@ var OpenGeniSlackBotClient = class {
2453
2472
  const headers = await this.headersFor(operation);
2454
2473
  let channelId = input.channelId;
2455
2474
  if (input.userId) {
2456
- const opened = await this.call(headers, "conversations.open", { users: input.userId });
2475
+ const opened = await this.call(headers, "conversations.open", {
2476
+ users: input.userId
2477
+ });
2457
2478
  channelId = requiredSlackString(slackRecord(opened.channel)?.id, "channel.id");
2458
2479
  } else if (channelId) {
2459
2480
  await this.requireMemberChannel(headers, channelId);
@@ -2671,7 +2692,10 @@ var OpenGeniSlackBotClient = class {
2671
2692
  `${SLACK_API_BASE}chat.getPermalink`
2672
2693
  );
2673
2694
  try {
2674
- await this.call(headers, "chat.getPermalink", { channel: channelId, message_ts: timestamp });
2695
+ await this.call(headers, "chat.getPermalink", {
2696
+ channel: channelId,
2697
+ message_ts: timestamp
2698
+ });
2675
2699
  return true;
2676
2700
  } catch (error) {
2677
2701
  if (error instanceof SlackBotProviderError && error.code === "message_not_found") {
@@ -2681,7 +2705,9 @@ var OpenGeniSlackBotClient = class {
2681
2705
  }
2682
2706
  }
2683
2707
  async requireMemberChannel(headers, channelId) {
2684
- const payload = await this.call(headers, "conversations.info", { channel: channelId });
2708
+ const payload = await this.call(headers, "conversations.info", {
2709
+ channel: channelId
2710
+ });
2685
2711
  const projected = projectChannel(payload.channel);
2686
2712
  if (!projected || projected.isMember !== true) {
2687
2713
  throw new SlackBotProviderError("not_in_channel");
@@ -2909,19 +2935,31 @@ var OpenGeniSlackBotClient = class {
2909
2935
  return { type: "subject", id: this.context.subjectId };
2910
2936
  }
2911
2937
  if (this.context.scheduledTaskId) {
2912
- return { type: "service", id: `scheduler:${this.context.scheduledTaskId}` };
2938
+ return {
2939
+ type: "service",
2940
+ id: `scheduler:${this.context.scheduledTaskId}`
2941
+ };
2913
2942
  }
2914
- return { type: "service", id: `session:${this.context.sessionId ?? "workspace"}` };
2943
+ return {
2944
+ type: "service",
2945
+ id: `session:${this.context.sessionId ?? "workspace"}`
2946
+ };
2915
2947
  }
2916
2948
  fileListPage(input) {
2917
2949
  const key = environmentsEncryptionKeyBytes(this.settings);
2918
2950
  if (!key) throw new Error("connection encryption is not configured");
2919
- return resolveSlackFilesListPage(input, { connectionId: this.connection.id, key });
2951
+ return resolveSlackFilesListPage(input, {
2952
+ connectionId: this.connection.id,
2953
+ key
2954
+ });
2920
2955
  }
2921
2956
  fileListCursor(input) {
2922
2957
  const key = environmentsEncryptionKeyBytes(this.settings);
2923
2958
  if (!key) throw new Error("connection encryption is not configured");
2924
- return createSlackFilesListCursor(input, { connectionId: this.connection.id, key });
2959
+ return createSlackFilesListCursor(input, {
2960
+ connectionId: this.connection.id,
2961
+ key
2962
+ });
2925
2963
  }
2926
2964
  async recordAudit(operation, outcome, failureCode, operationId) {
2927
2965
  await recordAuditEvent(this.db, {
@@ -2954,6 +2992,32 @@ function createOpenGeniSlackBotClient(deps, resolved) {
2954
2992
  deps.slackFetch
2955
2993
  );
2956
2994
  }
2995
+ async function createOpenGeniSlackBotInteractionClient(deps, input) {
2996
+ const connection = await requireOpenGeniSlackBotConnection(
2997
+ deps.db,
2998
+ input.workspaceId,
2999
+ input.connectionId
3000
+ );
3001
+ if (connection.accountId !== input.accountId) {
3002
+ throw new Error("OpenGeni Slack bot connection tenant mismatch");
3003
+ }
3004
+ const metadata = openGeniSlackBotMetadata(connection.metadata);
3005
+ if (!metadata) throw new Error("OpenGeni Slack bot connection metadata is invalid");
3006
+ return new OpenGeniSlackBotClient(
3007
+ deps.db,
3008
+ deps.settings,
3009
+ connection,
3010
+ metadata,
3011
+ {
3012
+ accountId: input.accountId,
3013
+ workspaceId: input.workspaceId,
3014
+ subjectId: input.subjectId,
3015
+ sessionId: input.sessionId ?? null,
3016
+ scheduledTaskId: null
3017
+ },
3018
+ deps.slackFetch
3019
+ );
3020
+ }
2957
3021
  async function slackApiFetch(fetchImpl, method, token, params) {
2958
3022
  return await slackApiFetchWithHeaders(
2959
3023
  fetchImpl,
@@ -2967,7 +3031,7 @@ async function slackApiFetchWithHeaders(fetchImpl, method, credentialHeaders, pa
2967
3031
  throw new Error("invalid Slack API method");
2968
3032
  }
2969
3033
  const url = new URL(method, SLACK_API_BASE);
2970
- const body = new URLSearchParams(params);
3034
+ const body2 = new URLSearchParams(params);
2971
3035
  let response;
2972
3036
  try {
2973
3037
  response = await fetchImpl(url, {
@@ -2977,7 +3041,7 @@ async function slackApiFetchWithHeaders(fetchImpl, method, credentialHeaders, pa
2977
3041
  accept: "application/json",
2978
3042
  "content-type": "application/x-www-form-urlencoded"
2979
3043
  },
2980
- body,
3044
+ body: body2,
2981
3045
  signal: AbortSignal.timeout(SLACK_TIMEOUT_MS)
2982
3046
  });
2983
3047
  } catch {
@@ -3121,9 +3185,9 @@ function isSupportedSlackTextContentType(value) {
3121
3185
  return value.startsWith("text/") || value === "application/json" || value === "application/xml" || value === "application/xhtml+xml" || value === "application/vnd.slack-docs" || value === "application/vnd.slack-huddle-transcript";
3122
3186
  }
3123
3187
  function embeddedHuddleTranscription(fileRecord, parentFileRecord) {
3124
- for (const record3 of [fileRecord, parentFileRecord]) {
3125
- if (!record3) continue;
3126
- const transcription = slackRecord(record3.huddle_transcription);
3188
+ for (const record4 of [fileRecord, parentFileRecord]) {
3189
+ if (!record4) continue;
3190
+ const transcription = slackRecord(record4.huddle_transcription);
3127
3191
  if (!transcription) continue;
3128
3192
  const content = JSON.stringify(transcription);
3129
3193
  if (content !== "{}") {
@@ -3336,7 +3400,12 @@ var FIRST_PARTY_TOOL_AUTHORIZATION = {
3336
3400
  slack_bot_file_info: { allOf: ["connections:read"] },
3337
3401
  slack_bot_file_content: { allOf: ["connections:read"] },
3338
3402
  slack_bot_post_message: { allOf: ["connections:read"] },
3339
- slack_bot_delete_message: { allOf: ["connections:read"] }
3403
+ slack_bot_delete_message: { allOf: ["connections:read"] },
3404
+ artifacts_list: { sessionRequired: true, allOf: ["artifacts:read"] },
3405
+ artifacts_get_source: { sessionRequired: true, allOf: ["artifacts:read"] },
3406
+ artifacts_create: { sessionRequired: true, allOf: ["artifacts:publish"] },
3407
+ artifacts_publish: { sessionRequired: true, allOf: ["artifacts:publish"] },
3408
+ artifacts_rollback: { sessionRequired: true, allOf: ["artifacts:publish"] }
3340
3409
  };
3341
3410
  var FIRST_PARTY_MCP_TOOL_NAME_SET = new Set(FIRST_PARTY_MCP_TOOL_NAMES);
3342
3411
  var PolicyMcpServer = class extends McpServer {
@@ -3431,6 +3500,9 @@ function buildOpenGeniMcpServer(deps, grant, options = {}) {
3431
3500
  if (!toolspaceMode && sessionId !== null && preferenceAttemptClaims(grant) !== null) {
3432
3501
  registerPreferenceRegistryTools(server, deps, grant, json);
3433
3502
  }
3503
+ if (!toolspaceMode && sessionId !== null && preferenceAttemptClaims(grant) !== null) {
3504
+ registerWorkspaceArtifactTools(server, deps, grant, sessionId, json);
3505
+ }
3434
3506
  if (!toolspaceMode && sessionId !== null && deps.settings.sandboxSelfhostedEnabled) {
3435
3507
  registerFleetTools(server, deps, grant, sessionId, json);
3436
3508
  }
@@ -4171,6 +4243,172 @@ async function authorizeFirstPartySession(deps, grant, sessionId, operation) {
4171
4243
  surface: "first_party_mcp"
4172
4244
  });
4173
4245
  }
4246
+ function registerWorkspaceArtifactTools(server, deps, grant, sessionId, json) {
4247
+ const attempt = () => {
4248
+ const claims = preferenceAttemptClaims(grant);
4249
+ if (!claims) throw new Error("Exact signed artifact attempt authority is required.");
4250
+ return claims;
4251
+ };
4252
+ const authorize = async () => {
4253
+ await authorizeFirstPartySession(deps, grant, sessionId, "session.first_party_mcp.call");
4254
+ };
4255
+ const prepare = (html) => {
4256
+ if (!deps.objectStorage) throw new Error("Object storage is not configured");
4257
+ const bytes = new TextEncoder().encode(html);
4258
+ if (bytes.byteLength < 1 || bytes.byteLength > WORKSPACE_ARTIFACT_HTML_MAX_UTF8_BYTES) {
4259
+ throw new Error(
4260
+ `Artifact HTML must be 1-${WORKSPACE_ARTIFACT_HTML_MAX_UTF8_BYTES} UTF-8 bytes`
4261
+ );
4262
+ }
4263
+ const contentSha256 = createHash2("sha256").update(bytes).digest("hex");
4264
+ const contentKey = `workspaces/${grant.workspaceId}/workspace-artifacts/blobs/${contentSha256}.html`;
4265
+ return {
4266
+ contentKey,
4267
+ contentSha256,
4268
+ sizeBytes: bytes.byteLength,
4269
+ persistContent: async () => {
4270
+ await deps.objectStorage.putObject({
4271
+ key: contentKey,
4272
+ contentType: "text/html; charset=utf-8",
4273
+ body: bytes,
4274
+ sha256: contentSha256
4275
+ });
4276
+ }
4277
+ };
4278
+ };
4279
+ const provenance2 = (idempotencyKey, sourceToolName) => {
4280
+ const claims = attempt();
4281
+ return {
4282
+ accountId: grant.accountId,
4283
+ workspaceId: grant.workspaceId,
4284
+ operationKey: `attempt:${createHash2("sha256").update(
4285
+ `${claims.sessionId}:${claims.turnId}:${claims.attemptId}:${claims.executionGeneration}:${idempotencyKey}`
4286
+ ).digest("hex")}`,
4287
+ actorSubjectId: grant.subjectId,
4288
+ sourceSessionId: claims.sessionId,
4289
+ sourceTurnId: claims.turnId,
4290
+ sourceAttemptId: claims.attemptId,
4291
+ sourceExecutionGeneration: claims.executionGeneration,
4292
+ sourceToolName
4293
+ };
4294
+ };
4295
+ server.registerTool(
4296
+ "artifacts_list",
4297
+ {
4298
+ description: "List the generic published artifacts in this workspace and their current versions.",
4299
+ inputSchema: {}
4300
+ },
4301
+ async () => {
4302
+ await authorize();
4303
+ return json(await listWorkspaceArtifacts(deps.db, grant.workspaceId));
4304
+ }
4305
+ );
4306
+ server.registerTool(
4307
+ "artifacts_get_source",
4308
+ {
4309
+ description: "Read an artifact's metadata and exact HTML source. Omit versionId for the current version.",
4310
+ inputSchema: {
4311
+ artifactId: z4.string().uuid(),
4312
+ versionId: z4.string().uuid().optional()
4313
+ }
4314
+ },
4315
+ async ({ artifactId: artifactId2, versionId }) => {
4316
+ await authorize();
4317
+ if (!deps.objectStorage) throw new Error("Object storage is not configured");
4318
+ const [detail, ref] = await Promise.all([
4319
+ getWorkspaceArtifact(deps.db, grant.workspaceId, artifactId2),
4320
+ getWorkspaceArtifactContentRef(deps.db, grant.workspaceId, artifactId2, versionId)
4321
+ ]);
4322
+ const object5 = await deps.objectStorage.getObjectBytes(ref.contentKey);
4323
+ if (!object5) throw new Error("Artifact content is unavailable");
4324
+ const actualHash = createHash2("sha256").update(object5.bytes).digest("hex");
4325
+ if (actualHash !== ref.version.contentSha256)
4326
+ throw new Error("Artifact content failed integrity verification");
4327
+ return json({ detail, version: ref.version, html: new TextDecoder().decode(object5.bytes) });
4328
+ }
4329
+ );
4330
+ server.registerTool(
4331
+ "artifacts_create",
4332
+ {
4333
+ description: "Create and publish a generic static workspace artifact from a complete, self-contained HTML document with inline CSS. JavaScript and active or navigation-capable markup do not render in the MVP.",
4334
+ inputSchema: {
4335
+ title: z4.string().min(1).max(120),
4336
+ description: z4.string().max(2e3).nullable().optional(),
4337
+ slug: z4.string().regex(/^[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$/).max(96).optional(),
4338
+ html: z4.string().min(1).max(WORKSPACE_ARTIFACT_HTML_MAX_UTF8_BYTES),
4339
+ idempotencyKey: z4.string().min(1).max(200)
4340
+ }
4341
+ },
4342
+ async ({ title, description, slug, html, idempotencyKey }) => {
4343
+ await authorize();
4344
+ const artifactId2 = crypto.randomUUID();
4345
+ const slugBase = slug ?? (normalizeWorkspaceArtifactSlug(title) || "artifact");
4346
+ const resolvedSlug = slug ?? `${slugBase.slice(0, 87)}-${artifactId2.slice(0, 8)}`;
4347
+ return json(
4348
+ await createWorkspaceArtifact(deps.db, {
4349
+ artifactId: artifactId2,
4350
+ slug: resolvedSlug,
4351
+ title,
4352
+ description: description ?? null,
4353
+ ...prepare(html),
4354
+ ...provenance2(idempotencyKey, "artifacts_create")
4355
+ })
4356
+ );
4357
+ }
4358
+ );
4359
+ server.registerTool(
4360
+ "artifacts_publish",
4361
+ {
4362
+ description: "Publish a new immutable static HTML/CSS version. JavaScript and active or navigation-capable markup do not render in the MVP. First read the current source and pass its version id for optimistic concurrency.",
4363
+ inputSchema: {
4364
+ artifactId: z4.string().uuid(),
4365
+ expectedCurrentVersionId: z4.string().uuid(),
4366
+ title: z4.string().min(1).max(120).optional(),
4367
+ description: z4.string().max(2e3).nullable().optional(),
4368
+ html: z4.string().min(1).max(WORKSPACE_ARTIFACT_HTML_MAX_UTF8_BYTES),
4369
+ idempotencyKey: z4.string().min(1).max(200)
4370
+ }
4371
+ },
4372
+ async ({ artifactId: artifactId2, expectedCurrentVersionId, title, description, html, idempotencyKey }) => {
4373
+ await authorize();
4374
+ return json(
4375
+ await publishWorkspaceArtifactVersion(deps.db, {
4376
+ artifactId: artifactId2,
4377
+ expectedCurrentVersionId,
4378
+ ...title !== void 0 ? { title } : {},
4379
+ ...description !== void 0 ? { description } : {},
4380
+ ...prepare(html),
4381
+ ...provenance2(idempotencyKey, "artifacts_publish")
4382
+ })
4383
+ );
4384
+ }
4385
+ );
4386
+ server.registerTool(
4387
+ "artifacts_rollback",
4388
+ {
4389
+ description: "Promote an existing immutable artifact version back to current without rewriting history.",
4390
+ inputSchema: {
4391
+ artifactId: z4.string().uuid(),
4392
+ versionId: z4.string().uuid(),
4393
+ expectedCurrentVersionId: z4.string().uuid(),
4394
+ reason: z4.string().min(1).max(4096),
4395
+ idempotencyKey: z4.string().min(1).max(200)
4396
+ }
4397
+ },
4398
+ async ({ artifactId: artifactId2, versionId, expectedCurrentVersionId, reason, idempotencyKey }) => {
4399
+ await authorize();
4400
+ return json(
4401
+ await rollbackWorkspaceArtifact(deps.db, {
4402
+ artifactId: artifactId2,
4403
+ versionId,
4404
+ expectedCurrentVersionId,
4405
+ reason,
4406
+ ...provenance2(idempotencyKey, "artifacts_rollback")
4407
+ })
4408
+ );
4409
+ }
4410
+ );
4411
+ }
4174
4412
  function preferenceAttemptClaims(grant) {
4175
4413
  const metadata = grant.metadata ?? {};
4176
4414
  if (typeof metadata["sessionId"] !== "string" || typeof metadata["turnId"] !== "string" || typeof metadata["attemptId"] !== "string" || typeof metadata["executionGeneration"] !== "number" || !Number.isSafeInteger(metadata["executionGeneration"]) || metadata["executionGeneration"] < 1 || metadata["executionGeneration"] > 2147483647) {
@@ -5651,7 +5889,7 @@ import {
5651
5889
  mcpSerializedSizeBytes
5652
5890
  } from "@opengeni/runtime/mcp-network";
5653
5891
  import { Buffer as Buffer2 } from "buffer";
5654
- import { createHash as createHash2 } from "crypto";
5892
+ import { createHash as createHash3 } from "crypto";
5655
5893
  var APPROVAL_REQUIRED_MESSAGE = "requires approval - invoke via the agent";
5656
5894
  var TOOLSPACE_AUTH_NEEDED_ERROR = {
5657
5895
  // OpenGeni application-defined JSON-RPC code. Keep this positive so it cannot
@@ -6169,7 +6407,7 @@ function toolspaceAuditSummary(value) {
6169
6407
  return {
6170
6408
  redacted: true,
6171
6409
  sizeBytes: Buffer2.byteLength(serialized),
6172
- sha256: createHash2("sha256").update(serialized).digest("hex")
6410
+ sha256: createHash3("sha256").update(serialized).digest("hex")
6173
6411
  };
6174
6412
  }
6175
6413
  function toolspaceErrorAttributes(error) {
@@ -6390,12 +6628,12 @@ async function authNeededFetchResponse(input, request, auth) {
6390
6628
  return new Response("Authentication required for MCP server connection", { status: 401 });
6391
6629
  }
6392
6630
  async function mcpRequestInfo(input, init) {
6393
- const body = typeof init?.body === "string" ? init.body : input instanceof Request && (init?.method ?? input.method).toUpperCase() === "POST" ? await input.clone().text().catch(() => "") : "";
6394
- if (!body) {
6631
+ const body2 = typeof init?.body === "string" ? init.body : input instanceof Request && (init?.method ?? input.method).toUpperCase() === "POST" ? await input.clone().text().catch(() => "") : "";
6632
+ if (!body2) {
6395
6633
  return {};
6396
6634
  }
6397
6635
  try {
6398
- const parsed = JSON.parse(body);
6636
+ const parsed = JSON.parse(body2);
6399
6637
  const method = typeof parsed.method === "string" ? parsed.method : void 0;
6400
6638
  const id = typeof parsed.id === "string" || typeof parsed.id === "number" || parsed.id === null ? parsed.id : void 0;
6401
6639
  const toolName = method === "tools/call" && typeof parsed.params?.name === "string" ? parsed.params.name : void 0;
@@ -6451,9 +6689,9 @@ async function loadAsset(file) {
6451
6689
  if (cached !== void 0) {
6452
6690
  return cached;
6453
6691
  }
6454
- const body = await readFile(new URL(file, INSTALL_DIR), "utf8");
6455
- assetCache.set(file, body);
6456
- return body;
6692
+ const body2 = await readFile(new URL(file, INSTALL_DIR), "utf8");
6693
+ assetCache.set(file, body2);
6694
+ return body2;
6457
6695
  }
6458
6696
  var DEFAULT_BASE_REWRITES = {
6459
6697
  "install.sh": (base) => ({
@@ -6465,16 +6703,16 @@ var DEFAULT_BASE_REWRITES = {
6465
6703
  to: `$OpengeniInstallDefaultBaseUrl = '${base}'`
6466
6704
  })
6467
6705
  };
6468
- function rewriteDefaultBaseUrl(file, body, publicBaseUrl) {
6706
+ function rewriteDefaultBaseUrl(file, body2, publicBaseUrl) {
6469
6707
  if (!publicBaseUrl || !/^https?:\/\//.test(publicBaseUrl)) {
6470
- return body;
6708
+ return body2;
6471
6709
  }
6472
6710
  const base = publicBaseUrl.replace(/\/+$/, "");
6473
6711
  const rule = DEFAULT_BASE_REWRITES[file]?.(base);
6474
- if (!rule || !body.includes(rule.from)) {
6475
- return body;
6712
+ if (!rule || !body2.includes(rule.from)) {
6713
+ return body2;
6476
6714
  }
6477
- return body.replace(rule.from, rule.to);
6715
+ return body2.replace(rule.from, rule.to);
6478
6716
  }
6479
6717
  function bakedContentType(asset) {
6480
6718
  if (asset.endsWith(".sha256") || asset.endsWith(".minisig")) {
@@ -6502,8 +6740,8 @@ function registerInstallRoutes(app, deps) {
6502
6740
  const stableAgentTag = `agent-v${deps.settings.agentStableVersion}`;
6503
6741
  for (const [path, { file, contentType }] of Object.entries(TEXT_ASSETS)) {
6504
6742
  app.get(path, async (c) => {
6505
- const body = rewriteDefaultBaseUrl(file, await loadAsset(file), deps.settings.publicBaseUrl);
6506
- return c.text(body, 200, {
6743
+ const body2 = rewriteDefaultBaseUrl(file, await loadAsset(file), deps.settings.publicBaseUrl);
6744
+ return c.text(body2, 200, {
6507
6745
  "content-type": contentType,
6508
6746
  // Short cache: the edge serves the latest committed copy; new installs
6509
6747
  // should pick up script fixes promptly, but a brief cache absorbs bursts.
@@ -6582,7 +6820,7 @@ function isAuthExempt(c, settings) {
6582
6820
  if (path === "/v1/github/setup" || path === "/v1/github/install/callback" || path === "/v1/github/oauth/callback" || path === "/v1/github/app-manifest/callback") {
6583
6821
  return true;
6584
6822
  }
6585
- if (path === "/v1/integrations/oauth/callback" || path === "/v1/integrations/oauth/client-metadata.json" || path === "/v1/integrations/slack/callback") {
6823
+ if (path === "/v1/integrations/oauth/callback" || path === "/v1/integrations/oauth/client-metadata.json" || path === "/v1/integrations/slack/callback" || path === "/v1/integrations/slack/events" || path === "/v1/integrations/slack/commands" || path === "/v1/integrations/slack/interactions") {
6586
6824
  return true;
6587
6825
  }
6588
6826
  if (path.startsWith("/v1/catalog-assets/")) {
@@ -7450,12 +7688,12 @@ function registerCodexRoutes(app, deps) {
7450
7688
  app.patch("/v1/workspaces/:workspaceId/codex/settings", async (c) => {
7451
7689
  const workspaceId = c.req.param("workspaceId");
7452
7690
  const grant = await requireAccessGrant2(c, deps, workspaceId, "workspace:admin");
7453
- const body = await c.req.json().catch(() => ({}));
7691
+ const body2 = await c.req.json().catch(() => ({}));
7454
7692
  const patch = {};
7455
- if (typeof body.rotationEnabled === "boolean") {
7456
- patch.rotationEnabled = body.rotationEnabled;
7693
+ if (typeof body2.rotationEnabled === "boolean") {
7694
+ patch.rotationEnabled = body2.rotationEnabled;
7457
7695
  }
7458
- if (patch.rotationEnabled === void 0 && body.rotationStrategy === void 0) {
7696
+ if (patch.rotationEnabled === void 0 && body2.rotationStrategy === void 0) {
7459
7697
  throw new HTTPException6(400, { message: "no settings to update" });
7460
7698
  }
7461
7699
  if (patch.rotationEnabled === void 0) {
@@ -7488,8 +7726,8 @@ function registerCodexRoutes(app, deps) {
7488
7726
  const workspaceId = c.req.param("workspaceId");
7489
7727
  await requireAccessGrant2(c, deps, workspaceId, "workspace:admin");
7490
7728
  const accountId = c.req.param("accountId");
7491
- const body = await c.req.json();
7492
- const label = typeof body.label === "string" ? body.label : null;
7729
+ const body2 = await c.req.json();
7730
+ const label = typeof body2.label === "string" ? body2.label : null;
7493
7731
  const renamed = await renameCodexAccount(db, workspaceId, accountId, label);
7494
7732
  if (!renamed) {
7495
7733
  throw new HTTPException6(404, { message: "codex account not found" });
@@ -8000,7 +8238,7 @@ function registerCodexRoutes(app, deps) {
8000
8238
  }
8001
8239
 
8002
8240
  // src/routes/connections.ts
8003
- import { createHash as createHash5 } from "crypto";
8241
+ import { createHash as createHash6 } from "crypto";
8004
8242
  import {
8005
8243
  ConnectionResponse,
8006
8244
  CreateConnectionRequest,
@@ -8044,7 +8282,7 @@ import {
8044
8282
  import { HTTPException as HTTPException10 } from "hono/http-exception";
8045
8283
 
8046
8284
  // src/integrations/google-drive.ts
8047
- import { createHash as createHash4, randomBytes as randomBytes2 } from "crypto";
8285
+ import { createHash as createHash5, randomBytes as randomBytes2 } from "crypto";
8048
8286
  import {
8049
8287
  GOOGLE_DRIVE_CREDENTIAL_LABEL,
8050
8288
  GOOGLE_DRIVE_CREDENTIAL_ROLE,
@@ -8105,7 +8343,7 @@ import {
8105
8343
  validateHttpUrl
8106
8344
  } from "@opengeni/network";
8107
8345
  import { Buffer as Buffer3 } from "buffer";
8108
- import { createHash as createHash3, randomBytes } from "crypto";
8346
+ import { createHash as createHash4, randomBytes } from "crypto";
8109
8347
  import { HTTPException as HTTPException8 } from "hono/http-exception";
8110
8348
 
8111
8349
  // src/integrations/provider-domain.ts
@@ -8861,28 +9099,28 @@ async function clientForState(db, settings, state) {
8861
9099
  };
8862
9100
  }
8863
9101
  async function exchangeAuthorizationCode(settings, input) {
8864
- const body = new URLSearchParams();
8865
- body.set("grant_type", "authorization_code");
8866
- body.set("code", input.code);
8867
- body.set("redirect_uri", input.redirectUri);
8868
- body.set("code_verifier", input.verifier);
8869
- body.set("resource", input.resource);
9102
+ const body2 = new URLSearchParams();
9103
+ body2.set("grant_type", "authorization_code");
9104
+ body2.set("code", input.code);
9105
+ body2.set("redirect_uri", input.redirectUri);
9106
+ body2.set("code_verifier", input.verifier);
9107
+ body2.set("resource", input.resource);
8870
9108
  const headers = {
8871
9109
  "content-type": "application/x-www-form-urlencoded",
8872
9110
  accept: "application/json"
8873
9111
  };
8874
9112
  if (input.client.clientSecret && input.client.tokenEndpointAuthMethod === "client_secret_post") {
8875
- body.set("client_id", input.client.clientId);
8876
- body.set("client_secret", input.client.clientSecret);
9113
+ body2.set("client_id", input.client.clientId);
9114
+ body2.set("client_secret", input.client.clientSecret);
8877
9115
  } else if (input.client.clientSecret && input.client.tokenEndpointAuthMethod === "client_secret_basic") {
8878
9116
  headers.authorization = `Basic ${Buffer3.from(`${input.client.clientId}:${input.client.clientSecret}`).toString("base64")}`;
8879
9117
  } else {
8880
- body.set("client_id", input.client.clientId);
9118
+ body2.set("client_id", input.client.clientId);
8881
9119
  }
8882
9120
  const response = await fetchOAuth(input.tokenEndpoint, settings, {
8883
9121
  method: "POST",
8884
9122
  headers,
8885
- body
9123
+ body: body2
8886
9124
  });
8887
9125
  if (!response.ok) {
8888
9126
  const oauthError = await oauthErrorFromResponse(response);
@@ -9256,7 +9494,7 @@ function expiresAtFromTokenResponse(payload) {
9256
9494
  return null;
9257
9495
  }
9258
9496
  function pkceChallenge(verifier) {
9259
- return createHash3("sha256").update(verifier).digest("base64url");
9497
+ return createHash4("sha256").update(verifier).digest("base64url");
9260
9498
  }
9261
9499
  function randomPkceVerifier() {
9262
9500
  return randomBytes(32).toString("base64url");
@@ -9328,7 +9566,7 @@ async function startGoogleDriveOAuth(deps, input) {
9328
9566
  authorizationUrl.searchParams.set("code_challenge_method", "S256");
9329
9567
  authorizationUrl.searchParams.set(
9330
9568
  "code_challenge",
9331
- createHash4("sha256").update(verifier).digest("base64url")
9569
+ createHash5("sha256").update(verifier).digest("base64url")
9332
9570
  );
9333
9571
  return GoogleDriveOAuthStartResponse.parse({
9334
9572
  authorizationUrl: authorizationUrl.toString(),
@@ -9515,8 +9753,8 @@ async function browseGoogleDrive(deps, input) {
9515
9753
  url,
9516
9754
  label: "Google Drive file list"
9517
9755
  });
9518
- const record3 = objectRecord(payload);
9519
- const items = Array.isArray(record3.files) ? record3.files.map(parseDriveItem).filter((item) => item !== null) : [];
9756
+ const record4 = objectRecord(payload);
9757
+ const items = Array.isArray(record4.files) ? record4.files.map(parseDriveItem).filter((item) => item !== null) : [];
9520
9758
  const current = await getConnectionMetadata2(
9521
9759
  deps.db,
9522
9760
  input.workspaceId,
@@ -9528,8 +9766,8 @@ async function browseGoogleDrive(deps, input) {
9528
9766
  parentId,
9529
9767
  current: currentItem,
9530
9768
  items,
9531
- nextPageToken: optionalString(record3.nextPageToken),
9532
- incompleteSearch: record3.incompleteSearch === true
9769
+ nextPageToken: optionalString(record4.nextPageToken),
9770
+ incompleteSearch: record4.incompleteSearch === true
9533
9771
  });
9534
9772
  }
9535
9773
  async function saveGoogleDriveSource(deps, input) {
@@ -9724,7 +9962,7 @@ async function requireGoogleDriveCallbackGrant(deps, state) {
9724
9962
  }
9725
9963
  }
9726
9964
  async function exchangeGoogleAuthorizationCode(input, fetchImpl) {
9727
- const body = new URLSearchParams({
9965
+ const body2 = new URLSearchParams({
9728
9966
  code: input.code,
9729
9967
  code_verifier: input.verifier,
9730
9968
  client_id: input.clientId,
@@ -9738,7 +9976,7 @@ async function exchangeGoogleAuthorizationCode(input, fetchImpl) {
9738
9976
  accept: "application/json",
9739
9977
  "content-type": "application/x-www-form-urlencoded"
9740
9978
  },
9741
- body
9979
+ body: body2
9742
9980
  });
9743
9981
  if (!response.ok) {
9744
9982
  await response.body?.cancel().catch(() => void 0);
@@ -10087,7 +10325,7 @@ function registerConnectionRoutes(app, deps) {
10087
10325
  accountId: state.accountId,
10088
10326
  workspaceId: state.workspaceId,
10089
10327
  subjectId: state.subjectId,
10090
- callbackDigest: createHash5("sha256").update(state.nonce).digest("hex"),
10328
+ callbackDigest: createHash6("sha256").update(state.nonce).digest("hex"),
10091
10329
  installMode: state.connectionId ? "reinstall" : "connect",
10092
10330
  ...failure
10093
10331
  });
@@ -11121,27 +11359,27 @@ function registerFileRoutes(app, deps) {
11121
11359
  app.get("/v1/workspaces/:workspaceId/artifacts/:artifactId", async (c) => {
11122
11360
  const workspaceId = c.req.param("workspaceId");
11123
11361
  await requireAccessGrant4(c, deps, workspaceId, "files:read");
11124
- const artifactId = retainedArtifactId(c.req.param("artifactId"));
11125
- const artifact = await getRetainedFileArtifact(db, workspaceId, artifactId);
11362
+ const artifactId2 = retainedArtifactId(c.req.param("artifactId"));
11363
+ const artifact = await getRetainedFileArtifact(db, workspaceId, artifactId2);
11126
11364
  if (!artifact) {
11127
- return c.json(retainedArtifactUnavailable(artifactId, "deleted"), 404);
11365
+ return c.json(retainedArtifactUnavailable(artifactId2, "deleted"), 404);
11128
11366
  }
11129
11367
  return c.json(retainedArtifactMetadata(artifact));
11130
11368
  });
11131
11369
  app.get("/v1/workspaces/:workspaceId/artifacts/:artifactId/content", async (c) => {
11132
11370
  const workspaceId = c.req.param("workspaceId");
11133
11371
  await requireAccessGrant4(c, deps, workspaceId, "files:read");
11134
- const artifactId = retainedArtifactId(c.req.param("artifactId"));
11135
- const artifact = await getRetainedFileArtifact(db, workspaceId, artifactId);
11372
+ const artifactId2 = retainedArtifactId(c.req.param("artifactId"));
11373
+ const artifact = await getRetainedFileArtifact(db, workspaceId, artifactId2);
11136
11374
  if (!artifact) {
11137
- return c.json(retainedArtifactUnavailable(artifactId, "deleted"), 404);
11375
+ return c.json(retainedArtifactUnavailable(artifactId2, "deleted"), 404);
11138
11376
  }
11139
11377
  const metadata = retainedArtifactMetadata(artifact);
11140
11378
  if (!metadata.available) {
11141
11379
  return c.json(metadata, retainedArtifactUnavailableStatus(metadata.reason));
11142
11380
  }
11143
11381
  if (!objectStorage) {
11144
- return c.json(retainedArtifactUnavailable(artifactId, "missing_storage"), 503);
11382
+ return c.json(retainedArtifactUnavailable(artifactId2, "missing_storage"), 503);
11145
11383
  }
11146
11384
  const rangeHeader = c.req.header("range");
11147
11385
  const range = resolveRetainedOutputRange(
@@ -11180,7 +11418,7 @@ function registerFileRoutes(app, deps) {
11180
11418
  };
11181
11419
  if (range.kind === "empty") {
11182
11420
  if (!await objectStorage.fileExists(artifact.file)) {
11183
- return c.json(retainedArtifactUnavailable(artifactId, "missing_storage"), 410);
11421
+ return c.json(retainedArtifactUnavailable(artifactId2, "missing_storage"), 410);
11184
11422
  }
11185
11423
  return c.body(null, 200, headers);
11186
11424
  }
@@ -11189,7 +11427,7 @@ function registerFileRoutes(app, deps) {
11189
11427
  end: range.end
11190
11428
  });
11191
11429
  if (!bytes) {
11192
- return c.json(retainedArtifactUnavailable(artifactId, "missing_storage"), 410);
11430
+ return c.json(retainedArtifactUnavailable(artifactId2, "missing_storage"), 410);
11193
11431
  }
11194
11432
  if (bytes.byteLength !== range.length) {
11195
11433
  throw new HTTPException11(502, { message: "object storage returned an invalid byte range" });
@@ -11233,8 +11471,8 @@ function retainedArtifactId(value) {
11233
11471
  }
11234
11472
  return parsed.data;
11235
11473
  }
11236
- function retainedArtifactUnavailable(artifactId, reason) {
11237
- return RetainedArtifactMetadataSchema.parse({ available: false, artifactId, reason });
11474
+ function retainedArtifactUnavailable(artifactId2, reason) {
11475
+ return RetainedArtifactMetadataSchema.parse({ available: false, artifactId: artifactId2, reason });
11238
11476
  }
11239
11477
  function retainedArtifactMetadata(artifact) {
11240
11478
  const reference = retainedArtifactReferenceFromFile(artifact.file);
@@ -11920,18 +12158,18 @@ async function lookupDeviceEnrollment(services, input) {
11920
12158
  const { db } = services;
11921
12159
  return await getPendingDeviceEnrollmentRequestByUserCodeGlobal(db, input.userCode);
11922
12160
  }
11923
- function toLookupResponse(record3) {
12161
+ function toLookupResponse(record4) {
11924
12162
  return {
11925
- workspaceId: record3.workspaceId,
11926
- userCode: record3.userCode,
12163
+ workspaceId: record4.workspaceId,
12164
+ userCode: record4.userCode,
11927
12165
  machine: {
11928
- machineName: record3.machineName,
11929
- os: record3.os,
11930
- arch: record3.arch,
11931
- canOfferDisplay: record3.canOfferDisplay,
11932
- requestsScreenControl: record3.requestsScreenControl
12166
+ machineName: record4.machineName,
12167
+ os: record4.os,
12168
+ arch: record4.arch,
12169
+ canOfferDisplay: record4.canOfferDisplay,
12170
+ requestsScreenControl: record4.requestsScreenControl
11933
12171
  },
11934
- expiresAt: record3.expiresAt
12172
+ expiresAt: record4.expiresAt
11935
12173
  };
11936
12174
  }
11937
12175
  async function denyDeviceEnrollment(services, input) {
@@ -12114,8 +12352,8 @@ function registerEnrollmentRoutes(app, deps) {
12114
12352
  if (!parsed.success) {
12115
12353
  throw new HTTPException13(400, { message: "invalid device-start request" });
12116
12354
  }
12117
- const body = parsed.data;
12118
- const workspace = await getWorkspace(db, body.workspaceId);
12355
+ const body2 = parsed.data;
12356
+ const workspace = await getWorkspace(db, body2.workspaceId);
12119
12357
  if (!workspace) {
12120
12358
  throw new HTTPException13(404, { message: "workspace not found" });
12121
12359
  }
@@ -12124,12 +12362,12 @@ function registerEnrollmentRoutes(app, deps) {
12124
12362
  {
12125
12363
  accountId: workspace.accountId,
12126
12364
  workspaceId: workspace.id,
12127
- publicKey: body.publicKey,
12128
- os: body.os,
12129
- arch: body.arch,
12130
- machineName: body.machineName ?? null,
12131
- canOfferDisplay: body.canOfferDisplay,
12132
- requestsScreenControl: body.requestsScreenControl,
12365
+ publicKey: body2.publicKey,
12366
+ os: body2.os,
12367
+ arch: body2.arch,
12368
+ machineName: body2.machineName ?? null,
12369
+ canOfferDisplay: body2.canOfferDisplay,
12370
+ requestsScreenControl: body2.requestsScreenControl,
12133
12371
  // The approve page is served at the SAME origin as this request.
12134
12372
  verificationOrigin: new URL(c.req.url).origin
12135
12373
  }
@@ -12156,19 +12394,19 @@ function registerEnrollmentRoutes(app, deps) {
12156
12394
  if (!parsed.success) {
12157
12395
  throw new HTTPException13(400, { message: "invalid device-lookup request" });
12158
12396
  }
12159
- const record3 = await lookupDeviceEnrollment(
12397
+ const record4 = await lookupDeviceEnrollment(
12160
12398
  { db, settings },
12161
12399
  { userCode: parsed.data.userCode }
12162
12400
  );
12163
- if (!record3) {
12401
+ if (!record4) {
12164
12402
  throw new HTTPException13(404, { message: "no pending enrollment for that code" });
12165
12403
  }
12166
12404
  try {
12167
- await requireAccessGrant6(c, deps, record3.workspaceId, "enrollments:read");
12405
+ await requireAccessGrant6(c, deps, record4.workspaceId, "enrollments:read");
12168
12406
  } catch {
12169
12407
  throw new HTTPException13(404, { message: "no pending enrollment for that code" });
12170
12408
  }
12171
- return c.json(DeviceEnrollmentLookupResponse.parse(toLookupResponse(record3)), 200);
12409
+ return c.json(DeviceEnrollmentLookupResponse.parse(toLookupResponse(record4)), 200);
12172
12410
  });
12173
12411
  app.post("/v1/enrollments/token/exchange", async (c) => {
12174
12412
  assertSelfhostedEnabled();
@@ -12177,16 +12415,16 @@ function registerEnrollmentRoutes(app, deps) {
12177
12415
  if (!parsed.success) {
12178
12416
  throw new HTTPException13(400, { message: "invalid enroll-token-exchange request" });
12179
12417
  }
12180
- const body = parsed.data;
12418
+ const body2 = parsed.data;
12181
12419
  const result = await exchangeEnrollToken(
12182
12420
  { db, settings },
12183
12421
  {
12184
- token: body.token,
12185
- publicKey: body.publicKey,
12186
- os: body.os,
12187
- arch: body.arch,
12188
- machineName: body.machineName ?? null,
12189
- canOfferDisplay: body.canOfferDisplay
12422
+ token: body2.token,
12423
+ publicKey: body2.publicKey,
12424
+ os: body2.os,
12425
+ arch: body2.arch,
12426
+ machineName: body2.machineName ?? null,
12427
+ canOfferDisplay: body2.canOfferDisplay
12190
12428
  }
12191
12429
  );
12192
12430
  if (!result.ok) {
@@ -12205,14 +12443,14 @@ function registerEnrollmentRoutes(app, deps) {
12205
12443
  if (!parsed.success) {
12206
12444
  throw new HTTPException13(400, { message: "invalid device-approve request" });
12207
12445
  }
12208
- const body = parsed.data;
12446
+ const body2 = parsed.data;
12209
12447
  const approved = await approveDeviceEnrollment(
12210
12448
  { db, settings },
12211
12449
  {
12212
12450
  accountId: grant.accountId,
12213
12451
  workspaceId,
12214
- userCode: body.userCode,
12215
- allowScreenControl: body.allowScreenControl,
12452
+ userCode: body2.userCode,
12453
+ allowScreenControl: body2.allowScreenControl,
12216
12454
  // The LOUD consent record: WHO consented (the authenticated subject + label).
12217
12455
  approvedBySubjectId: grant.subjectId,
12218
12456
  approvedBySubjectLabel: grant.subjectLabel ?? null
@@ -12579,7 +12817,7 @@ function registerMachineRoutes(app, deps) {
12579
12817
  const grant = await requireAccessGrant7(c, deps, workspaceId, "sessions:control");
12580
12818
  assertSelfhostedEnabled();
12581
12819
  const sessionId = c.req.param("sessionId");
12582
- const body = SwapActiveSandboxRequest.parse(await c.req.json());
12820
+ const body2 = SwapActiveSandboxRequest.parse(await c.req.json());
12583
12821
  const ctx = await buildFleetContextForSession2(deps, {
12584
12822
  accountId: grant.accountId,
12585
12823
  workspaceId,
@@ -12603,7 +12841,7 @@ function registerMachineRoutes(app, deps) {
12603
12841
  }
12604
12842
  },
12605
12843
  ctx,
12606
- body.target
12844
+ body2.target
12607
12845
  );
12608
12846
  return c.json(SwapActiveSandboxResponse.parse(result));
12609
12847
  });
@@ -12858,8 +13096,8 @@ function registerApiKeyRoutes(app, deps) {
12858
13096
  async (c) => {
12859
13097
  const workspaceId = c.req.param("workspaceId");
12860
13098
  const grant = await requireAccessGrant9(c, deps, workspaceId, "api_keys:manage");
12861
- const body = c.req.valid("json");
12862
- const permissions = body.permissions.length > 0 ? body.permissions : ["workspace:read"];
13099
+ const body2 = c.req.valid("json");
13100
+ const permissions = body2.permissions.length > 0 ? body2.permissions : ["workspace:read"];
12863
13101
  ensureDelegablePermissions(grant.permissions, permissions);
12864
13102
  await requireLimit4(deps, {
12865
13103
  accountId: grant.accountId,
@@ -12872,11 +13110,11 @@ function registerApiKeyRoutes(app, deps) {
12872
13110
  const apiKey = await createApiKey(deps.db, {
12873
13111
  accountId: grant.accountId,
12874
13112
  workspaceId: grant.workspaceId,
12875
- name: body.name,
13113
+ name: body2.name,
12876
13114
  prefix,
12877
13115
  keyHash: await sha256Hex(token),
12878
13116
  permissions,
12879
- expiresAt: body.expiresAt ? new Date(body.expiresAt) : null
13117
+ expiresAt: body2.expiresAt ? new Date(body2.expiresAt) : null
12880
13118
  });
12881
13119
  return c.json(CreateApiKeyResponse.parse({ apiKey, token }), 201);
12882
13120
  }
@@ -12977,9 +13215,9 @@ function registerBillingRoutes(app, deps) {
12977
13215
  message: parsed.error.issues[0]?.message ?? "invalid checkout request"
12978
13216
  });
12979
13217
  }
12980
- const body = parsed.data;
12981
- const accountId = requireSelectedAccount(context, body.accountId, "billing:manage");
12982
- const amountCents = usdToCents(body.amountUsd);
13218
+ const body2 = parsed.data;
13219
+ const accountId = requireSelectedAccount(context, body2.accountId, "billing:manage");
13220
+ const amountCents = usdToCents(body2.amountUsd);
12983
13221
  const amountMicros = centsToMicros(amountCents);
12984
13222
  const stripe = stripeClient(deps);
12985
13223
  const customerId = await getOrCreateStripeCustomer(deps, stripe, context, accountId);
@@ -12992,8 +13230,8 @@ function registerBillingRoutes(app, deps) {
12992
13230
  amountMicros,
12993
13231
  creditsProductId: deps.settings.stripeCreditsProductId,
12994
13232
  publicBaseUrl: deps.settings.publicBaseUrl,
12995
- successUrl: body.successUrl,
12996
- cancelUrl: body.cancelUrl,
13233
+ successUrl: body2.successUrl,
13234
+ cancelUrl: body2.cancelUrl,
12997
13235
  idempotencyKey
12998
13236
  }),
12999
13237
  { idempotencyKey }
@@ -14605,8 +14843,8 @@ function registerScheduledTaskRoutes(app, deps) {
14605
14843
  quantity: 1,
14606
14844
  model: task.agentConfig.model ?? deps.settings.openaiModel
14607
14845
  });
14608
- const body = await c.req.json().catch(() => ({}));
14609
- const { triggerId } = TriggerScheduledTaskRequest.parse(body ?? {});
14846
+ const body2 = await c.req.json().catch(() => ({}));
14847
+ const { triggerId } = TriggerScheduledTaskRequest.parse(body2 ?? {});
14610
14848
  const triggerToken = scheduledTaskTriggerToken2(triggerId);
14611
14849
  const agentRunUsageIdempotencyKey = manualScheduledTaskTriggerUsageKey2(
14612
14850
  workspaceId,
@@ -15103,7 +15341,7 @@ function createByteBoundedSseStream(options = {}) {
15103
15341
  if (!Number.isSafeInteger(stallTimeoutMs) || stallTimeoutMs <= 0) {
15104
15342
  throw new RangeError("SSE write stall timeout must be a positive safe integer");
15105
15343
  }
15106
- const encoder2 = new TextEncoder();
15344
+ const encoder3 = new TextEncoder();
15107
15345
  let controller;
15108
15346
  let stopped = false;
15109
15347
  let capacityWake = null;
@@ -15149,7 +15387,7 @@ function createByteBoundedSseStream(options = {}) {
15149
15387
  return {
15150
15388
  stream,
15151
15389
  write: async (frame) => {
15152
- const chunk = encoder2.encode(frame);
15390
+ const chunk = encoder3.encode(frame);
15153
15391
  if (chunk.byteLength > maxQueuedBytes) {
15154
15392
  const error = new RangeError(
15155
15393
  `SSE frame cannot fit in the configured queue (${chunk.byteLength} > ${maxQueuedBytes} bytes)`
@@ -15862,10 +16100,6 @@ function registerSessionRoutes(app, deps) {
15862
16100
  )
15863
16101
  });
15864
16102
  const authorizeSessionHttp = async (c, next) => {
15865
- if (!deps.sessionAuthorization) {
15866
- await next();
15867
- return;
15868
- }
15869
16103
  const workspaceId = c.req.param("workspaceId") ?? "";
15870
16104
  const sessionId = c.req.param("sessionId") ?? "";
15871
16105
  const operation = sessionAuthorizationOperationForHttp(
@@ -15880,6 +16114,10 @@ function registerSessionRoutes(app, deps) {
15880
16114
  if (!operation) {
15881
16115
  throw sessionAuthorizationHttpError(new SessionAuthorizationUnavailableError());
15882
16116
  }
16117
+ if (operation === "session.codex_account.write" && !deps.sessionAuthorization) {
16118
+ await next();
16119
+ return;
16120
+ }
15883
16121
  const grant = await requireAccessGrant14(c, deps, workspaceId);
15884
16122
  try {
15885
16123
  const authorization = await requireSessionAuthorization2(deps, grant, {
@@ -16112,15 +16350,26 @@ function registerSessionRoutes(app, deps) {
16112
16350
  });
16113
16351
  app.post("/v1/workspaces/:workspaceId/sessions/:sessionId/codex-account", async (c) => {
16114
16352
  const workspaceId = c.req.param("workspaceId");
16115
- await requireAccessGrant14(c, deps, workspaceId, "sessions:control");
16353
+ const grant = await requireAccessGrant14(c, deps, workspaceId, "sessions:control");
16116
16354
  const sessionId = c.req.param("sessionId");
16117
- const body = await c.req.json();
16118
- const target = typeof body.target === "string" ? body.target : "";
16355
+ const body2 = await c.req.json();
16356
+ const target = typeof body2.target === "string" ? body2.target : "";
16119
16357
  if (!target) {
16120
16358
  throw new HTTPException23(400, {
16121
16359
  message: 'target is required ("auto" or an account id)'
16122
16360
  });
16123
16361
  }
16362
+ if (!deps.sessionAuthorization) {
16363
+ try {
16364
+ await requireSessionAuthorization2(deps, grant, {
16365
+ sessionId,
16366
+ operation: "session.codex_account.write",
16367
+ surface: "http"
16368
+ });
16369
+ } catch (error) {
16370
+ throw sessionAuthorizationHttpError(error);
16371
+ }
16372
+ }
16124
16373
  const pinned = target === "auto" ? null : target;
16125
16374
  const mutation = await withCodexCapacityMutation2(
16126
16375
  db,
@@ -19044,6 +19293,265 @@ function registerWorkspaceStateRoutes(app, deps) {
19044
19293
  });
19045
19294
  }
19046
19295
 
19296
+ // src/routes/workspace-artifacts.ts
19297
+ import { createHash as createHash7 } from "crypto";
19298
+ import {
19299
+ CreateWorkspaceArtifactRequest,
19300
+ PublishWorkspaceArtifactVersionRequest,
19301
+ RollbackWorkspaceArtifactRequest,
19302
+ WorkspaceArtifactContentResponse,
19303
+ WorkspaceArtifactDetailResponse,
19304
+ WorkspaceArtifactListQuery,
19305
+ WorkspaceArtifactListResponse,
19306
+ WorkspaceArtifactMutationResponse,
19307
+ normalizeWorkspaceArtifactSlug as normalizeWorkspaceArtifactSlug2
19308
+ } from "@opengeni/contracts";
19309
+ import { requireAccessGrant as requireAccessGrant19 } from "@opengeni/core";
19310
+ import {
19311
+ createWorkspaceArtifact as createWorkspaceArtifact2,
19312
+ getWorkspaceArtifact as getWorkspaceArtifact2,
19313
+ getWorkspaceArtifactContentRef as getWorkspaceArtifactContentRef2,
19314
+ listWorkspaceArtifacts as listWorkspaceArtifacts2,
19315
+ publishWorkspaceArtifactVersion as publishWorkspaceArtifactVersion2,
19316
+ rollbackWorkspaceArtifact as rollbackWorkspaceArtifact2,
19317
+ WorkspaceArtifactConflictError,
19318
+ WorkspaceArtifactNotFoundError,
19319
+ WorkspaceArtifactOperationError
19320
+ } from "@opengeni/db";
19321
+ import { HTTPException as HTTPException28 } from "hono/http-exception";
19322
+ import { z as z8 } from "zod";
19323
+ var ArtifactId = z8.string().uuid();
19324
+ var encoder2 = new TextEncoder();
19325
+ var decoder = new TextDecoder("utf-8", { fatal: true });
19326
+ async function body(context, schema) {
19327
+ const parsed = schema.safeParse(await context.req.json().catch(() => null));
19328
+ if (!parsed.success) throw new HTTPException28(422, { message: "Invalid artifact request" });
19329
+ return parsed.data;
19330
+ }
19331
+ function artifactId(context) {
19332
+ const parsed = ArtifactId.safeParse(context.req.param("artifactId"));
19333
+ if (!parsed.success) throw new HTTPException28(422, { message: "Invalid artifact id" });
19334
+ return parsed.data;
19335
+ }
19336
+ function errorResponse(context, error) {
19337
+ if (error instanceof WorkspaceArtifactNotFoundError) {
19338
+ return context.json({ code: "WORKSPACE_ARTIFACT_NOT_FOUND", message: error.message }, 404);
19339
+ }
19340
+ if (error instanceof WorkspaceArtifactConflictError) {
19341
+ return context.json(
19342
+ {
19343
+ code: "WORKSPACE_ARTIFACT_CONFLICT",
19344
+ message: error.message,
19345
+ currentVersionId: error.currentVersionId
19346
+ },
19347
+ 409
19348
+ );
19349
+ }
19350
+ if (error instanceof WorkspaceArtifactOperationError) {
19351
+ return context.json(
19352
+ { code: "INVALID_WORKSPACE_ARTIFACT_OPERATION", message: error.message },
19353
+ 422
19354
+ );
19355
+ }
19356
+ if (typeof error === "object" && error !== null && "code" in error && error.code === "23505") {
19357
+ return context.json(
19358
+ { code: "WORKSPACE_ARTIFACT_CONFLICT", message: "Artifact slug or operation already exists" },
19359
+ 409
19360
+ );
19361
+ }
19362
+ throw error;
19363
+ }
19364
+ function contentMetadata(workspaceId, html) {
19365
+ const bytes = encoder2.encode(html);
19366
+ const sha256 = createHash7("sha256").update(bytes).digest("hex");
19367
+ return {
19368
+ bytes,
19369
+ contentSha256: sha256,
19370
+ sizeBytes: bytes.byteLength,
19371
+ contentKey: `workspaces/${workspaceId}/workspace-artifacts/blobs/${sha256}.html`
19372
+ };
19373
+ }
19374
+ function prepareHtml(deps, workspaceId, html) {
19375
+ if (!deps.objectStorage)
19376
+ throw new HTTPException28(503, { message: "Object storage is not configured" });
19377
+ const content = contentMetadata(workspaceId, html);
19378
+ return {
19379
+ ...content,
19380
+ persistContent: async () => {
19381
+ await deps.objectStorage.putObject({
19382
+ key: content.contentKey,
19383
+ contentType: "text/html; charset=utf-8",
19384
+ body: content.bytes,
19385
+ sha256: content.contentSha256
19386
+ });
19387
+ }
19388
+ };
19389
+ }
19390
+ function provenance(subjectId, idempotencyKey) {
19391
+ return {
19392
+ operationKey: `subject:${createHash7("sha256").update(`${subjectId}:${idempotencyKey}`).digest("hex")}`,
19393
+ actorSubjectId: subjectId,
19394
+ sourceSessionId: null,
19395
+ sourceTurnId: null,
19396
+ sourceAttemptId: null,
19397
+ sourceExecutionGeneration: null,
19398
+ sourceToolName: null
19399
+ };
19400
+ }
19401
+ function registerWorkspaceArtifactRoutes(app, deps) {
19402
+ const base = "/v1/workspaces/:workspaceId/published-artifacts";
19403
+ app.get(base, async (context) => {
19404
+ const workspaceId = context.req.param("workspaceId");
19405
+ await requireAccessGrant19(context, deps, workspaceId, "artifacts:read");
19406
+ const query = WorkspaceArtifactListQuery.safeParse({
19407
+ limit: context.req.query("limit"),
19408
+ cursor: context.req.query("cursor")
19409
+ });
19410
+ if (!query.success) throw new HTTPException28(422, { message: "Invalid artifact list query" });
19411
+ try {
19412
+ return context.json(
19413
+ WorkspaceArtifactListResponse.parse(
19414
+ await listWorkspaceArtifacts2(deps.db, workspaceId, {
19415
+ limit: query.data.limit,
19416
+ ...query.data.cursor ? { cursor: query.data.cursor } : {}
19417
+ })
19418
+ )
19419
+ );
19420
+ } catch (error) {
19421
+ return errorResponse(context, error);
19422
+ }
19423
+ });
19424
+ app.post(base, async (context) => {
19425
+ const workspaceId = context.req.param("workspaceId");
19426
+ const grant = await requireAccessGrant19(context, deps, workspaceId, "artifacts:publish");
19427
+ const request = await body(context, CreateWorkspaceArtifactRequest);
19428
+ const id = crypto.randomUUID();
19429
+ const slugBase = request.slug ?? (normalizeWorkspaceArtifactSlug2(request.title) || "artifact");
19430
+ const slug = request.slug ?? `${slugBase.slice(0, 87)}-${id.slice(0, 8)}`;
19431
+ const content = prepareHtml(deps, workspaceId, request.html);
19432
+ try {
19433
+ return context.json(
19434
+ WorkspaceArtifactMutationResponse.parse(
19435
+ await createWorkspaceArtifact2(deps.db, {
19436
+ accountId: grant.accountId,
19437
+ workspaceId,
19438
+ artifactId: id,
19439
+ slug,
19440
+ title: request.title,
19441
+ description: request.description ?? null,
19442
+ ...content,
19443
+ ...provenance(grant.subjectId, request.idempotencyKey)
19444
+ })
19445
+ ),
19446
+ 201
19447
+ );
19448
+ } catch (error) {
19449
+ return errorResponse(context, error);
19450
+ }
19451
+ });
19452
+ app.get(`${base}/:artifactId`, async (context) => {
19453
+ const workspaceId = context.req.param("workspaceId");
19454
+ await requireAccessGrant19(context, deps, workspaceId, "artifacts:read");
19455
+ try {
19456
+ return context.json(
19457
+ WorkspaceArtifactDetailResponse.parse(
19458
+ await getWorkspaceArtifact2(deps.db, workspaceId, artifactId(context))
19459
+ )
19460
+ );
19461
+ } catch (error) {
19462
+ return errorResponse(context, error);
19463
+ }
19464
+ });
19465
+ app.get(`${base}/:artifactId/content`, async (context) => {
19466
+ const workspaceId = context.req.param("workspaceId");
19467
+ await requireAccessGrant19(context, deps, workspaceId, "artifacts:read");
19468
+ if (!deps.objectStorage)
19469
+ throw new HTTPException28(503, { message: "Object storage is not configured" });
19470
+ const parsedVersion = context.req.query("versionId");
19471
+ if (parsedVersion && !ArtifactId.safeParse(parsedVersion).success) {
19472
+ throw new HTTPException28(422, { message: "Invalid artifact version id" });
19473
+ }
19474
+ try {
19475
+ const ref = await getWorkspaceArtifactContentRef2(
19476
+ deps.db,
19477
+ workspaceId,
19478
+ artifactId(context),
19479
+ parsedVersion
19480
+ );
19481
+ const object5 = await deps.objectStorage.getObjectBytes(ref.contentKey);
19482
+ if (!object5) throw new HTTPException28(503, { message: "Artifact content is unavailable" });
19483
+ const actualHash = createHash7("sha256").update(object5.bytes).digest("hex");
19484
+ if (actualHash !== ref.version.contentSha256) {
19485
+ throw new HTTPException28(503, { message: "Artifact content failed integrity verification" });
19486
+ }
19487
+ let html;
19488
+ try {
19489
+ html = decoder.decode(object5.bytes);
19490
+ } catch {
19491
+ throw new HTTPException28(503, { message: "Artifact content is not valid UTF-8" });
19492
+ }
19493
+ return context.json(
19494
+ WorkspaceArtifactContentResponse.parse({
19495
+ artifactId: ref.artifactId,
19496
+ versionId: ref.version.id,
19497
+ contentType: "text/html",
19498
+ contentSha256: ref.version.contentSha256,
19499
+ html
19500
+ })
19501
+ );
19502
+ } catch (error) {
19503
+ return errorResponse(context, error);
19504
+ }
19505
+ });
19506
+ app.post(`${base}/:artifactId/versions`, async (context) => {
19507
+ const workspaceId = context.req.param("workspaceId");
19508
+ const grant = await requireAccessGrant19(context, deps, workspaceId, "artifacts:publish");
19509
+ const request = await body(context, PublishWorkspaceArtifactVersionRequest);
19510
+ const id = artifactId(context);
19511
+ const content = prepareHtml(deps, workspaceId, request.html);
19512
+ try {
19513
+ return context.json(
19514
+ WorkspaceArtifactMutationResponse.parse(
19515
+ await publishWorkspaceArtifactVersion2(deps.db, {
19516
+ accountId: grant.accountId,
19517
+ workspaceId,
19518
+ artifactId: id,
19519
+ expectedCurrentVersionId: request.expectedCurrentVersionId,
19520
+ ...request.title !== void 0 ? { title: request.title } : {},
19521
+ ...request.description !== void 0 ? { description: request.description } : {},
19522
+ ...content,
19523
+ ...provenance(grant.subjectId, request.idempotencyKey)
19524
+ })
19525
+ )
19526
+ );
19527
+ } catch (error) {
19528
+ return errorResponse(context, error);
19529
+ }
19530
+ });
19531
+ app.post(`${base}/:artifactId/rollback`, async (context) => {
19532
+ const workspaceId = context.req.param("workspaceId");
19533
+ const grant = await requireAccessGrant19(context, deps, workspaceId, "artifacts:publish");
19534
+ const request = await body(context, RollbackWorkspaceArtifactRequest);
19535
+ try {
19536
+ return context.json(
19537
+ WorkspaceArtifactMutationResponse.parse(
19538
+ await rollbackWorkspaceArtifact2(deps.db, {
19539
+ accountId: grant.accountId,
19540
+ workspaceId,
19541
+ artifactId: artifactId(context),
19542
+ versionId: request.versionId,
19543
+ expectedCurrentVersionId: request.expectedCurrentVersionId,
19544
+ reason: request.reason,
19545
+ ...provenance(grant.subjectId, request.idempotencyKey)
19546
+ })
19547
+ )
19548
+ );
19549
+ } catch (error) {
19550
+ return errorResponse(context, error);
19551
+ }
19552
+ });
19553
+ }
19554
+
19047
19555
  // src/routes/preference-registry.ts
19048
19556
  import {
19049
19557
  ActivatePreferenceRegistryRevisionRequest,
@@ -19065,7 +19573,7 @@ import {
19065
19573
  } from "@opengeni/contracts";
19066
19574
  import {
19067
19575
  hasPermission as hasPermission12,
19068
- requireAccessGrant as requireAccessGrant19,
19576
+ requireAccessGrant as requireAccessGrant20,
19069
19577
  requireAccessGrantAuthorization
19070
19578
  } from "@opengeni/core";
19071
19579
  import {
@@ -19088,13 +19596,13 @@ import {
19088
19596
  rejectPreferenceRegistryProposal,
19089
19597
  supersedePreferenceRegistry
19090
19598
  } from "@opengeni/db";
19091
- import { HTTPException as HTTPException28 } from "hono/http-exception";
19092
- import { z as z8 } from "zod";
19093
- var Id = z8.string().uuid();
19599
+ import { HTTPException as HTTPException29 } from "hono/http-exception";
19600
+ import { z as z9 } from "zod";
19601
+ var Id = z9.string().uuid();
19094
19602
  async function parseBody2(context, schema) {
19095
19603
  const parsed = schema.safeParse(await context.req.json().catch(() => null));
19096
19604
  if (!parsed.success)
19097
- throw new HTTPException28(422, { message: "Invalid preference registry request" });
19605
+ throw new HTTPException29(422, { message: "Invalid preference registry request" });
19098
19606
  return parsed.data;
19099
19607
  }
19100
19608
  function exactAttemptClaims(grant) {
@@ -19108,14 +19616,14 @@ function exactAttemptClaims(grant) {
19108
19616
  const present = Object.values(values).filter((value) => value !== void 0).length;
19109
19617
  if (present === 0) return null;
19110
19618
  if (typeof values.sessionId !== "string" || typeof values.turnId !== "string" || typeof values.attemptId !== "string" || typeof values.executionGeneration !== "number" || !Number.isSafeInteger(values.executionGeneration) || values.executionGeneration < 1 || values.executionGeneration > 2147483647) {
19111
- throw new HTTPException28(403, { message: "Incomplete signed preference attempt authority" });
19619
+ throw new HTTPException29(403, { message: "Incomplete signed preference attempt authority" });
19112
19620
  }
19113
19621
  return values;
19114
19622
  }
19115
19623
  function requiredAttemptClaims(grant) {
19116
19624
  const claims = exactAttemptClaims(grant);
19117
19625
  if (!claims) {
19118
- throw new HTTPException28(403, {
19626
+ throw new HTTPException29(403, {
19119
19627
  message: "Preference snapshot retrieval requires a signed session, turn, and attempt"
19120
19628
  });
19121
19629
  }
@@ -19128,7 +19636,7 @@ function requiredAttemptClaims(grant) {
19128
19636
  function requireHumanMutation(access) {
19129
19637
  const { grant } = access;
19130
19638
  if (!access.contextIntegrity || access.authenticatedSubjectId !== grant.subjectId || grant.principalKind !== "human_session" || exactAttemptClaims(grant) !== null || grant.serviceInitiator || grant.serviceInitiatorContext || grant.subjectId.startsWith("api_key:")) {
19131
- throw new HTTPException28(403, {
19639
+ throw new HTTPException29(403, {
19132
19640
  message: "Preference activation and scope mutation require a direct human-authorized request"
19133
19641
  });
19134
19642
  }
@@ -19138,12 +19646,12 @@ function authorizePreferenceRegistryScopeMutation(access, scope) {
19138
19646
  requireHumanMutation(access);
19139
19647
  if (scope === "organization") {
19140
19648
  if (!access.accountGrant?.permissions.includes("account:admin")) {
19141
- throw new HTTPException28(403, { message: "missing permission: account:admin" });
19649
+ throw new HTTPException29(403, { message: "missing permission: account:admin" });
19142
19650
  }
19143
19651
  return;
19144
19652
  }
19145
19653
  if (scope === "workspace" && !hasPermission12(grant.permissions, "workspace:admin")) {
19146
- throw new HTTPException28(403, { message: "missing permission: workspace:admin" });
19654
+ throw new HTTPException29(403, { message: "missing permission: workspace:admin" });
19147
19655
  }
19148
19656
  }
19149
19657
  function preferenceError(context, error) {
@@ -19180,21 +19688,21 @@ function preferenceError(context, error) {
19180
19688
  }
19181
19689
  function preferenceId(context) {
19182
19690
  const parsed = Id.safeParse(context.req.param("preferenceId"));
19183
- if (!parsed.success) throw new HTTPException28(422, { message: "Invalid preference id" });
19691
+ if (!parsed.success) throw new HTTPException29(422, { message: "Invalid preference id" });
19184
19692
  return parsed.data;
19185
19693
  }
19186
19694
  function registerPreferenceRegistryRoutes(app, deps) {
19187
19695
  const base = "/v1/workspaces/:workspaceId/preferences";
19188
19696
  app.get(base, async (context) => {
19189
19697
  const workspaceId = context.req.param("workspaceId");
19190
- const grant = await requireAccessGrant19(context, deps, workspaceId, "workspace:read");
19698
+ const grant = await requireAccessGrant20(context, deps, workspaceId, "workspace:read");
19191
19699
  const query = PreferenceRegistryListQuery.safeParse({
19192
19700
  scope: context.req.query("scope"),
19193
19701
  status: context.req.query("status"),
19194
19702
  limit: context.req.query("limit")
19195
19703
  });
19196
19704
  if (!query.success)
19197
- throw new HTTPException28(422, { message: "Invalid preference registry query" });
19705
+ throw new HTTPException29(422, { message: "Invalid preference registry query" });
19198
19706
  try {
19199
19707
  const claims = exactAttemptClaims(grant);
19200
19708
  const result = claims ? await listPreferenceRegistryForAttempt(deps.db, {
@@ -19242,7 +19750,7 @@ function registerPreferenceRegistryRoutes(app, deps) {
19242
19750
  });
19243
19751
  app.get(`${base}/summary`, async (context) => {
19244
19752
  const workspaceId = context.req.param("workspaceId");
19245
- const grant = await requireAccessGrant19(context, deps, workspaceId, "workspace:read");
19753
+ const grant = await requireAccessGrant20(context, deps, workspaceId, "workspace:read");
19246
19754
  try {
19247
19755
  return context.json(
19248
19756
  PreferenceRegistrySnapshot.parse(
@@ -19255,7 +19763,7 @@ function registerPreferenceRegistryRoutes(app, deps) {
19255
19763
  });
19256
19764
  app.post(`${base}/full-content`, async (context) => {
19257
19765
  const workspaceId = context.req.param("workspaceId");
19258
- const grant = await requireAccessGrant19(context, deps, workspaceId, "workspace:read");
19766
+ const grant = await requireAccessGrant20(context, deps, workspaceId, "workspace:read");
19259
19767
  const request = await parseBody2(context, PreferenceRegistryFullContentRequest);
19260
19768
  try {
19261
19769
  return context.json(
@@ -19273,7 +19781,7 @@ function registerPreferenceRegistryRoutes(app, deps) {
19273
19781
  });
19274
19782
  app.get(`${base}/:preferenceId`, async (context) => {
19275
19783
  const workspaceId = context.req.param("workspaceId");
19276
- const grant = await requireAccessGrant19(context, deps, workspaceId, "workspace:read");
19784
+ const grant = await requireAccessGrant20(context, deps, workspaceId, "workspace:read");
19277
19785
  try {
19278
19786
  const claims = exactAttemptClaims(grant);
19279
19787
  const id = preferenceId(context);
@@ -19470,16 +19978,16 @@ function registerPreferenceRegistryRoutes(app, deps) {
19470
19978
 
19471
19979
  // src/routes/insights.ts
19472
19980
  import { InsightsRange, WorkspaceInsightsResponse } from "@opengeni/contracts";
19473
- import { getWorkspaceInsights, requireAccessGrant as requireAccessGrant20 } from "@opengeni/core";
19474
- import { HTTPException as HTTPException29 } from "hono/http-exception";
19981
+ import { getWorkspaceInsights, requireAccessGrant as requireAccessGrant21 } from "@opengeni/core";
19982
+ import { HTTPException as HTTPException30 } from "hono/http-exception";
19475
19983
  function registerInsightsRoutes(app, deps) {
19476
19984
  app.get("/v1/workspaces/:workspaceId/insights", async (c) => {
19477
19985
  const workspaceId = c.req.param("workspaceId");
19478
- await requireAccessGrant20(c, deps, workspaceId, "workspace:admin");
19986
+ await requireAccessGrant21(c, deps, workspaceId, "workspace:admin");
19479
19987
  const rangeRaw = c.req.query("range") ?? "week";
19480
19988
  const rangeParsed = InsightsRange.safeParse(rangeRaw);
19481
19989
  if (!rangeParsed.success) {
19482
- throw new HTTPException29(400, {
19990
+ throw new HTTPException30(400, {
19483
19991
  message: "range must be one of today|week|month|ytd"
19484
19992
  });
19485
19993
  }
@@ -19500,12 +20008,12 @@ function registerInsightsRoutes(app, deps) {
19500
20008
  import {
19501
20009
  resolveWorkspaceVoiceInputEnabled
19502
20010
  } from "@opengeni/contracts";
19503
- import { requireAccessGrant as requireAccessGrant21, TranscriptionServiceError } from "@opengeni/core";
20011
+ import { requireAccessGrant as requireAccessGrant22, TranscriptionServiceError } from "@opengeni/core";
19504
20012
  import { getWorkspace as getWorkspace3 } from "@opengeni/db";
19505
20013
  function registerTranscriptionRoutes(app, deps) {
19506
20014
  app.post("/v1/workspaces/:workspaceId/transcriptions", async (c) => {
19507
20015
  const workspaceId = c.req.param("workspaceId");
19508
- const grant = await requireAccessGrant21(c, deps, workspaceId, "sessions:create");
20016
+ const grant = await requireAccessGrant22(c, deps, workspaceId, "sessions:create");
19509
20017
  const workspace = await getWorkspace3(deps.db, workspaceId);
19510
20018
  if (!workspace) return c.json({ code: "not_found" }, 404);
19511
20019
  if (resolveWorkspaceVoiceInputEnabled(workspace.settings) === false) {
@@ -19516,13 +20024,13 @@ function registerTranscriptionRoutes(app, deps) {
19516
20024
  return c.json({ code: "unavailable" }, 503);
19517
20025
  }
19518
20026
  try {
19519
- const body = await audioRequest(c.req.raw, service.limits().maxSizeBytes);
20027
+ const body2 = await audioRequest(c.req.raw, service.limits().maxSizeBytes);
19520
20028
  const result = await service.transcribe({
19521
20029
  workspaceId,
19522
20030
  accountId: grant.accountId,
19523
- audio: body.audio,
19524
- mimeType: body.mimeType,
19525
- durationSeconds: body.durationSeconds,
20031
+ audio: body2.audio,
20032
+ mimeType: body2.mimeType,
20033
+ durationSeconds: body2.durationSeconds,
19526
20034
  signal: c.req.raw.signal,
19527
20035
  requestId: c.req.header("x-opengeni-correlation-id") ?? crypto.randomUUID()
19528
20036
  });
@@ -19585,13 +20093,13 @@ async function audioRequest(request, maxSizeBytes) {
19585
20093
  ...durationSeconds === void 0 ? {} : { durationSeconds }
19586
20094
  };
19587
20095
  }
19588
- async function readBounded(body, maxSizeBytes, signal) {
19589
- if (!body)
20096
+ async function readBounded(body2, maxSizeBytes, signal) {
20097
+ if (!body2)
19590
20098
  throw new TranscriptionServiceError({
19591
20099
  code: "invalid_audio",
19592
20100
  message: "Audio is required."
19593
20101
  });
19594
- const reader = body.getReader();
20102
+ const reader = body2.getReader();
19595
20103
  const chunks = [];
19596
20104
  let size = 0;
19597
20105
  try {
@@ -19671,16 +20179,16 @@ function createOpenAiTranscriptionProvider(input) {
19671
20179
  throw fetchError(error);
19672
20180
  }
19673
20181
  if (!response.ok) throw responseError(response.status);
19674
- const body = await response.json().catch(() => null);
19675
- if (!body || typeof body.text !== "string") {
20182
+ const body2 = await response.json().catch(() => null);
20183
+ if (!body2 || typeof body2.text !== "string") {
19676
20184
  throw new TranscriptionServiceError2({
19677
20185
  code: "provider",
19678
20186
  message: "Invalid transcription response."
19679
20187
  });
19680
20188
  }
19681
20189
  return {
19682
- text: body.text,
19683
- languages: typeof body.language === "string" && body.language ? [body.language] : []
20190
+ text: body2.text,
20191
+ languages: typeof body2.language === "string" && body2.language ? [body2.language] : []
19684
20192
  };
19685
20193
  }
19686
20194
  };
@@ -19757,13 +20265,13 @@ function createAzureOpenAiTranscriptionProvider(input) {
19757
20265
  throw fetchError(error);
19758
20266
  }
19759
20267
  if (!response.ok) throw responseError(response.status);
19760
- const body = await response.json().catch(() => null);
19761
- if (!body || typeof body.text !== "string") {
20268
+ const body2 = await response.json().catch(() => null);
20269
+ if (!body2 || typeof body2.text !== "string") {
19762
20270
  throw responseError(502);
19763
20271
  }
19764
20272
  return {
19765
- text: body.text,
19766
- languages: typeof body.language === "string" && body.language ? [body.language] : []
20273
+ text: body2.text,
20274
+ languages: typeof body2.language === "string" && body2.language ? [body2.language] : []
19767
20275
  };
19768
20276
  }
19769
20277
  };
@@ -19843,11 +20351,11 @@ function createCodexSubscriptionTranscriptionProvider(input) {
19843
20351
  throw fetchError(error);
19844
20352
  }
19845
20353
  if (!response.ok) throw responseError(response.status);
19846
- const body = await response.json().catch(() => null);
19847
- if (!body || typeof body.text !== "string") throw responseError(502);
20354
+ const body2 = await response.json().catch(() => null);
20355
+ if (!body2 || typeof body2.text !== "string") throw responseError(502);
19848
20356
  return {
19849
- text: body.text,
19850
- languages: typeof body.language === "string" && body.language ? [body.language] : []
20357
+ text: body2.text,
20358
+ languages: typeof body2.language === "string" && body2.language ? [body2.language] : []
19851
20359
  };
19852
20360
  }
19853
20361
  };
@@ -19945,6 +20453,738 @@ async function firstAvailable(providers, context) {
19945
20453
  return null;
19946
20454
  }
19947
20455
 
20456
+ // src/integrations/slack-interactions.ts
20457
+ import { createHash as createHash8, createHmac as createHmac2, timingSafeEqual as timingSafeEqual2 } from "crypto";
20458
+ import {
20459
+ acceptSessionHumanInputResponse as acceptSessionHumanInputResponse2,
20460
+ advanceSlackInteractionDelivery,
20461
+ bindSlackInteractionSession,
20462
+ claimSlackInteractionDelivery,
20463
+ claimSlackInteractionProgressDelivery,
20464
+ claimSlackInteractionInbox,
20465
+ closeSlackInteractionDelivery,
20466
+ deleteSlackBotUserLink,
20467
+ enqueueSlackInteractionInbox,
20468
+ getOrCreateSlackInteraction,
20469
+ getSlackBotUserLink,
20470
+ getSlackInteractionByRoute,
20471
+ getWorkspaceGrant as getWorkspaceGrant4,
20472
+ listSessionEventPage as listSessionEventPage3,
20473
+ listSessionHumanInputRequests as listSessionHumanInputRequests2,
20474
+ rekeySlackInteractionRoute,
20475
+ reopenSlackInteractionDelivery,
20476
+ releaseSlackInteractionDelivery,
20477
+ releaseSlackInteractionInbox,
20478
+ resolveSlackInstallationRoute,
20479
+ saveSlackBotUserLink,
20480
+ settleSlackInteractionInbox
20481
+ } from "@opengeni/db";
20482
+ import {
20483
+ acceptSessionUserMessage as acceptSessionUserMessage3,
20484
+ controlHumanSessionWorkstream as controlHumanSessionWorkstream3,
20485
+ createSessionForRequest as createSessionForRequest3,
20486
+ hasPermission as hasPermission13,
20487
+ requireAccessGrant as requireAccessGrant23
20488
+ } from "@opengeni/core";
20489
+ import { publishDurableSessionEvents as publishDurableSessionEvents3 } from "@opengeni/events";
20490
+ import { HTTPException as HTTPException31 } from "hono/http-exception";
20491
+ var SLACK_INTERACTION_MAX_BODY_BYTES = 256 * 1024;
20492
+ var SLACK_SIGNATURE_REPLAY_WINDOW_SECONDS = 300;
20493
+ var SLACK_DELIVERY_EVENT_TYPES = [
20494
+ "agent.message.completed",
20495
+ "session.humanInput.requested",
20496
+ "turn.completed",
20497
+ "turn.failed",
20498
+ "turn.cancelled",
20499
+ "session.status.changed"
20500
+ ];
20501
+ var MAX_SLACK_TEXT_CHARS = 3500;
20502
+ var MAX_SLACK_INPUT_CHARS = 8e3;
20503
+ var MAX_PROGRESS_MESSAGES = 3;
20504
+ var INBOX_LEASE_MS = 3e4;
20505
+ var DELIVERY_LEASE_MS = 3e4;
20506
+ var SLACK_TASK_INSTRUCTIONS = [
20507
+ "This turn originated from Slack. Slack message and thread context is task-local only.",
20508
+ "Do not write Slack context to Documents, Knowledge, Memory, preferences, Workspace Charter, instructions, or policy unless a separate explicit authorized user action requests it.",
20509
+ "Never expose private reasoning, credentials, secrets, raw logs, or unbounded output.",
20510
+ "Keep user-visible output concise, bounded, and safe to send back to Slack."
20511
+ ].join(" ");
20512
+ function verifySlackRequestSignature(input, signingSecret, nowMs = Date.now()) {
20513
+ if (!/^\d{1,16}$/.test(input.timestamp ?? "") || !/^v0=[0-9a-f]{64}$/.test(input.signature ?? "")) {
20514
+ return false;
20515
+ }
20516
+ const timestamp = Number(input.timestamp);
20517
+ if (!Number.isSafeInteger(timestamp)) return false;
20518
+ const nowSeconds = Math.floor(nowMs / 1e3);
20519
+ if (Math.abs(nowSeconds - timestamp) > SLACK_SIGNATURE_REPLAY_WINDOW_SECONDS) return false;
20520
+ const expected = `v0=${createHmac2("sha256", signingSecret).update(`v0:${input.timestamp}:${input.rawBody}`).digest("hex")}`;
20521
+ const actualBytes = Buffer.from(input.signature, "utf8");
20522
+ const expectedBytes = Buffer.from(expected, "utf8");
20523
+ return actualBytes.length === expectedBytes.length && timingSafeEqual2(actualBytes, expectedBytes);
20524
+ }
20525
+ function slackEventInboxEntry(payload, bot) {
20526
+ const envelope = record3(payload);
20527
+ if (!envelope || envelope.type !== "event_callback") return null;
20528
+ const event = record3(envelope.event);
20529
+ const teamId = boundedString(envelope.team_id, 64);
20530
+ const eventId = boundedString(envelope.event_id, 256);
20531
+ if (!event || !teamId || !eventId) return null;
20532
+ if (event.bot_id || event.bot_profile || event.subtype || event.user === bot.botUserId)
20533
+ return null;
20534
+ const userId = boundedString(event.user, 64);
20535
+ const channelId = boundedString(event.channel, 64);
20536
+ const timestamp = boundedString(event.ts, 64);
20537
+ const threadTimestamp = boundedString(event.thread_ts, 64);
20538
+ const text = boundedText(event.text);
20539
+ if (!userId || !channelId || !timestamp || !text) return null;
20540
+ let triggerKind;
20541
+ if (event.type === "app_mention") {
20542
+ triggerKind = "app_mention";
20543
+ } else if (event.type === "message" && threadTimestamp) {
20544
+ triggerKind = "thread_reply";
20545
+ } else if (event.type === "message" && event.channel_type === "im") {
20546
+ triggerKind = "dm";
20547
+ } else {
20548
+ return null;
20549
+ }
20550
+ return {
20551
+ providerEventId: eventId,
20552
+ providerMessageId: `${teamId}:${channelId}:${timestamp}`,
20553
+ slackTeamId: teamId,
20554
+ slackUserId: userId,
20555
+ slackChannelId: channelId,
20556
+ slackMessageTs: timestamp,
20557
+ slackThreadTs: threadTimestamp,
20558
+ triggerKind,
20559
+ text
20560
+ };
20561
+ }
20562
+ function registerSlackInteractionRoutes(app, deps) {
20563
+ app.post("/v1/integrations/slack/events", async (c) => {
20564
+ const signed = await readSignedSlackRequest(c, deps);
20565
+ const payload = parseJsonObject(signed.rawBody);
20566
+ if (payload.type === "url_verification") {
20567
+ const challenge = boundedString(payload.challenge, 512);
20568
+ if (!challenge) throw new HTTPException31(400, { message: "invalid Slack challenge" });
20569
+ return c.json({ challenge });
20570
+ }
20571
+ const teamId = boundedString(payload.team_id, 64);
20572
+ if (!teamId) throw new HTTPException31(400, { message: "invalid Slack event" });
20573
+ const installation = await resolveSlackInstallationRoute(deps.db, teamId);
20574
+ if (!installation)
20575
+ throw new HTTPException31(403, {
20576
+ message: "Slack installation unavailable"
20577
+ });
20578
+ const entry = slackEventInboxEntry(payload, installation);
20579
+ if (entry) await enqueueNormalizedSlackInteraction(deps, installation, entry);
20580
+ return c.json({ ok: true });
20581
+ });
20582
+ app.post("/v1/integrations/slack/commands", async (c) => {
20583
+ const signed = await readSignedSlackRequest(c, deps);
20584
+ const form = new URLSearchParams(signed.rawBody);
20585
+ if (form.get("command") !== "/opengeni") {
20586
+ throw new HTTPException31(400, { message: "invalid Slack command" });
20587
+ }
20588
+ const entry = normalizedFormInteraction(form, "slash_command");
20589
+ const installation = await resolveSlackInstallationRoute(deps.db, entry.slackTeamId);
20590
+ if (!installation)
20591
+ throw new HTTPException31(403, {
20592
+ message: "Slack installation unavailable"
20593
+ });
20594
+ await enqueueNormalizedSlackInteraction(deps, installation, entry);
20595
+ return c.text("OpenGeni accepted this task and will reply in a thread.", 200);
20596
+ });
20597
+ app.post("/v1/integrations/slack/interactions", async (c) => {
20598
+ const signed = await readSignedSlackRequest(c, deps);
20599
+ const form = new URLSearchParams(signed.rawBody);
20600
+ const payload = parseJsonObject(form.get("payload") ?? "");
20601
+ if (payload.type !== "message_action") {
20602
+ throw new HTTPException31(400, {
20603
+ message: "unsupported Slack interaction"
20604
+ });
20605
+ }
20606
+ const team = record3(payload.team);
20607
+ const user = record3(payload.user);
20608
+ const channel = record3(payload.channel);
20609
+ const message = record3(payload.message);
20610
+ const triggerId = boundedString(payload.trigger_id, 256);
20611
+ const teamId = boundedString(team?.id, 64);
20612
+ const userId = boundedString(user?.id, 64);
20613
+ const channelId = boundedString(channel?.id, 64);
20614
+ const messageTs = boundedString(message?.ts, 64);
20615
+ const threadTs = boundedString(message?.thread_ts, 64);
20616
+ const text = boundedText(message?.text);
20617
+ if (!triggerId || !teamId || !userId || !channelId || !messageTs || !text) {
20618
+ throw new HTTPException31(400, {
20619
+ message: "invalid Slack message shortcut"
20620
+ });
20621
+ }
20622
+ const installation = await resolveSlackInstallationRoute(deps.db, teamId);
20623
+ if (!installation)
20624
+ throw new HTTPException31(403, {
20625
+ message: "Slack installation unavailable"
20626
+ });
20627
+ await enqueueNormalizedSlackInteraction(deps, installation, {
20628
+ providerEventId: `shortcut:${triggerId}`,
20629
+ providerMessageId: `shortcut:${triggerId}`,
20630
+ slackTeamId: teamId,
20631
+ slackUserId: userId,
20632
+ slackChannelId: channelId,
20633
+ slackMessageTs: messageTs,
20634
+ slackThreadTs: threadTs,
20635
+ triggerKind: "message_shortcut",
20636
+ text
20637
+ });
20638
+ return c.json({ ok: true });
20639
+ });
20640
+ app.post("/v1/workspaces/:workspaceId/integrations/slack/user-links", async (c) => {
20641
+ const workspaceId = c.req.param("workspaceId");
20642
+ const grant = await requireAccessGrant23(c, deps, workspaceId, "connections:write");
20643
+ const body2 = record3(await c.req.json().catch(() => null));
20644
+ const connectionId = boundedString(body2?.connectionId, 64);
20645
+ const slackTeamId = boundedString(body2?.slackTeamId, 64);
20646
+ const slackUserId = boundedString(body2?.slackUserId, 64);
20647
+ if (!connectionId || !slackTeamId || !slackUserId) {
20648
+ throw new HTTPException31(400, {
20649
+ message: "invalid Slack identity link request"
20650
+ });
20651
+ }
20652
+ const route = await resolveSlackInstallationRoute(deps.db, slackTeamId);
20653
+ if (!route || route.workspaceId !== workspaceId || route.connectionId !== connectionId) {
20654
+ throw new HTTPException31(404, {
20655
+ message: "Slack installation not found"
20656
+ });
20657
+ }
20658
+ return c.json(
20659
+ await saveSlackBotUserLink(deps.db, {
20660
+ accountId: grant.accountId,
20661
+ workspaceId,
20662
+ connectionId,
20663
+ slackTeamId,
20664
+ slackUserId,
20665
+ subjectId: grant.subjectId,
20666
+ linkedBySubjectId: grant.subjectId
20667
+ }),
20668
+ 201
20669
+ );
20670
+ });
20671
+ app.delete(
20672
+ "/v1/workspaces/:workspaceId/integrations/slack/user-links/:slackUserId",
20673
+ async (c) => {
20674
+ const workspaceId = c.req.param("workspaceId");
20675
+ await requireAccessGrant23(c, deps, workspaceId, "connections:write");
20676
+ const connectionId = boundedString(c.req.query("connectionId"), 64);
20677
+ if (!connectionId) throw new HTTPException31(400, { message: "connectionId is required" });
20678
+ return c.json({
20679
+ deleted: await deleteSlackBotUserLink(
20680
+ deps.db,
20681
+ workspaceId,
20682
+ connectionId,
20683
+ c.req.param("slackUserId")
20684
+ )
20685
+ });
20686
+ }
20687
+ );
20688
+ }
20689
+ async function drainSlackInteractionsOnce(deps) {
20690
+ const holder = crypto.randomUUID();
20691
+ const entry = await claimSlackInteractionInbox(deps.db, holder, INBOX_LEASE_MS);
20692
+ if (entry) {
20693
+ try {
20694
+ await processSlackInboxEntry(deps, entry);
20695
+ await settleSlackInteractionInbox(deps.db, {
20696
+ entry,
20697
+ claimHolderId: holder,
20698
+ outcome: "processed"
20699
+ });
20700
+ } catch (error) {
20701
+ const code = safeErrorCode(error);
20702
+ if (entry.attemptCount >= 5 || permanentSlackInteractionError(error)) {
20703
+ await settleSlackInteractionInbox(deps.db, {
20704
+ entry,
20705
+ claimHolderId: holder,
20706
+ outcome: "failed",
20707
+ errorCode: code
20708
+ });
20709
+ } else {
20710
+ await releaseSlackInteractionInbox(deps.db, {
20711
+ entry,
20712
+ claimHolderId: holder,
20713
+ errorCode: code
20714
+ });
20715
+ }
20716
+ }
20717
+ return true;
20718
+ }
20719
+ const deliveryHolder = crypto.randomUUID();
20720
+ const interaction = await claimSlackInteractionDelivery(
20721
+ deps.db,
20722
+ deliveryHolder,
20723
+ DELIVERY_LEASE_MS
20724
+ );
20725
+ if (!interaction) return false;
20726
+ try {
20727
+ await deliverSlackSessionEvents(deps, interaction, deliveryHolder);
20728
+ } catch {
20729
+ await releaseSlackInteractionDelivery(deps.db, {
20730
+ ...interaction,
20731
+ claimHolderId: deliveryHolder
20732
+ }).catch(() => void 0);
20733
+ }
20734
+ return true;
20735
+ }
20736
+ function startSlackInteractionPump(deps, options = {}) {
20737
+ let stopped = false;
20738
+ let running = false;
20739
+ const intervalMs = Math.max(250, options.intervalMs ?? 1e3);
20740
+ const maxPerTick = Math.max(1, Math.min(50, options.maxPerTick ?? 10));
20741
+ const tick = async () => {
20742
+ if (stopped || running) return;
20743
+ running = true;
20744
+ try {
20745
+ for (let index = 0; index < maxPerTick; index += 1) {
20746
+ if (!await drainSlackInteractionsOnce(deps)) break;
20747
+ }
20748
+ } finally {
20749
+ running = false;
20750
+ }
20751
+ };
20752
+ const timer = setInterval(() => void tick(), intervalMs);
20753
+ void tick();
20754
+ return () => {
20755
+ stopped = true;
20756
+ clearInterval(timer);
20757
+ };
20758
+ }
20759
+ async function processSlackInboxEntry(deps, entry) {
20760
+ const routeKey = slackRouteKey(entry.slackChannelId, entry.slackThreadTs ?? entry.slackMessageTs);
20761
+ const existing = await getSlackInteractionByRoute(
20762
+ deps.db,
20763
+ entry.workspaceId,
20764
+ entry.connectionId,
20765
+ routeKey
20766
+ );
20767
+ if (entry.triggerKind === "thread_reply" && !existing) return;
20768
+ const link = await getSlackBotUserLink(
20769
+ deps.db,
20770
+ entry.workspaceId,
20771
+ entry.connectionId,
20772
+ entry.slackUserId
20773
+ );
20774
+ const client = await createOpenGeniSlackBotInteractionClient(deps, {
20775
+ accountId: entry.accountId,
20776
+ workspaceId: entry.workspaceId,
20777
+ connectionId: entry.connectionId,
20778
+ subjectId: link?.subjectId ?? "service:slack-interaction",
20779
+ ...existing?.sessionId ? { sessionId: existing.sessionId } : {}
20780
+ });
20781
+ await client.verifyChannelAccess(entry.slackChannelId);
20782
+ if (!link) {
20783
+ await client.postMessage({
20784
+ operationId: deterministicUuid(`slack-link:${entry.id}`),
20785
+ channelId: entry.slackChannelId,
20786
+ ...entry.slackThreadTs ? { threadTimestamp: entry.slackThreadTs } : {},
20787
+ text: `Link your Slack identity to OpenGeni before starting work: ${linkUrl(deps, entry)}. No session was created.`
20788
+ });
20789
+ return;
20790
+ }
20791
+ const grant = await getWorkspaceGrant4(deps.db, link.subjectId, entry.workspaceId, {
20792
+ principalKind: "human_session"
20793
+ });
20794
+ if (!grant || grant.accountId !== entry.accountId) {
20795
+ throw new SlackInteractionPermanentError("identity_access_revoked");
20796
+ }
20797
+ if (existing?.sessionId) {
20798
+ await continueSlackSession(deps, grant, existing, entry);
20799
+ return;
20800
+ }
20801
+ if (!hasPermission13(grant.permissions, "sessions:create")) {
20802
+ throw new SlackInteractionPermanentError("sessions_create_denied");
20803
+ }
20804
+ const { interaction } = await getOrCreateSlackInteraction(deps.db, {
20805
+ accountId: entry.accountId,
20806
+ workspaceId: entry.workspaceId,
20807
+ connectionId: entry.connectionId,
20808
+ slackTeamId: entry.slackTeamId,
20809
+ slackChannelId: entry.slackChannelId,
20810
+ slackThreadTs: entry.slackThreadTs ?? entry.slackMessageTs,
20811
+ routeKey,
20812
+ triggeringProviderEventId: entry.providerEventId,
20813
+ owningSubjectId: grant.subjectId,
20814
+ visibility: entry.triggerKind === "dm" ? "private" : "workspace"
20815
+ });
20816
+ if (interaction.sessionId) {
20817
+ await continueSlackSession(deps, grant, interaction, entry);
20818
+ return;
20819
+ }
20820
+ const session = await createSessionForRequest3(deps, grant, entry.workspaceId, {
20821
+ requestedSessionId: interaction.sessionReservationId,
20822
+ initialMessage: entry.text,
20823
+ turnInstructions: SLACK_TASK_INSTRUCTIONS,
20824
+ idempotencyKey: `slack:${entry.connectionId}:${entry.providerEventId}`,
20825
+ clientEventId: `slack:${entry.providerEventId}`
20826
+ });
20827
+ const bound = await bindSlackInteractionSession(deps.db, {
20828
+ ...interaction,
20829
+ owningSubjectId: grant.subjectId,
20830
+ sessionId: session.id
20831
+ });
20832
+ if (!bound) throw new Error("Slack route could not bind its durable session");
20833
+ const ack = await client.postMessage({
20834
+ operationId: deterministicUuid(`slack-ack:${interaction.id}`),
20835
+ channelId: entry.slackChannelId,
20836
+ ...entry.triggerKind === "slash_command" ? {} : { threadTimestamp: entry.slackThreadTs ?? entry.slackMessageTs },
20837
+ text: `OpenGeni started this task. ${openSessionText(deps, entry.workspaceId, session.id)} Reply in this thread to continue, or reply \`stop\` to stop. Start a new top-level DM or invoke /opengeni again for a new session.`
20838
+ });
20839
+ if (entry.triggerKind === "slash_command") {
20840
+ await rekeySlackInteractionRoute(deps.db, {
20841
+ ...interaction,
20842
+ routeKey: slackRouteKey(entry.slackChannelId, ack.timestamp),
20843
+ slackThreadTs: ack.timestamp,
20844
+ ackSlackMessageTs: ack.timestamp
20845
+ });
20846
+ }
20847
+ }
20848
+ async function continueSlackSession(deps, grant, interaction, entry) {
20849
+ if (!interaction.sessionId || interaction.visibility === "private" && interaction.owningSubjectId !== grant.subjectId) {
20850
+ throw new SlackInteractionPermanentError("session_owner_mismatch");
20851
+ }
20852
+ await reopenSlackInteractionDelivery(deps.db, interaction);
20853
+ if (entry.text.trim().toLowerCase() === "stop") {
20854
+ if (!hasPermission13(grant.permissions, "sessions:control")) {
20855
+ throw new SlackInteractionPermanentError("sessions_control_denied");
20856
+ }
20857
+ await controlHumanSessionWorkstream3(
20858
+ deps,
20859
+ {
20860
+ accountId: grant.accountId,
20861
+ workspaceId: grant.workspaceId,
20862
+ subjectId: grant.subjectId,
20863
+ sessionId: interaction.sessionId
20864
+ },
20865
+ {
20866
+ action: "pause",
20867
+ clientEventId: deterministicUuid(`slack-stop:${entry.providerEventId}`),
20868
+ reason: "Stopped from the originating Slack thread"
20869
+ }
20870
+ );
20871
+ return;
20872
+ }
20873
+ const pending = await listSessionHumanInputRequests2(
20874
+ deps.db,
20875
+ entry.workspaceId,
20876
+ interaction.sessionId,
20877
+ { status: "pending", limit: 2 }
20878
+ );
20879
+ if (pending.length === 1) {
20880
+ const response = humanInputResponse(pending[0].questions, entry.text);
20881
+ if (response) {
20882
+ const accepted = await acceptSessionHumanInputResponse2(deps.db, {
20883
+ accountId: grant.accountId,
20884
+ workspaceId: grant.workspaceId,
20885
+ sessionId: interaction.sessionId,
20886
+ requestId: pending[0].id,
20887
+ response,
20888
+ respondedBy: grant.subjectId,
20889
+ clientEventId: `slack:${entry.providerEventId}`
20890
+ });
20891
+ if (accepted.action === "accepted") {
20892
+ await publishDurableSessionEvents3(
20893
+ deps.bus,
20894
+ grant.workspaceId,
20895
+ interaction.sessionId,
20896
+ accepted.events
20897
+ );
20898
+ if (accepted.workflowWakeRevision !== null) {
20899
+ await deps.workflowClient.signalApprovalDecision({
20900
+ accountId: grant.accountId,
20901
+ workspaceId: grant.workspaceId,
20902
+ sessionId: interaction.sessionId,
20903
+ eventId: accepted.events[0]?.id ?? pending[0].id,
20904
+ workflowId: `session-${interaction.sessionId}`,
20905
+ workflowWakeRevision: accepted.workflowWakeRevision
20906
+ });
20907
+ }
20908
+ return;
20909
+ }
20910
+ }
20911
+ }
20912
+ if (!hasPermission13(grant.permissions, "sessions:control")) {
20913
+ throw new SlackInteractionPermanentError("sessions_control_denied");
20914
+ }
20915
+ await acceptSessionUserMessage3(deps, grant, entry.workspaceId, interaction.sessionId, {
20916
+ text: entry.text,
20917
+ turnInstructions: SLACK_TASK_INSTRUCTIONS,
20918
+ clientEventId: `slack:${entry.providerEventId}`
20919
+ });
20920
+ }
20921
+ async function deliverSlackSessionEvents(deps, interaction, claimHolderId) {
20922
+ if (!interaction.sessionId) return;
20923
+ const page = await listSessionEventPage3(deps.db, interaction.workspaceId, interaction.sessionId, {
20924
+ after: interaction.lastDeliveredSessionEventSequence,
20925
+ limit: 100,
20926
+ includeTypes: [...SLACK_DELIVERY_EVENT_TYPES],
20927
+ authoritativeLatest: true,
20928
+ maxBytes: 256 * 1024
20929
+ });
20930
+ if (page.events.length === 0) {
20931
+ await releaseSlackInteractionDelivery(deps.db, {
20932
+ ...interaction,
20933
+ claimHolderId
20934
+ });
20935
+ return;
20936
+ }
20937
+ const client = await createOpenGeniSlackBotInteractionClient(deps, {
20938
+ accountId: interaction.accountId,
20939
+ workspaceId: interaction.workspaceId,
20940
+ connectionId: interaction.connectionId,
20941
+ subjectId: interaction.owningSubjectId,
20942
+ sessionId: interaction.sessionId
20943
+ });
20944
+ let lastSequence = interaction.lastDeliveredSessionEventSequence;
20945
+ let terminal = null;
20946
+ let latestAssistantText = "";
20947
+ for (const event of [...page.events].sort((left, right) => left.sequence - right.sequence)) {
20948
+ lastSequence = Math.max(lastSequence, event.sequence);
20949
+ if (event.type === "agent.message.completed") {
20950
+ latestAssistantText = safePayloadText(event.payload, "text");
20951
+ if (latestAssistantText) {
20952
+ const progress = await claimSlackInteractionProgressDelivery(deps.db, {
20953
+ accountId: interaction.accountId,
20954
+ workspaceId: interaction.workspaceId,
20955
+ interactionId: interaction.id,
20956
+ claimHolderId,
20957
+ sessionEventSequence: event.sequence,
20958
+ maxProgress: MAX_PROGRESS_MESSAGES
20959
+ });
20960
+ if (progress.kind === "not_owned") {
20961
+ throw new Error("Slack progress delivery lost its durable interaction claim");
20962
+ }
20963
+ if (progress.kind === "claimed") {
20964
+ await postDelivery(
20965
+ client,
20966
+ interaction,
20967
+ event,
20968
+ latestAssistantText,
20969
+ "progress",
20970
+ progress.delivery.operationId
20971
+ );
20972
+ }
20973
+ }
20974
+ } else if (event.type === "session.humanInput.requested") {
20975
+ const requests = await listSessionHumanInputRequests2(
20976
+ deps.db,
20977
+ interaction.workspaceId,
20978
+ interaction.sessionId,
20979
+ { status: "pending", limit: 1 }
20980
+ );
20981
+ const request = requests[0];
20982
+ if (request) {
20983
+ await postDelivery(
20984
+ client,
20985
+ interaction,
20986
+ event,
20987
+ `OpenGeni needs your input:
20988
+ ${formatQuestions(request.questions)}
20989
+ Reply in this thread, or use ${openSessionText(deps, interaction.workspaceId, interaction.sessionId)}.`,
20990
+ "human-input"
20991
+ );
20992
+ }
20993
+ } else if (event.type === "turn.completed") {
20994
+ const output = safePayloadText(event.payload, "output") || latestAssistantText;
20995
+ await postDelivery(
20996
+ client,
20997
+ interaction,
20998
+ event,
20999
+ `${output || "OpenGeni finished this task."}
21000
+
21001
+ ${openSessionText(deps, interaction.workspaceId, interaction.sessionId)} Reply in this thread to continue.`,
21002
+ "final"
21003
+ );
21004
+ terminal = "completed";
21005
+ } else if (event.type === "turn.failed") {
21006
+ await postDelivery(
21007
+ client,
21008
+ interaction,
21009
+ event,
21010
+ `OpenGeni could not complete this task. ${openSessionText(deps, interaction.workspaceId, interaction.sessionId)} for the bounded failure details.`,
21011
+ "failed"
21012
+ );
21013
+ terminal = "failed";
21014
+ } else if (event.type === "turn.cancelled") {
21015
+ await postDelivery(client, interaction, event, "OpenGeni stopped this task.", "cancelled");
21016
+ terminal = "cancelled";
21017
+ }
21018
+ }
21019
+ if (terminal) {
21020
+ await closeSlackInteractionDelivery(deps.db, {
21021
+ ...interaction,
21022
+ claimHolderId,
21023
+ sequence: lastSequence,
21024
+ state: terminal
21025
+ });
21026
+ } else {
21027
+ await advanceSlackInteractionDelivery(deps.db, {
21028
+ ...interaction,
21029
+ claimHolderId,
21030
+ sequence: lastSequence
21031
+ });
21032
+ await releaseSlackInteractionDelivery(deps.db, {
21033
+ ...interaction,
21034
+ claimHolderId
21035
+ });
21036
+ }
21037
+ }
21038
+ async function postDelivery(client, interaction, event, text, kind, operationId = deterministicUuid(`slack-delivery:${interaction.id}:${event.sequence}:${kind}`)) {
21039
+ await client.postMessage({
21040
+ operationId,
21041
+ channelId: interaction.slackChannelId,
21042
+ threadTimestamp: interaction.slackThreadTs,
21043
+ text: boundedOutput(text)
21044
+ });
21045
+ }
21046
+ async function enqueueNormalizedSlackInteraction(deps, route, entry) {
21047
+ await enqueueSlackInteractionInbox(deps.db, {
21048
+ accountId: route.accountId,
21049
+ workspaceId: route.workspaceId,
21050
+ connectionId: route.connectionId,
21051
+ ...entry
21052
+ });
21053
+ }
21054
+ async function readSignedSlackRequest(c, deps) {
21055
+ const signingSecret = deps.settings.slackSigningSecret;
21056
+ if (!signingSecret)
21057
+ throw new HTTPException31(503, {
21058
+ message: "Slack interactions are disabled"
21059
+ });
21060
+ const rawBytes = new Uint8Array(await c.req.raw.arrayBuffer());
21061
+ if (rawBytes.byteLength === 0 || rawBytes.byteLength > SLACK_INTERACTION_MAX_BODY_BYTES) {
21062
+ throw new HTTPException31(rawBytes.byteLength > SLACK_INTERACTION_MAX_BODY_BYTES ? 413 : 400, {
21063
+ message: "invalid Slack request body"
21064
+ });
21065
+ }
21066
+ const rawBody = new TextDecoder("utf-8", { fatal: true }).decode(rawBytes);
21067
+ if (!verifySlackRequestSignature(
21068
+ {
21069
+ timestamp: c.req.header("x-slack-request-timestamp") ?? null,
21070
+ signature: c.req.header("x-slack-signature") ?? null,
21071
+ rawBody
21072
+ },
21073
+ signingSecret
21074
+ )) {
21075
+ throw new HTTPException31(401, { message: "invalid Slack signature" });
21076
+ }
21077
+ return { rawBody };
21078
+ }
21079
+ function normalizedFormInteraction(form, triggerKind) {
21080
+ const teamId = boundedString(form.get("team_id"), 64);
21081
+ const userId = boundedString(form.get("user_id"), 64);
21082
+ const channelId = boundedString(form.get("channel_id"), 64);
21083
+ const triggerId = boundedString(form.get("trigger_id"), 256);
21084
+ const text = boundedText(form.get("text"));
21085
+ if (!teamId || !userId || !channelId || !triggerId || !text) {
21086
+ throw new HTTPException31(400, { message: "invalid Slack command payload" });
21087
+ }
21088
+ return {
21089
+ providerEventId: `command:${triggerId}`,
21090
+ providerMessageId: `command:${triggerId}`,
21091
+ slackTeamId: teamId,
21092
+ slackUserId: userId,
21093
+ slackChannelId: channelId,
21094
+ slackMessageTs: triggerId.slice(0, 64),
21095
+ slackThreadTs: null,
21096
+ triggerKind,
21097
+ text
21098
+ };
21099
+ }
21100
+ function humanInputResponse(questions, text) {
21101
+ if (questions.length !== 1) return null;
21102
+ const question = questions[0];
21103
+ if (question.kind === "text") {
21104
+ return {
21105
+ outcome: "answered",
21106
+ answers: [{ questionId: question.id, values: [text] }]
21107
+ };
21108
+ }
21109
+ const normalized = text.trim().toLowerCase();
21110
+ const matches = question.options.filter(
21111
+ (option) => option.id.toLowerCase() === normalized || option.label.toLowerCase() === normalized
21112
+ );
21113
+ if (matches.length !== 1) return null;
21114
+ return {
21115
+ outcome: "answered",
21116
+ answers: [{ questionId: question.id, values: [matches[0].id] }]
21117
+ };
21118
+ }
21119
+ function formatQuestions(questions) {
21120
+ return questions.slice(0, 5).map((question, index) => {
21121
+ const options = question.options.slice(0, 10).map((option) => option.label).join(", ");
21122
+ return `${index + 1}. ${boundedOutput(question.prompt)}${options ? ` (${options})` : ""}`;
21123
+ }).join("\n");
21124
+ }
21125
+ function openSessionText(deps, workspaceId, sessionId) {
21126
+ const base = deps.settings.webBaseUrl ?? deps.settings.publicBaseUrl;
21127
+ return base ? `Open in OpenGeni: ${new URL(`/workspaces/${workspaceId}/sessions/${sessionId}`, base).toString()}` : "Open this session in OpenGeni";
21128
+ }
21129
+ function linkUrl(deps, entry) {
21130
+ const base = deps.settings.webBaseUrl ?? deps.settings.publicBaseUrl;
21131
+ if (!base) return "OpenGeni Settings \u2192 Integrations \u2192 Slack";
21132
+ const url = new URL("/settings/integrations/slack", base);
21133
+ url.searchParams.set("workspaceId", entry.workspaceId);
21134
+ url.searchParams.set("connectionId", entry.connectionId);
21135
+ url.searchParams.set("teamId", entry.slackTeamId);
21136
+ url.searchParams.set("userId", entry.slackUserId);
21137
+ return url.toString();
21138
+ }
21139
+ function slackRouteKey(channelId, threadTs) {
21140
+ return `${channelId}:${threadTs}`;
21141
+ }
21142
+ function deterministicUuid(value) {
21143
+ const bytes = createHash8("sha256").update(value).digest().subarray(0, 16);
21144
+ bytes[6] = bytes[6] & 15 | 80;
21145
+ bytes[8] = bytes[8] & 63 | 128;
21146
+ const hex = bytes.toString("hex");
21147
+ return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;
21148
+ }
21149
+ function parseJsonObject(value) {
21150
+ try {
21151
+ const parsed = JSON.parse(value);
21152
+ const result = record3(parsed);
21153
+ if (result) return result;
21154
+ } catch {
21155
+ }
21156
+ throw new HTTPException31(400, { message: "invalid Slack JSON payload" });
21157
+ }
21158
+ function record3(value) {
21159
+ return value && typeof value === "object" && !Array.isArray(value) ? value : null;
21160
+ }
21161
+ function boundedString(value, max) {
21162
+ return typeof value === "string" && value.length > 0 && Buffer.byteLength(value) <= max ? value : null;
21163
+ }
21164
+ function boundedText(value) {
21165
+ if (typeof value !== "string") return null;
21166
+ const trimmed = value.trim();
21167
+ if (!trimmed) return null;
21168
+ return trimmed.slice(0, MAX_SLACK_INPUT_CHARS);
21169
+ }
21170
+ function boundedOutput(value) {
21171
+ return value.length <= MAX_SLACK_TEXT_CHARS ? value : `${value.slice(0, MAX_SLACK_TEXT_CHARS - 20)}
21172
+ \u2026 output truncated`;
21173
+ }
21174
+ function safePayloadText(payload, field) {
21175
+ const value = record3(payload)?.[field];
21176
+ return typeof value === "string" ? boundedOutput(value) : "";
21177
+ }
21178
+ function safeErrorCode(error) {
21179
+ const raw = error instanceof Error ? error.name : "slack_interaction_error";
21180
+ return raw.toLowerCase().replace(/[^a-z0-9_-]/g, "_").slice(0, 128) || "error";
21181
+ }
21182
+ var SlackInteractionPermanentError = class extends Error {
21183
+ };
21184
+ function permanentSlackInteractionError(error) {
21185
+ return error instanceof SlackInteractionPermanentError || error instanceof HTTPException31;
21186
+ }
21187
+
19948
21188
  // src/app.ts
19949
21189
  import {
19950
21190
  mergeResourceRefs,
@@ -19964,6 +21204,9 @@ function apiRequestBodyLimitBytes(settings) {
19964
21204
  }
19965
21205
  var API_PUBLIC_ERROR_MESSAGE_MAX_BYTES = 512;
19966
21206
  function createApp(deps) {
21207
+ return createAppComposition(deps).app;
21208
+ }
21209
+ function createAppComposition(deps) {
19967
21210
  const managedAuth = deps.managedAuth ?? createManagedAuth(deps.settings, deps.db);
19968
21211
  const objectStorage = deps.objectStorage === void 0 ? createObjectStorage(deps.settings) : deps.objectStorage;
19969
21212
  let documentServices = deps.documentServices ?? null;
@@ -19978,7 +21221,7 @@ function createApp(deps) {
19978
21221
  documentId
19979
21222
  }) => {
19980
21223
  if (!objectStorage) {
19981
- throw new HTTPException30(503, {
21224
+ throw new HTTPException32(503, {
19982
21225
  message: "object storage is not configured"
19983
21226
  });
19984
21227
  }
@@ -20029,28 +21272,29 @@ function createApp(deps) {
20029
21272
  c.header(OPENGENI_CORRELATION_HEADER, correlationId);
20030
21273
  await next();
20031
21274
  });
20032
- app.use(
20033
- "*",
20034
- cors({
20035
- credentials: true,
20036
- allowHeaders: [
20037
- "Accept",
20038
- "Authorization",
20039
- "Content-Type",
20040
- "X-OpenGeni-Access-Key",
20041
- "X-OpenGeni-Api-Contract",
20042
- "X-OpenGeni-Correlation-Id",
20043
- "X-OpenGeni-Subject"
20044
- ],
20045
- exposeHeaders: ["X-OpenGeni-Api-Contract", "X-OpenGeni-Correlation-Id"],
20046
- origin: (origin) => {
20047
- if (!origin) {
20048
- return null;
20049
- }
20050
- return allowedCorsOrigin(deps.settings.corsAllowOriginRegex, origin) ? origin : null;
20051
- }
20052
- })
20053
- );
21275
+ const corsHeaders = {
21276
+ allowHeaders: [
21277
+ "Accept",
21278
+ "Authorization",
21279
+ "Content-Type",
21280
+ "X-OpenGeni-Access-Key",
21281
+ "X-OpenGeni-Api-Contract",
21282
+ "X-OpenGeni-Correlation-Id",
21283
+ "X-OpenGeni-Subject"
21284
+ ],
21285
+ exposeHeaders: ["X-OpenGeni-Api-Contract", "X-OpenGeni-Correlation-Id"]
21286
+ };
21287
+ const publicApiCors = cors({ ...corsHeaders, credentials: false, origin: "*" });
21288
+ const credentialedCors = cors({
21289
+ ...corsHeaders,
21290
+ credentials: true,
21291
+ origin: (origin) => allowedCorsOrigin(deps.settings.corsAllowOriginRegex, origin) ? origin : null
21292
+ });
21293
+ app.use("*", (c, next) => {
21294
+ const origin = c.req.header("origin");
21295
+ const middleware = origin && allowedCorsOrigin(deps.settings.corsAllowOriginRegex, origin) ? credentialedCors : publicApiCors;
21296
+ return middleware(c, next);
21297
+ });
20054
21298
  app.use(
20055
21299
  "*",
20056
21300
  bodyLimit({
@@ -20219,7 +21463,9 @@ function createApp(deps) {
20219
21463
  boundedRequest = await boundedMcpRequest(c.req.raw);
20220
21464
  } catch (error) {
20221
21465
  if (error instanceof McpPayloadTooLargeError2) {
20222
- throw new HTTPException30(413, { message: "MCP request body exceeds the safety limit" });
21466
+ throw new HTTPException32(413, {
21467
+ message: "MCP request body exceeds the safety limit"
21468
+ });
20223
21469
  }
20224
21470
  throw error;
20225
21471
  }
@@ -20228,7 +21474,7 @@ function createApp(deps) {
20228
21474
  const boundSessionId = grant.metadata?.sessionId;
20229
21475
  if (toolspaceGrant || typeof boundSessionId === "string") {
20230
21476
  if (typeof boundSessionId !== "string") {
20231
- throw new HTTPException30(404, { message: "session not found" });
21477
+ throw new HTTPException32(404, { message: "session not found" });
20232
21478
  }
20233
21479
  try {
20234
21480
  await requireSessionAuthorization3(routeDeps, grant, {
@@ -20238,10 +21484,12 @@ function createApp(deps) {
20238
21484
  });
20239
21485
  } catch (error) {
20240
21486
  if (error instanceof SessionAuthorizationDeniedError2) {
20241
- throw new HTTPException30(404, { message: "session not found" });
21487
+ throw new HTTPException32(404, { message: "session not found" });
20242
21488
  }
20243
21489
  if (error instanceof SessionAuthorizationUnavailableError2) {
20244
- throw new HTTPException30(503, { message: "session authorization is unavailable" });
21490
+ throw new HTTPException32(503, {
21491
+ message: "session authorization is unavailable"
21492
+ });
20245
21493
  }
20246
21494
  throw error;
20247
21495
  }
@@ -20249,10 +21497,15 @@ function createApp(deps) {
20249
21497
  let toolspace = null;
20250
21498
  if (toolspaceGrant) {
20251
21499
  try {
20252
- toolspace = await prepareToolspaceMcpSurface({ deps: routeDeps, grant });
21500
+ toolspace = await prepareToolspaceMcpSurface({
21501
+ deps: routeDeps,
21502
+ grant
21503
+ });
20253
21504
  } catch (error) {
20254
21505
  if (error instanceof McpPayloadTooLargeError2) {
20255
- throw new HTTPException30(413, { message: "MCP tool list exceeds the safety limit" });
21506
+ throw new HTTPException32(413, {
21507
+ message: "MCP tool list exceeds the safety limit"
21508
+ });
20256
21509
  }
20257
21510
  throw error;
20258
21511
  }
@@ -20284,6 +21537,7 @@ function createApp(deps) {
20284
21537
  registerInsightsRoutes(app, routeDeps);
20285
21538
  registerWorkspaceInstructionPolicyRoutes(app, routeDeps);
20286
21539
  registerWorkspaceStateRoutes(app, routeDeps);
21540
+ registerWorkspaceArtifactRoutes(app, routeDeps);
20287
21541
  registerPreferenceRegistryRoutes(app, routeDeps);
20288
21542
  registerSocialRoutes(app, routeDeps);
20289
21543
  registerConnectionRoutes(app, routeDeps);
@@ -20298,6 +21552,7 @@ function createApp(deps) {
20298
21552
  registerScheduledTaskRoutes(app, routeDeps);
20299
21553
  registerCodexRoutes(app, routeDeps);
20300
21554
  registerTranscriptionRoutes(app, routeDeps);
21555
+ registerSlackInteractionRoutes(app, routeDeps);
20301
21556
  app.notFound((c) => {
20302
21557
  if (!new URL(c.req.url).pathname.startsWith("/v1/")) return c.text("Not Found", 404);
20303
21558
  const requestId = correlationIds.get(c.req.raw) ?? crypto.randomUUID();
@@ -20334,11 +21589,11 @@ function createApp(deps) {
20334
21589
  });
20335
21590
  return c.json(envelope, status);
20336
21591
  });
20337
- return app;
21592
+ return { app, routeDeps };
20338
21593
  }
20339
21594
  async function requireMcpAccessGrant(c, deps, workspaceId) {
20340
- const grant = await requireAccessGrant22(c, deps, workspaceId);
20341
- if (hasPermission13(grant.permissions, "workspace:read")) {
21595
+ const grant = await requireAccessGrant24(c, deps, workspaceId);
21596
+ if (hasPermission14(grant.permissions, "workspace:read")) {
20342
21597
  return grant;
20343
21598
  }
20344
21599
  if (isToolspaceGrant(deps.settings, grant)) {
@@ -20378,7 +21633,7 @@ function allowedCorsOrigin(pattern, origin) {
20378
21633
  }
20379
21634
  function codexCompactionV2ProviderLockedError(error) {
20380
21635
  if (error instanceof CodexCompactionV2ProviderLockedError) return error;
20381
- if (error instanceof HTTPException30 && error.cause instanceof CodexCompactionV2ProviderLockedError) {
21636
+ if (error instanceof HTTPException32 && error.cause instanceof CodexCompactionV2ProviderLockedError) {
20382
21637
  return error.cause;
20383
21638
  }
20384
21639
  return null;
@@ -20387,7 +21642,7 @@ function httpStatusForError(error) {
20387
21642
  if (codexCompactionV2ProviderLockedError(error)) {
20388
21643
  return 422;
20389
21644
  }
20390
- if (error instanceof HTTPException30) {
21645
+ if (error instanceof HTTPException32) {
20391
21646
  return error.status;
20392
21647
  }
20393
21648
  if (error instanceof McpPayloadTooLargeError2) {
@@ -20416,7 +21671,7 @@ function publicErrorMessage(error, status) {
20416
21671
  if (status >= 500) {
20417
21672
  return "OpenGeni could not complete the request.";
20418
21673
  }
20419
- if (error instanceof HTTPException30) {
21674
+ if (error instanceof HTTPException32) {
20420
21675
  return boundedPublicMessage(error.message) ?? "Request failed.";
20421
21676
  }
20422
21677
  if (error instanceof McpPayloadTooLargeError2) {
@@ -20882,7 +22137,7 @@ function isApiContractProtectedMutation(method, pathname) {
20882
22137
  if (!pathname.startsWith("/v1/")) {
20883
22138
  return false;
20884
22139
  }
20885
- if (pathname.startsWith("/v1/auth/") || pathname.startsWith("/v1/webhooks/") || pathname.startsWith("/v1/integrations/oauth/") || pathname.startsWith("/v1/github/") || pathname === "/v1/enrollments/device/start" || pathname === "/v1/enrollments/device/poll" || pathname === "/v1/enrollments/token/exchange") {
22140
+ if (pathname.startsWith("/v1/auth/") || pathname.startsWith("/v1/webhooks/") || pathname.startsWith("/v1/integrations/oauth/") || pathname === "/v1/integrations/slack/events" || pathname === "/v1/integrations/slack/commands" || pathname === "/v1/integrations/slack/interactions" || pathname.startsWith("/v1/github/") || pathname === "/v1/enrollments/device/start" || pathname === "/v1/enrollments/device/poll" || pathname === "/v1/enrollments/token/exchange") {
20886
22141
  return false;
20887
22142
  }
20888
22143
  return !pathname.split("/").includes("mcp");
@@ -20892,9 +22147,11 @@ export {
20892
22147
  sseSessionStream,
20893
22148
  replaySessionEvents,
20894
22149
  sseWorkspaceControlStream,
22150
+ startSlackInteractionPump,
20895
22151
  API_MAX_REQUEST_BODY_BYTES,
20896
22152
  apiRequestBodyLimitBytes,
20897
22153
  createApp,
22154
+ createAppComposition,
20898
22155
  allowedCorsOrigin,
20899
22156
  httpStatusForError,
20900
22157
  errorCodeForStatus,
@@ -20911,4 +22168,4 @@ export {
20911
22168
  withDefaultEnabledCapabilityMcpTools,
20912
22169
  workflowIdForSession2 as workflowIdForSession
20913
22170
  };
20914
- //# sourceMappingURL=chunk-36PW33ND.js.map
22171
+ //# sourceMappingURL=chunk-UVL2KH56.js.map