@opengeni/api-router 4.1.0 → 4.1.2

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.
@@ -294,6 +294,8 @@ var FIRST_PARTY_TOOL_AUTHORIZATION = {
294
294
  browser_open: { sessionRequired: true, allOf: ["sessions:control"] },
295
295
  browser_tabs: { sessionRequired: true, allOf: ["sessions:control"] },
296
296
  browser_observe: { sessionRequired: true, allOf: ["sessions:read"] },
297
+ browser_read: { sessionRequired: true, allOf: ["sessions:read"] },
298
+ browser_screenshot: { sessionRequired: true, allOf: ["sessions:read"] },
297
299
  browser_act: { sessionRequired: true, allOf: ["sessions:control"] },
298
300
  browser_clipboard: { sessionRequired: true, allOf: ["sessions:read"] },
299
301
  browser_debug: { sessionRequired: true, allOf: ["sessions:read"] },
@@ -4600,6 +4602,9 @@ var Name = class {
4600
4602
  return new SQL([this]);
4601
4603
  }
4602
4604
  };
4605
+ function isDriverValueEncoder(value) {
4606
+ return typeof value === "object" && value !== null && "mapToDriverValue" in value && typeof value.mapToDriverValue === "function";
4607
+ }
4603
4608
  var noopDecoder = {
4604
4609
  mapFromDriverValue: (value) => value
4605
4610
  };
@@ -4732,6 +4737,33 @@ Subquery.prototype.getSQL = function() {
4732
4737
  return new SQL([this]);
4733
4738
  };
4734
4739
 
4740
+ // ../../node_modules/.bun/drizzle-orm@0.45.2+1d2c014f134ec9f4/node_modules/drizzle-orm/sql/expressions/conditions.js
4741
+ function bindIfParam(value, column) {
4742
+ if (isDriverValueEncoder(column) && !isSQLWrapper(value) && !is(value, Param) && !is(value, Placeholder) && !is(value, Column) && !is(value, Table) && !is(value, View)) {
4743
+ return new Param(value, column);
4744
+ }
4745
+ return value;
4746
+ }
4747
+ var eq = (left, right) => {
4748
+ return sql`${left} = ${bindIfParam(right, left)}`;
4749
+ };
4750
+ function and(...unfilteredConditions) {
4751
+ const conditions = unfilteredConditions.filter(
4752
+ (c) => c !== void 0
4753
+ );
4754
+ if (conditions.length === 0) {
4755
+ return void 0;
4756
+ }
4757
+ if (conditions.length === 1) {
4758
+ return new SQL(conditions);
4759
+ }
4760
+ return new SQL([
4761
+ new StringChunk("("),
4762
+ sql.join(conditions, new StringChunk(" and ")),
4763
+ new StringChunk(")")
4764
+ ]);
4765
+ }
4766
+
4735
4767
  // src/auth/managed-auth.ts
4736
4768
  import { Pool } from "pg";
4737
4769
 
@@ -7598,7 +7630,14 @@ function registerKnowledgeEntryTools(server, deps, grant, sessionId) {
7598
7630
  inputSchema: KnowledgeSavePreparationRequest.shape
7599
7631
  },
7600
7632
  (input) => run(
7601
- (context) => prepareKnowledgeSave(deps.db, context, input, () => deps.getDocumentServices().embedder)
7633
+ (context) => prepareKnowledgeSave(
7634
+ deps.db,
7635
+ context,
7636
+ input,
7637
+ () => deps.getDocumentServices().embedder,
7638
+ void 0,
7639
+ deps.settings
7640
+ )
7602
7641
  )
7603
7642
  );
7604
7643
  server.registerTool(
@@ -7613,7 +7652,13 @@ function registerKnowledgeEntryTools(server, deps, grant, sessionId) {
7613
7652
  }).extend({ view: z8.enum(["published", "needs_review"]).default("published") }).shape
7614
7653
  },
7615
7654
  (input) => run(
7616
- (context) => searchKnowledgeEntries(deps.db, context, input, () => deps.getDocumentServices().embedder)
7655
+ (context) => searchKnowledgeEntries(
7656
+ deps.db,
7657
+ context,
7658
+ input,
7659
+ () => deps.getDocumentServices().embedder,
7660
+ deps.settings
7661
+ )
7617
7662
  )
7618
7663
  );
7619
7664
  server.registerTool(
@@ -7901,26 +7946,26 @@ async function listWorkspaceGitHubRepositoryBranches(deps, input, services = wor
7901
7946
  ])) {
7902
7947
  throw new GitHubRepositoryBranchAuthorityError("changed");
7903
7948
  }
7904
- const page = await services.listProviderBranches(deps, {
7949
+ const page2 = await services.listProviderBranches(deps, {
7905
7950
  installationId: input.installationId,
7906
7951
  repositoryId: input.repositoryId,
7907
7952
  page: input.query.cursor,
7908
7953
  limit: input.query.limit
7909
7954
  });
7910
- if (!page) {
7955
+ if (!page2) {
7911
7956
  throw new GitHubAppApiError(
7912
7957
  "The configured GitHub provider cannot list exact repository branches"
7913
7958
  );
7914
7959
  }
7915
- if (page.installationId !== input.installationId || page.repositoryId !== input.repositoryId || page.branches.length > input.query.limit || page.nextPage !== null && page.nextPage !== input.query.cursor + 1 || page.branches.length < input.query.limit && page.nextPage !== null) {
7960
+ if (page2.installationId !== input.installationId || page2.repositoryId !== input.repositoryId || page2.branches.length > input.query.limit || page2.nextPage !== null && page2.nextPage !== input.query.cursor + 1 || page2.branches.length < input.query.limit && page2.nextPage !== null) {
7916
7961
  throw new GitHubAppApiError("GitHub returned an invalid repository branches page");
7917
7962
  }
7918
7963
  const response = GitHubRepositoryBranchesResponseSchema.safeParse({
7919
- branches: page.branches.map((name) => ({
7964
+ branches: page2.branches.map((name) => ({
7920
7965
  name,
7921
- isDefault: name === page.defaultBranch
7966
+ isDefault: name === page2.defaultBranch
7922
7967
  })),
7923
- nextCursor: page.nextPage
7968
+ nextCursor: page2.nextPage
7924
7969
  });
7925
7970
  if (!response.success) {
7926
7971
  throw new GitHubAppApiError("GitHub returned an invalid repository branches page");
@@ -8009,6 +8054,11 @@ import {
8009
8054
  } from "@opengeni/db";
8010
8055
  import { retryWhileMissing as retryWhileMissing4 } from "@opengeni/storage";
8011
8056
  import { HTTPException as HTTPException12 } from "hono/http-exception";
8057
+ import {
8058
+ isConnectedMachineAbsolutePath,
8059
+ relativeConnectedMachinePath,
8060
+ resolveConnectedMachinePath
8061
+ } from "@opengeni/runtime/sandbox";
8012
8062
 
8013
8063
  // src/sandbox/channel-a.ts
8014
8064
  import {
@@ -8016,6 +8066,7 @@ import {
8016
8066
  hasGitCredentialRepositorySelection,
8017
8067
  hasGitHubRepositorySelection,
8018
8068
  sandboxLifecycleTransitionWaitMs,
8069
+ sandboxWarmRateMicrosPerSecond,
8019
8070
  stableSandboxEnvironmentForRun
8020
8071
  } from "@opengeni/config";
8021
8072
  import { githubAppBotIdentity } from "@opengeni/github";
@@ -8034,6 +8085,7 @@ import {
8034
8085
  SandboxImageConflictError,
8035
8086
  SandboxProviderReadLockUnavailableError,
8036
8087
  SandboxRigConflictError,
8088
+ SandboxPaidComputeAdmissionError,
8037
8089
  withSandboxProviderReadLock
8038
8090
  } from "@opengeni/db";
8039
8091
  import { appendAndPublishEvents } from "@opengeni/events";
@@ -8853,6 +8905,10 @@ async function withChannelAOperation(services, ctx, readOnly, fn) {
8853
8905
  holderId,
8854
8906
  subjectId: session.id,
8855
8907
  backend: session.sandboxBackend,
8908
+ warmBilling: {
8909
+ mode: settings.sandboxWarmBillingMode,
8910
+ rateMicrosPerSecond: sandboxWarmRateMicrosPerSecond(settings, session.sandboxBackend)
8911
+ },
8856
8912
  os: session.sandboxOs,
8857
8913
  image: sandboxRuntime.image,
8858
8914
  rigVersionId: session.rigVersionId,
@@ -9153,6 +9209,8 @@ async function withChannelAOperation(services, ctx, readOnly, fn) {
9153
9209
  }
9154
9210
  function mapChannelAError(error, waitSignal) {
9155
9211
  if (error instanceof HTTPException6) return error;
9212
+ if (error instanceof SandboxPaidComputeAdmissionError)
9213
+ return new HTTPException6(402, { message: error.message, cause: error });
9156
9214
  if (isChannelARequestCancellation(error, waitSignal))
9157
9215
  return new HTTPException6(499, {
9158
9216
  message: "request cancelled",
@@ -9335,6 +9393,7 @@ import {
9335
9393
  retainedGeneratedImageReferenceFromFile,
9336
9394
  retainedGeneratedVideoReferenceFromFile,
9337
9395
  retainedScreenshotReferenceFromFile,
9396
+ retainedSessionScreenshotKindFromObjectKey,
9338
9397
  VideoArtifactPlaybackSource,
9339
9398
  resolveRetainedOutputRange
9340
9399
  } from "@opengeni/contracts";
@@ -9631,7 +9690,13 @@ function buildDocumentsMcpServer(db, accountId, workspaceId, documentServices, o
9631
9690
  {
9632
9691
  type: "text",
9633
9692
  text: JSON.stringify(
9634
- await searchKnowledgeEntries2(db, context, input, () => documentServices.embedder)
9693
+ await searchKnowledgeEntries2(
9694
+ db,
9695
+ context,
9696
+ input,
9697
+ () => documentServices.embedder,
9698
+ options.settings
9699
+ )
9635
9700
  )
9636
9701
  }
9637
9702
  ]
@@ -9917,7 +9982,8 @@ async function prepareWorkspaceToolGatewayForGrantInternal(routeDeps, grant, all
9917
9982
  grant.workspaceId,
9918
9983
  routeDeps.getDocumentServices(),
9919
9984
  {
9920
- knowledge: await knowledgeContextForGateway(routeDeps, grant)
9985
+ knowledge: await knowledgeContextForGateway(routeDeps, grant),
9986
+ settings: gatewaySettings
9921
9987
  }
9922
9988
  )
9923
9989
  )
@@ -11718,12 +11784,15 @@ function retainedGeneratedImageMetadata(artifact) {
11718
11784
  }
11719
11785
  function retainedScreenshotMetadata(artifact) {
11720
11786
  if (artifact.status === "ready" && artifact.sessionId && artifact.retentionExpiresAt.getTime() > Date.now()) {
11787
+ const kind = retainedSessionScreenshotKindFromObjectKey(artifact.file.objectKey);
11788
+ if (!kind) return retainedArtifactUnavailable(artifact.artifactId, "invalid_content");
11721
11789
  const reference = retainedScreenshotReferenceFromFile({
11722
11790
  ...artifact.file,
11723
11791
  sessionId: artifact.sessionId,
11724
11792
  width: artifact.width,
11725
11793
  height: artifact.height,
11726
- expiresAt: artifact.retentionExpiresAt.toISOString()
11794
+ expiresAt: artifact.retentionExpiresAt.toISOString(),
11795
+ kind
11727
11796
  });
11728
11797
  if (reference) return reference;
11729
11798
  }
@@ -11798,13 +11867,12 @@ async function publishSandboxFileArtifactInScope(deps, input) {
11798
11867
  message: "object storage cannot verify immutable artifact files"
11799
11868
  });
11800
11869
  }
11801
- const path = sandboxArtifactRelativePath(input.path);
11802
11870
  const maxArtifactBytes = Math.min(SANDBOX_FILE_ARTIFACT_MAX_BYTES, storage.maxSinglePutSizeBytes);
11803
11871
  if (maxArtifactBytes < 1) {
11804
11872
  throw new HTTPException12(503, { message: "object storage cannot accept artifact files" });
11805
11873
  }
11806
11874
  const readLimit = Math.min(25 * 1024 * 1024, maxArtifactBytes + FILE_READ_SENTINEL_BYTES);
11807
- const read = await withChannelARead(
11875
+ const { path, sandboxPath, read } = await withChannelARead(
11808
11876
  {
11809
11877
  db: deps.db,
11810
11878
  settings: deps.settings,
@@ -11819,7 +11887,7 @@ async function publishSandboxFileArtifactInScope(deps, input) {
11819
11887
  ...input.signal ? { waitSignal: input.signal } : {},
11820
11888
  operation: "artifact.publish"
11821
11889
  },
11822
- ({ service }) => service.fsRead({ path, encoding: "base64", maxBytes: readLimit })
11890
+ ({ service }) => readSandboxArtifactFile(service, input.path, readLimit)
11823
11891
  );
11824
11892
  if (read.truncated || read.sizeBytes > maxArtifactBytes) {
11825
11893
  throw new HTTPException12(413, {
@@ -11929,42 +11997,46 @@ async function publishSandboxFileArtifactInScope(deps, input) {
11929
11997
  });
11930
11998
  return SandboxFileArtifactReceipt.parse({
11931
11999
  type: "sandbox_file",
11932
- sandboxPath: `/workspace/${path}`,
12000
+ sandboxPath,
11933
12001
  filename,
11934
12002
  artifact
11935
12003
  });
11936
12004
  }
11937
- function sandboxArtifactRelativePath(value) {
12005
+ async function readSandboxArtifactFile(service, value, maxBytes) {
12006
+ const root2 = service.capabilities().FileSystem.root;
12007
+ const path = sandboxArtifactRelativePath(value, root2);
12008
+ const sandboxPath = resolveConnectedMachinePath(root2, path);
12009
+ const read = await service.fsRead({ path, encoding: "base64", maxBytes });
12010
+ return { path, sandboxPath, read };
12011
+ }
12012
+ function sandboxArtifactRelativePath(value, workspaceRoot = "/workspace") {
11938
12013
  let path = value.trim();
11939
12014
  if (path.startsWith("sandbox:")) {
11940
12015
  path = path.slice("sandbox:".length);
11941
12016
  }
11942
- if (path === "/workspace" || path === "/workspace/") {
12017
+ if (!path || /[\\/]$/.test(path)) {
11943
12018
  throw new HTTPException12(400, { message: "sandbox artifact path must name a file" });
11944
12019
  }
11945
- if (path.startsWith("/workspace/")) {
11946
- path = path.slice("/workspace/".length);
11947
- } else if (path.startsWith("/")) {
12020
+ let absolute;
12021
+ let relative;
12022
+ try {
12023
+ if (!isConnectedMachineAbsolutePath(workspaceRoot)) throw new Error("Missing workspace root");
12024
+ absolute = resolveConnectedMachinePath(workspaceRoot, path);
12025
+ relative = relativeConnectedMachinePath(workspaceRoot, absolute);
12026
+ } catch {
11948
12027
  throw new HTTPException12(400, {
11949
- message: "sandbox artifact path must be inside /workspace"
12028
+ message: "sandbox artifact path is invalid for this workspace"
11950
12029
  });
11951
12030
  }
11952
- if (!path) {
11953
- throw new HTTPException12(400, { message: "sandbox artifact path must name a file" });
11954
- }
11955
- if (path.includes("\0")) {
11956
- throw new HTTPException12(400, { message: "sandbox artifact path is invalid" });
11957
- }
11958
- const normalized = posix.normalize(path);
11959
- if (normalized === "." || normalized === ".." || normalized.startsWith("../") || normalized.endsWith("/")) {
12031
+ if (!relative) {
11960
12032
  throw new HTTPException12(400, {
11961
- message: "sandbox artifact path must canonically name a file inside /workspace"
12033
+ message: "sandbox artifact path must name a file inside the active workspace root"
11962
12034
  });
11963
12035
  }
11964
- if (`/workspace/${normalized}`.length > SANDBOX_ARTIFACT_ABSOLUTE_PATH_MAX_CHARS) {
12036
+ if (absolute.length > SANDBOX_ARTIFACT_ABSOLUTE_PATH_MAX_CHARS) {
11965
12037
  throw new HTTPException12(400, { message: "sandbox artifact path is too long" });
11966
12038
  }
11967
- return normalized;
12039
+ return relative;
11968
12040
  }
11969
12041
  function sandboxArtifactSafeFilename(filename) {
11970
12042
  return sanitizeFilename(filename).slice(0, SANDBOX_ARTIFACT_SAFE_FILENAME_MAX_CHARS);
@@ -15624,16 +15696,16 @@ function capEventPayload(event, perFieldChars) {
15624
15696
  function prettyJsonBytes(value) {
15625
15697
  return Buffer.byteLength(JSON.stringify(value, null, 2), "utf8");
15626
15698
  }
15627
- function setMeasuredBytes(page) {
15628
- let measured = page.bytes;
15699
+ function setMeasuredBytes(page2) {
15700
+ let measured = page2.bytes;
15629
15701
  for (let attempt = 0; attempt < 8; attempt += 1) {
15630
- page.bytes = measured;
15631
- const next = prettyJsonBytes(page);
15702
+ page2.bytes = measured;
15703
+ const next = prettyJsonBytes(page2);
15632
15704
  if (next === measured) return next;
15633
15705
  measured = next;
15634
15706
  }
15635
- page.bytes = measured;
15636
- return prettyJsonBytes(page);
15707
+ page2.bytes = measured;
15708
+ return prettyJsonBytes(page2);
15637
15709
  }
15638
15710
  function boundSessionEventMcpPage(input) {
15639
15711
  const maxBytes = Math.max(8 * 1024, input.maxBytes ?? SESSION_EVENT_MCP_MAX_BYTES);
@@ -15655,7 +15727,7 @@ function boundSessionEventMcpPage(input) {
15655
15727
  if (modelRowsDropped) reasons.push("model_bytes");
15656
15728
  const nextAfter = input.direction === "after" ? last ?? input.after : null;
15657
15729
  const nextBefore = input.direction === "before" ? first ?? input.before : null;
15658
- const page2 = {
15730
+ const page3 = {
15659
15731
  mode: input.mode,
15660
15732
  payloadMode: input.payloadMode,
15661
15733
  direction: input.direction,
@@ -15675,20 +15747,20 @@ function boundSessionEventMcpPage(input) {
15675
15747
  bytes: 0,
15676
15748
  maxBytes
15677
15749
  };
15678
- setMeasuredBytes(page2);
15679
- return page2;
15750
+ setMeasuredBytes(page3);
15751
+ return page3;
15680
15752
  };
15681
- let page = build();
15682
- while (page.bytes > maxBytes && events.length > 0) {
15753
+ let page2 = build();
15754
+ while (page2.bytes > maxBytes && events.length > 0) {
15683
15755
  if (input.direction === "before") events.shift();
15684
15756
  else events.pop();
15685
15757
  modelRowsDropped = true;
15686
- page = build();
15758
+ page2 = build();
15687
15759
  }
15688
- if (page.bytes > maxBytes) {
15760
+ if (page2.bytes > maxBytes) {
15689
15761
  throw new RangeError(`Session-event MCP metadata exceeds its ${maxBytes}-byte envelope`);
15690
15762
  }
15691
- return page;
15763
+ return page2;
15692
15764
  }
15693
15765
  function modelStringProjection(value, maxBytes) {
15694
15766
  const originalBytes = Buffer.byteLength(value, "utf8");
@@ -16145,9 +16217,9 @@ function boundRigDetailMcp(rig, versionsPage, changesPage, maxBytes = RIG_DETAIL
16145
16217
  }
16146
16218
 
16147
16219
  // src/mcp/child-read-evidence.ts
16148
- function completeChildReadSequences(page) {
16149
- if (!page.sourceExact || !["conversation", "results"].includes(page.view)) return [];
16150
- return page.events.filter((event) => {
16220
+ function completeChildReadSequences(page2) {
16221
+ if (!page2.sourceExact || !["conversation", "results"].includes(page2.view)) return [];
16222
+ return page2.events.filter((event) => {
16151
16223
  if (event.sourceOmitted) return false;
16152
16224
  if (event.fragment === void 0) return true;
16153
16225
  if (!event.fragment || typeof event.fragment !== "object") return false;
@@ -17042,7 +17114,7 @@ function boundScheduledTaskMcpPage(input) {
17042
17114
  const rowsDroppedForBytes = droppedTasks.length > 0;
17043
17115
  const hasMore = input.sourceHasMore || rowsDroppedForBytes;
17044
17116
  const nextOffset = hasMore && input.tasks.length > 0 ? input.offset + input.tasks.length : null;
17045
- const page2 = {
17117
+ const page3 = {
17046
17118
  tasks: summaries,
17047
17119
  page: {
17048
17120
  limit: input.limit,
@@ -17070,20 +17142,20 @@ function boundScheduledTaskMcpPage(input) {
17070
17142
  }
17071
17143
  };
17072
17144
  settleMeasuredBytes(
17073
- page2,
17074
- () => page2.projection.bytes,
17145
+ page3,
17146
+ () => page3.projection.bytes,
17075
17147
  (bytes) => {
17076
- page2.projection.bytes = bytes;
17148
+ page3.projection.bytes = bytes;
17077
17149
  }
17078
17150
  );
17079
- return page2;
17151
+ return page3;
17080
17152
  };
17081
- let page = build();
17082
- while (page.projection.bytes > maxBytes && summaries.length > 0) {
17153
+ let page2 = build();
17154
+ while (page2.projection.bytes > maxBytes && summaries.length > 0) {
17083
17155
  summaries = summaries.slice(0, -1);
17084
- page = build();
17156
+ page2 = build();
17085
17157
  }
17086
- return page;
17158
+ return page2;
17087
17159
  }
17088
17160
 
17089
17161
  // src/sandbox/viewer.ts
@@ -17094,6 +17166,7 @@ import {
17094
17166
  hasGitHubRepositorySelection as hasGitHubRepositorySelection2,
17095
17167
  resolveStreamTokenSecret,
17096
17168
  sandboxLifecycleTransitionWaitMs as sandboxLifecycleTransitionWaitMs2,
17169
+ sandboxWarmRateMicrosPerSecond as sandboxWarmRateMicrosPerSecond2,
17097
17170
  stableSandboxEnvironmentForRun as stableSandboxEnvironmentForRun2
17098
17171
  } from "@opengeni/config";
17099
17172
  import { githubAppBotIdentity as githubAppBotIdentity2 } from "@opengeni/github";
@@ -17103,7 +17176,7 @@ import {
17103
17176
  getSandbox as getSandbox2,
17104
17177
  getScheduledScopedRigVersionMetadata as getScheduledScopedRigVersionMetadata2,
17105
17178
  getSandboxSessionEnvelope as getSandboxSessionEnvelope2,
17106
- heartbeatLeaseHolder,
17179
+ heartbeatLeaseHolderStatus,
17107
17180
  loadWorkspaceEnvironmentForRun as loadWorkspaceEnvironmentForRun2,
17108
17181
  markSandboxProviderReady,
17109
17182
  markWarmLeaseInstanceLost as markWarmLeaseInstanceLost2,
@@ -17113,6 +17186,7 @@ import {
17113
17186
  releaseLeaseHolder as releaseLeaseHolder2,
17114
17187
  SandboxLeaseSupersededError as SandboxLeaseSupersededError2,
17115
17188
  SandboxViewerAdmissionBlockedError,
17189
+ SandboxPaidComputeAdmissionError as SandboxPaidComputeAdmissionError2,
17116
17190
  getSessionAuthorityEpoch,
17117
17191
  namedSubjectHasLiveWorkspaceAuthority
17118
17192
  } from "@opengeni/db";
@@ -17242,6 +17316,10 @@ async function attachViewer(services, input) {
17242
17316
  ...attachSubjectId ? { viewerSubjectId: attachSubjectId } : {},
17243
17317
  ...attachAuthorityEpoch !== null ? { viewerAuthorityEpoch: attachAuthorityEpoch } : {},
17244
17318
  backend: session.sandboxBackend,
17319
+ warmBilling: {
17320
+ mode: settings.sandboxWarmBillingMode,
17321
+ rateMicrosPerSecond: sandboxWarmRateMicrosPerSecond2(settings, session.sandboxBackend)
17322
+ },
17245
17323
  os: session.sandboxOs,
17246
17324
  image: sandboxRuntime.image,
17247
17325
  rigVersionId: session.rigVersionId,
@@ -17251,6 +17329,9 @@ async function attachViewer(services, input) {
17251
17329
  ...input.waitSignal ? { waitSignal: input.waitSignal } : {}
17252
17330
  });
17253
17331
  } catch (error) {
17332
+ if (error instanceof SandboxPaidComputeAdmissionError2) {
17333
+ throw new HTTPException19(402, { message: error.message, cause: error });
17334
+ }
17254
17335
  if (error instanceof SandboxViewerAdmissionBlockedError) {
17255
17336
  throw new HTTPException19(error.reason === "balance" ? 402 : 429, {
17256
17337
  message: error.reason === "balance" ? "insufficient OpenGeni credits for an idle sandbox viewer" : "workspace sandbox warm allowance exhausted",
@@ -17437,16 +17518,17 @@ async function ensureSessionGroupReady(services, input) {
17437
17518
  throw lastError ?? new Error("sandbox readiness could not be established");
17438
17519
  }
17439
17520
  async function heartbeatViewer(services, input) {
17440
- const alive = await heartbeatLeaseHolder(services.db, {
17521
+ const status = await heartbeatLeaseHolderStatus(services.db, {
17441
17522
  accountId: input.accountId,
17442
17523
  workspaceId: input.workspaceId,
17443
17524
  sandboxGroupId: input.sandboxGroupId,
17444
17525
  kind: "viewer",
17445
17526
  holderId: input.viewerId,
17446
17527
  leaseTtlMs: services.settings.sandboxLeaseTtlMs,
17447
- expectedEpoch: input.expectedEpoch
17528
+ expectedEpoch: input.expectedEpoch,
17529
+ billingMode: services.settings.sandboxWarmBillingMode
17448
17530
  });
17449
- if (!alive) return false;
17531
+ if (!status.leaseExtended) return status.holderAlive && status.fence === "funding";
17450
17532
  const lease = await readLease2(services.db, input.workspaceId, input.sandboxGroupId);
17451
17533
  if (lease?.liveness === "warm" && lease.leaseEpoch === input.expectedEpoch && lease.instanceId) {
17452
17534
  await renewSandboxProviderExpiration({
@@ -18326,7 +18408,7 @@ var OpenGeniSlackBotClient = class {
18326
18408
  let reactedMessage = null;
18327
18409
  const checkpointCreatedAtMs = restored?.state.createdAtMs ?? Date.now();
18328
18410
  const firstPage = restored?.state.pageCount ?? 0;
18329
- for (let page = firstPage; page < MAX_REACTION_CONTEXT_PAGES; page += 1) {
18411
+ for (let page2 = firstPage; page2 < MAX_REACTION_CONTEXT_PAGES; page2 += 1) {
18330
18412
  const payload = await this.call(headers, "conversations.replies", {
18331
18413
  channel: input.channelId,
18332
18414
  // Slack accepts either the parent timestamp or a message timestamp from
@@ -18350,7 +18432,7 @@ var OpenGeniSlackBotClient = class {
18350
18432
  throw new SlackBotProviderError("reaction_pagination_invalid");
18351
18433
  }
18352
18434
  seenCursors.add(nextCursor);
18353
- const pageCount = page + 1;
18435
+ const pageCount = page2 + 1;
18354
18436
  if (pageCount >= MAX_REACTION_CONTEXT_PAGES) {
18355
18437
  throw new SlackBotProviderError("reaction_pagination_exhausted");
18356
18438
  }
@@ -18576,14 +18658,14 @@ var OpenGeniSlackBotClient = class {
18576
18658
  if (offset > content.length) {
18577
18659
  throw new SlackBotProviderError("invalid_file_offset");
18578
18660
  }
18579
- const page = content.slice(offset, offset + SLACK_FILE_CONTENT_PAGE_CHARS);
18580
- const nextOffset = offset + page.length < content.length ? offset + page.length : null;
18661
+ const page2 = content.slice(offset, offset + SLACK_FILE_CONTENT_PAGE_CHARS);
18662
+ const nextOffset = offset + page2.length < content.length ? offset + page2.length : null;
18581
18663
  return {
18582
18664
  channel: info,
18583
18665
  file,
18584
18666
  contentType,
18585
18667
  offset,
18586
- content: page,
18668
+ content: page2,
18587
18669
  nextOffset,
18588
18670
  truncated: nextOffset !== null
18589
18671
  };
@@ -18985,7 +19067,7 @@ var OpenGeniSlackBotClient = class {
18985
19067
  let cursor = null;
18986
19068
  let matchedTimestamp = null;
18987
19069
  let exhausted = false;
18988
- for (let page = 0; page < MAX_SLACK_POST_RECONCILIATION_PAGES; page += 1) {
19070
+ for (let page2 = 0; page2 < MAX_SLACK_POST_RECONCILIATION_PAGES; page2 += 1) {
18989
19071
  const payload = await this.call(headers, method, {
18990
19072
  channel: input.channelId,
18991
19073
  limit: String(input.threadTimestamp ? MAX_THREAD_PAGE : MAX_HISTORY_PAGE),
@@ -20092,13 +20174,13 @@ function nextSlackFilesListPage(payload, requested, returnedFileCount) {
20092
20174
  }
20093
20175
  const count = paging?.count;
20094
20176
  const total = paging?.total;
20095
- const page = paging?.page;
20177
+ const page2 = paging?.page;
20096
20178
  const pages = paging?.pages;
20097
- if (!positiveSafeInt(count) || !nonNegativeSafeInt(total) || !positiveSafeInt(page) || !nonNegativeSafeInt(pages) || count !== requested.count || page !== requested.page || returnedFileCount > count || pages !== (total === 0 ? 0 : Math.ceil(total / count)) || page > Math.max(1, pages)) {
20179
+ if (!positiveSafeInt(count) || !nonNegativeSafeInt(total) || !positiveSafeInt(page2) || !nonNegativeSafeInt(pages) || count !== requested.count || page2 !== requested.page || returnedFileCount > count || pages !== (total === 0 ? 0 : Math.ceil(total / count)) || page2 > Math.max(1, pages)) {
20098
20180
  throw new SlackBotProviderError("invalid_files_paging");
20099
20181
  }
20100
- if (page >= pages) return null;
20101
- const nextPage = page + 1;
20182
+ if (page2 >= pages) return null;
20183
+ const nextPage = page2 + 1;
20102
20184
  if (!positiveSafeInt(nextPage)) {
20103
20185
  throw new SlackBotProviderError("invalid_files_paging");
20104
20186
  }
@@ -20459,14 +20541,14 @@ var FikenClient = class {
20459
20541
  resolveCredential;
20460
20542
  expectedConnectionVersion;
20461
20543
  async listCompanies(input = {}) {
20462
- const { payload, page } = await this.request("companies.list", "GET", "/companies", {
20544
+ const { payload, page: page2 } = await this.request("companies.list", "GET", "/companies", {
20463
20545
  query: pageQuery(input)
20464
20546
  });
20465
- return { companies: projectArray(payload), page };
20547
+ return { companies: projectArray(payload), page: page2 };
20466
20548
  }
20467
20549
  async listContacts(input) {
20468
20550
  const slug = this.companySlugFor(input.companySlug);
20469
- const { payload, page } = await this.request(
20551
+ const { payload, page: page2 } = await this.request(
20470
20552
  "contacts.list",
20471
20553
  "GET",
20472
20554
  `/companies/${slug}/contacts`,
@@ -20482,7 +20564,7 @@ var FikenClient = class {
20482
20564
  }
20483
20565
  }
20484
20566
  );
20485
- return { companySlug: slug, contacts: projectArray(payload), page };
20567
+ return { companySlug: slug, contacts: projectArray(payload), page: page2 };
20486
20568
  }
20487
20569
  async createContact(input) {
20488
20570
  const slug = this.companySlugFor(input.companySlug);
@@ -20508,7 +20590,7 @@ var FikenClient = class {
20508
20590
  }
20509
20591
  async listProducts(input) {
20510
20592
  const slug = this.companySlugFor(input.companySlug);
20511
- const { payload, page } = await this.request(
20593
+ const { payload, page: page2 } = await this.request(
20512
20594
  "products.list",
20513
20595
  "GET",
20514
20596
  `/companies/${slug}/products`,
@@ -20520,11 +20602,11 @@ var FikenClient = class {
20520
20602
  }
20521
20603
  }
20522
20604
  );
20523
- return { companySlug: slug, products: projectArray(payload), page };
20605
+ return { companySlug: slug, products: projectArray(payload), page: page2 };
20524
20606
  }
20525
20607
  async listInvoices(input) {
20526
20608
  const slug = this.companySlugFor(input.companySlug);
20527
- const { payload, page } = await this.request(
20609
+ const { payload, page: page2 } = await this.request(
20528
20610
  "invoices.list",
20529
20611
  "GET",
20530
20612
  `/companies/${slug}/invoices`,
@@ -20539,7 +20621,7 @@ var FikenClient = class {
20539
20621
  }
20540
20622
  }
20541
20623
  );
20542
- return { companySlug: slug, invoices: projectArray(payload), page };
20624
+ return { companySlug: slug, invoices: projectArray(payload), page: page2 };
20543
20625
  }
20544
20626
  async getInvoice(input) {
20545
20627
  const slug = this.companySlugFor(input.companySlug);
@@ -20623,7 +20705,7 @@ var FikenClient = class {
20623
20705
  }
20624
20706
  async listBankAccounts(input) {
20625
20707
  const slug = this.companySlugFor(input.companySlug);
20626
- const { payload, page } = await this.request(
20708
+ const { payload, page: page2 } = await this.request(
20627
20709
  "bank_accounts.list",
20628
20710
  "GET",
20629
20711
  `/companies/${slug}/bankAccounts`,
@@ -20634,11 +20716,11 @@ var FikenClient = class {
20634
20716
  }
20635
20717
  }
20636
20718
  );
20637
- return { companySlug: slug, bankAccounts: projectArray(payload), page };
20719
+ return { companySlug: slug, bankAccounts: projectArray(payload), page: page2 };
20638
20720
  }
20639
20721
  async listPurchases(input) {
20640
20722
  const slug = this.companySlugFor(input.companySlug);
20641
- const { payload, page } = await this.request(
20723
+ const { payload, page: page2 } = await this.request(
20642
20724
  "purchases.list",
20643
20725
  "GET",
20644
20726
  `/companies/${slug}/purchases`,
@@ -20651,11 +20733,11 @@ var FikenClient = class {
20651
20733
  }
20652
20734
  }
20653
20735
  );
20654
- return { companySlug: slug, purchases: projectArray(payload), page };
20736
+ return { companySlug: slug, purchases: projectArray(payload), page: page2 };
20655
20737
  }
20656
20738
  async listSales(input) {
20657
20739
  const slug = this.companySlugFor(input.companySlug);
20658
- const { payload, page } = await this.request("sales.list", "GET", `/companies/${slug}/sales`, {
20740
+ const { payload, page: page2 } = await this.request("sales.list", "GET", `/companies/${slug}/sales`, {
20659
20741
  query: {
20660
20742
  ...pageQuery(input),
20661
20743
  ...input.dateGe ? { dateGe: input.dateGe } : {},
@@ -20663,7 +20745,7 @@ var FikenClient = class {
20663
20745
  ...input.settled !== void 0 ? { settled: String(input.settled) } : {}
20664
20746
  }
20665
20747
  });
20666
- return { companySlug: slug, sales: projectArray(payload), page };
20748
+ return { companySlug: slug, sales: projectArray(payload), page: page2 };
20667
20749
  }
20668
20750
  /**
20669
20751
  * The company scope for a call: an explicit slug wins, then the connection's
@@ -20865,11 +20947,11 @@ function boundedInt2(value, max, fallback) {
20865
20947
  return Math.min(value, max);
20866
20948
  }
20867
20949
  function pageFromHeaders(headers) {
20868
- const page = headerInt(headers, "fiken-api-page");
20950
+ const page2 = headerInt(headers, "fiken-api-page");
20869
20951
  const pageSize = headerInt(headers, "fiken-api-page-size");
20870
- if (page === null || pageSize === null) return null;
20952
+ if (page2 === null || pageSize === null) return null;
20871
20953
  return {
20872
- page,
20954
+ page: page2,
20873
20955
  pageSize,
20874
20956
  pageCount: headerInt(headers, "fiken-api-page-count"),
20875
20957
  resultCount: headerInt(headers, "fiken-api-result-count")
@@ -21828,24 +21910,24 @@ async function getAtlassianLiveItem(deps, input) {
21828
21910
  );
21829
21911
  url.searchParams.set("body-format", "storage");
21830
21912
  try {
21831
- const page = objectRecord(
21913
+ const page2 = objectRecord(
21832
21914
  await atlassianApiRequest(deps, { ...input, url, label: "Confluence live page" })
21833
21915
  );
21834
21916
  const source = sources.find(
21835
- (candidate) => candidate.resourceId === optionalString(page.spaceId)
21917
+ (candidate) => candidate.resourceId === optionalString(page2.spaceId)
21836
21918
  );
21837
21919
  if (!source) continue;
21838
21920
  const comments = await readConfluenceLiveComments(deps, input, cloudId);
21839
- const webUi = optionalString(objectRecord(page._links).webui);
21921
+ const webUi = optionalString(objectRecord(page2._links).webui);
21840
21922
  return {
21841
21923
  kind: "confluence_page",
21842
- id: requiredString2(page.id, "page.id"),
21843
- title: requiredString2(page.title, "page.title"),
21924
+ id: requiredString2(page2.id, "page.id"),
21925
+ title: requiredString2(page2.title, "page.title"),
21844
21926
  source: { id: source.id, name: source.name, siteName: source.siteName },
21845
- status: optionalString(page.status),
21846
- createdAt: optionalString(page.createdAt),
21847
- updatedAt: optionalString(objectRecord(page.version).createdAt),
21848
- content: optionalString(objectRecord(objectRecord(page.body).storage).value) ?? "",
21927
+ status: optionalString(page2.status),
21928
+ createdAt: optionalString(page2.createdAt),
21929
+ updatedAt: optionalString(objectRecord(page2.version).createdAt),
21930
+ content: optionalString(objectRecord(objectRecord(page2.body).storage).value) ?? "",
21849
21931
  comments,
21850
21932
  url: webUi ? new URL(webUi.startsWith("/wiki/") ? webUi : `/wiki${webUi}`, source.siteUrl).toString() : source.siteUrl
21851
21933
  };
@@ -22359,7 +22441,7 @@ async function materializeSchedules(deps, input) {
22359
22441
  async function browseJiraProjects(deps, input, site) {
22360
22442
  const items = [];
22361
22443
  let startAt = 0;
22362
- for (let page = 0; page < 20; page += 1) {
22444
+ for (let page2 = 0; page2 < 20; page2 += 1) {
22363
22445
  const url = new URL(
22364
22446
  `https://api.atlassian.com/ex/jira/${encodeURIComponent(site.cloudId)}/rest/api/3/project/search`
22365
22447
  );
@@ -22402,7 +22484,7 @@ async function browseConfluenceSpaces(deps, input, site) {
22402
22484
  let url = new URL(
22403
22485
  `https://api.atlassian.com/ex/confluence/${encodeURIComponent(site.cloudId)}/wiki/api/v2/spaces?limit=100`
22404
22486
  );
22405
- for (let page = 0; page < 20; page += 1) {
22487
+ for (let page2 = 0; page2 < 20; page2 += 1) {
22406
22488
  const payload = objectRecord(
22407
22489
  await atlassianApiRequest(deps, { ...input, url, label: "Confluence spaces" })
22408
22490
  );
@@ -22802,7 +22884,7 @@ function cqlString(value) {
22802
22884
  async function readJiraLiveComments(deps, input, cloudId) {
22803
22885
  const comments = [];
22804
22886
  let startAt = 0;
22805
- for (let page = 0; page < 20; page += 1) {
22887
+ for (let page2 = 0; page2 < 20; page2 += 1) {
22806
22888
  const url = new URL(
22807
22889
  `https://api.atlassian.com/ex/jira/${encodeURIComponent(cloudId)}/rest/api/3/issue/${encodeURIComponent(input.id)}/comment`
22808
22890
  );
@@ -22830,7 +22912,7 @@ async function readJiraLiveComments(deps, input, cloudId) {
22830
22912
  async function readConfluenceLiveComments(deps, input, cloudId) {
22831
22913
  const comments = [];
22832
22914
  let cursor = null;
22833
- for (let page = 0; page < 20; page += 1) {
22915
+ for (let page2 = 0; page2 < 20; page2 += 1) {
22834
22916
  const url = new URL(
22835
22917
  `https://api.atlassian.com/ex/confluence/${encodeURIComponent(cloudId)}/wiki/api/v2/pages/${encodeURIComponent(input.id)}/footer-comments`
22836
22918
  );
@@ -27285,9 +27367,9 @@ function registerFikenTools(server, deps, grant, sessionId, json) {
27285
27367
  ...pageInputs
27286
27368
  }
27287
27369
  },
27288
- async ({ connectionId, page, pageSize }) => json(
27370
+ async ({ connectionId, page: page2, pageSize }) => json(
27289
27371
  await (await clientFor(connectionId)).listCompanies({
27290
- ...page !== void 0 ? { page } : {},
27372
+ ...page2 !== void 0 ? { page: page2 } : {},
27291
27373
  ...pageSize !== void 0 ? { pageSize } : {}
27292
27374
  })
27293
27375
  )
@@ -29223,7 +29305,7 @@ function registerWorkspaceOrchestrationTools(server, deps, grant, can, callerSes
29223
29305
  if (decodedCursor && decodedCursor.updatedAfter !== (normalizedUpdatedAfter ?? null)) {
29224
29306
  throw new Error("sessions_list cursor does not match updatedAfter");
29225
29307
  }
29226
- const page = await listSessionDiscoverySummaries(deps.db, grant.workspaceId, {
29308
+ const page2 = await listSessionDiscoverySummaries(deps.db, grant.workspaceId, {
29227
29309
  limit: boundedSessionDiscoveryLimit(limit),
29228
29310
  ...decodedCursor ? { cursor: decodedCursor } : {},
29229
29311
  includeLastMessage: includeLastMessage === true,
@@ -29243,7 +29325,7 @@ function registerWorkspaceOrchestrationTools(server, deps, grant, can, callerSes
29243
29325
  subjectId: grant.subjectId,
29244
29326
  ...authorizationScope ? { authorizationScope } : {}
29245
29327
  });
29246
- const result = detail === "full" ? capSessionDiscoveryPage(page, includeLastMessage === true) : capSessionDiscoveryCompactPage(page, {
29328
+ const result = detail === "full" ? capSessionDiscoveryPage(page2, includeLastMessage === true) : capSessionDiscoveryCompactPage(page2, {
29247
29329
  includeLastMessage: includeLastMessage === true,
29248
29330
  includeRelatedWork: relatedWorkRequested
29249
29331
  });
@@ -29409,8 +29491,8 @@ function registerWorkspaceOrchestrationTools(server, deps, grant, can, callerSes
29409
29491
  }
29410
29492
  if (view !== "debug" && !auditRequested) {
29411
29493
  const { readSessionEventView } = await import("./session-event-view-MU3HMNV3.js");
29412
- const { listSessionEventSlices } = await import("@opengeni/db/session-event-slices");
29413
- const page2 = await readSessionEventView(
29494
+ const { listSessionEventSlices: listSessionEventSlices2 } = await import("@opengeni/db/session-event-slices");
29495
+ const page3 = await readSessionEventView(
29414
29496
  {
29415
29497
  sessionId,
29416
29498
  view,
@@ -29423,7 +29505,7 @@ function registerWorkspaceOrchestrationTools(server, deps, grant, can, callerSes
29423
29505
  direction: requestedDirection,
29424
29506
  limit
29425
29507
  },
29426
- (options) => listSessionEventSlices(
29508
+ (options) => listSessionEventSlices2(
29427
29509
  deps.db,
29428
29510
  grant.workspaceId,
29429
29511
  sessionId,
@@ -29431,8 +29513,8 @@ function registerWorkspaceOrchestrationTools(server, deps, grant, can, callerSes
29431
29513
  (legacyOptions) => listSessionEventPage(deps.db, grant.workspaceId, sessionId, legacyOptions)
29432
29514
  )
29433
29515
  );
29434
- await acknowledgeReads([{ sessionId, sequences: completeChildReadSequences(page2) }]);
29435
- return json(page2);
29516
+ await acknowledgeReads([{ sessionId, sequences: completeChildReadSequences(page3) }]);
29517
+ return json(page3);
29436
29518
  }
29437
29519
  if (cursor !== void 0 || callId !== void 0 || includeArguments !== void 0 || includeOutput !== void 0) {
29438
29520
  throw new Error("cursor/callId/includeArguments/includeOutput require a non-debug view");
@@ -29477,7 +29559,7 @@ function registerWorkspaceOrchestrationTools(server, deps, grant, can, callerSes
29477
29559
  }
29478
29560
  return json(result);
29479
29561
  }
29480
- const page = boundSessionEventMcpPage({
29562
+ const page2 = boundSessionEventMcpPage({
29481
29563
  events: dbPage.events,
29482
29564
  mode,
29483
29565
  payloadMode,
@@ -29487,12 +29569,12 @@ function registerWorkspaceOrchestrationTools(server, deps, grant, can, callerSes
29487
29569
  after: after ?? 0,
29488
29570
  before: before ?? null
29489
29571
  });
29490
- if (payloadMode === "full" && dbPage.fullPayloadsExact && !page.truncation?.reasons.includes("model_payload")) {
29572
+ if (payloadMode === "full" && dbPage.fullPayloadsExact && !page2.truncation?.reasons.includes("model_payload")) {
29491
29573
  await acknowledgeReads([
29492
- { sessionId, sequences: page.events.map((event) => event.sequence) }
29574
+ { sessionId, sequences: page2.events.map((event) => event.sequence) }
29493
29575
  ]);
29494
29576
  }
29495
- return json(page);
29577
+ return json(page2);
29496
29578
  }
29497
29579
  );
29498
29580
  server.registerTool(
@@ -29548,7 +29630,7 @@ function registerWorkspaceOrchestrationTools(server, deps, grant, can, callerSes
29548
29630
  }
29549
29631
  },
29550
29632
  readTargetEvents: async (target) => {
29551
- const page = await listSessionEventPage(deps.db, workspaceId, target.sessionId, {
29633
+ const page2 = await listSessionEventPage(deps.db, workspaceId, target.sessionId, {
29552
29634
  after: target.afterSequence,
29553
29635
  direction: "after",
29554
29636
  limit: SESSION_WAIT_EVENTS_PER_TARGET,
@@ -29556,10 +29638,10 @@ function registerWorkspaceOrchestrationTools(server, deps, grant, can, callerSes
29556
29638
  includeTypes: targetEventTypes,
29557
29639
  maxBytes: SESSION_EVENT_MCP_MAX_BYTES * 4
29558
29640
  });
29559
- if (!page.fullPayloadsExact) {
29560
- for (const event of page.events) incompleteWaitEvents.add(event.id);
29641
+ if (!page2.fullPayloadsExact) {
29642
+ for (const event of page2.events) incompleteWaitEvents.add(event.id);
29561
29643
  }
29562
- return { events: page.events, hasMore: page.hasMore };
29644
+ return { events: page2.events, hasMore: page2.hasMore };
29563
29645
  },
29564
29646
  readOwnPendingUpdateKinds: ownSessionId === null ? null : async () => (await listOutstandingSessionSystemUpdates(deps.db, workspaceId, ownSessionId)).map((update) => update.kind),
29565
29647
  subscribe: (targetSessionId, onEvents) => deps.bus.subscribe(workspaceId, targetSessionId, onEvents)
@@ -30443,9 +30525,12 @@ function registerCapabilityDiscoveryTools(server, deps, grant, sessionId, json)
30443
30525
  };
30444
30526
  const setupProjections = async (items) => {
30445
30527
  const availableServerIds = /* @__PURE__ */ new Set();
30528
+ const availableFirstPartyTools = /* @__PURE__ */ new Set();
30446
30529
  const acceptedServerIds = /* @__PURE__ */ new Set();
30447
30530
  let accountSelectionKnown = false;
30448
- if (items.some((item) => item.enabled && item.connectionRef)) {
30531
+ if (items.some(
30532
+ (item) => item.enabled && (item.connectionRef || item.surfaceType === "first_party_fiken")
30533
+ )) {
30449
30534
  const claims = exactAgentCommandContext(grant, sessionId);
30450
30535
  const attemptCatalog = await getAttemptToolCatalog2(deps.db, {
30451
30536
  accountId: grant.accountId,
@@ -30453,7 +30538,11 @@ function registerCapabilityDiscoveryTools(server, deps, grant, sessionId, json)
30453
30538
  attemptId: claims.callerAttemptId
30454
30539
  });
30455
30540
  if (attemptCatalog?.sessionId === sessionId && attemptCatalog.turnId === claims.callerTurnId && attemptCatalog.executionGeneration === claims.callerExecutionGeneration) {
30456
- for (const entry2 of attemptCatalog.entries) availableServerIds.add(entry2.identity.serverId);
30541
+ for (const entry2 of attemptCatalog.entries) {
30542
+ availableServerIds.add(entry2.identity.serverId);
30543
+ if (entry2.identity.serverId === "opengeni")
30544
+ availableFirstPartyTools.add(entry2.identity.toolName);
30545
+ }
30457
30546
  const bindings = await getSessionTurnMcpAccountBindings(
30458
30547
  deps.db,
30459
30548
  grant.workspaceId,
@@ -30474,7 +30563,9 @@ function registerCapabilityDiscoveryTools(server, deps, grant, sessionId, json)
30474
30563
  item,
30475
30564
  availableServerIds,
30476
30565
  acceptedServerIds,
30477
- accountSelectionKnown
30566
+ accountSelectionKnown,
30567
+ availableFirstPartyTools,
30568
+ new Set(grant.metadata?.firstPartyMcpTools ?? [])
30478
30569
  )
30479
30570
  )
30480
30571
  );
@@ -30593,7 +30684,38 @@ function registerCapabilityDiscoveryTools(server, deps, grant, sessionId, json)
30593
30684
  }
30594
30685
  );
30595
30686
  }
30596
- async function capabilitySetupProjection(deps, workspaceId, item, availableServerIds, acceptedServerIds, accountSelectionKnown) {
30687
+ async function capabilitySetupProjection(deps, workspaceId, item, availableServerIds, acceptedServerIds, accountSelectionKnown, availableFirstPartyTools, selectedFirstPartyTools) {
30688
+ if (item.surfaceType === "first_party_fiken") {
30689
+ if (!item.enabled || item.metadata.connectionStatus !== "active") {
30690
+ return {
30691
+ status: "authorization_required",
30692
+ action: "connect",
30693
+ detail: item.metadata.connectionStatus === "needs_reauth" ? "The workspace Fiken connection needs reconnection." : "Connect Fiken through the protected setup form."
30694
+ };
30695
+ }
30696
+ const selected = FIRST_PARTY_MCP_TOOL_NAMES2.filter(
30697
+ (name) => name.startsWith("fiken_") && selectedFirstPartyTools.has(name)
30698
+ );
30699
+ if (selected.length === 0) {
30700
+ return {
30701
+ status: "authorization_required",
30702
+ action: "enable",
30703
+ detail: "Fiken is connected, but its tools are not selected for this conversation. Ask the user to add its tools through the setup card, then send a new message."
30704
+ };
30705
+ }
30706
+ if (selected.some((name) => availableFirstPartyTools.has(name))) {
30707
+ return {
30708
+ status: "ready",
30709
+ action: null,
30710
+ detail: "Fiken is connected and has tools available in this turn."
30711
+ };
30712
+ }
30713
+ return {
30714
+ status: "unavailable",
30715
+ action: null,
30716
+ detail: "Fiken is connected, but its selected tools are unavailable in this execution. Check tool permissions and setup before retrying; reconnection is not required by this status."
30717
+ };
30718
+ }
30597
30719
  if (item.id === "api:github-app" || item.surfaceType === "first_party_github") {
30598
30720
  const missing = githubAppMissingSettings(deps.settings);
30599
30721
  if (missing.length > 0) {
@@ -30947,9 +31069,9 @@ function decodeSessionDiscoveryCursor(value) {
30947
31069
  throw new Error("sessions_list cursor is invalid");
30948
31070
  }
30949
31071
  }
30950
- function capSessionDiscoveryCompactPage(page, options = {}, maxBytes = SESSION_DISCOVERY_PAGE_MAX_BYTES) {
31072
+ function capSessionDiscoveryCompactPage(page2, options = {}, maxBytes = SESSION_DISCOVERY_PAGE_MAX_BYTES) {
30951
31073
  let previewBytes = 0;
30952
- const projected = page.sessions.map((session) => {
31074
+ const projected = page2.sessions.map((session) => {
30953
31075
  const row = {
30954
31076
  ...compactSessionMcpListRow(session, options.includeRelatedWork),
30955
31077
  ...options.includeLastMessage && session.queuedPromptCount > 0 ? { queuedPromptCount: session.queuedPromptCount } : {}
@@ -30986,24 +31108,24 @@ function capSessionDiscoveryCompactPage(page, options = {}, maxBytes = SESSION_D
30986
31108
  let kept = projected;
30987
31109
  const build = () => {
30988
31110
  const dropped = kept.length < projected.length;
30989
- const last = page.sessions[kept.length - 1];
31111
+ const last = page2.sessions[kept.length - 1];
30990
31112
  const cursor = dropped && last ? {
30991
- orderBy: page.orderBy,
31113
+ orderBy: page2.orderBy,
30992
31114
  sortRank: last.sortRank,
30993
31115
  sortRevision: last.sortRevision,
30994
31116
  sortAt: last.sortAt,
30995
31117
  id: last.id,
30996
- snapshotAt: page.snapshotAt,
30997
- snapshotRevision: page.snapshotRevision,
30998
- updatedAfter: page.updatedAfter,
30999
- filterHash: page.filterHash,
31000
- ...page.originSiteId ? { originSiteId: page.originSiteId } : {}
31001
- } : page.nextCursor;
31118
+ snapshotAt: page2.snapshotAt,
31119
+ snapshotRevision: page2.snapshotRevision,
31120
+ updatedAfter: page2.updatedAfter,
31121
+ filterHash: page2.filterHash,
31122
+ ...page2.originSiteId ? { originSiteId: page2.originSiteId } : {}
31123
+ } : page2.nextCursor;
31002
31124
  return {
31003
31125
  sessions: kept,
31004
- total: page.total,
31126
+ total: page2.total,
31005
31127
  nextCursor: cursor ? encodeSessionDiscoveryCursor(cursor) : null,
31006
- ...page.orderBy === "updatedAt" ? { updatedThrough: page.updatedThrough } : {},
31128
+ ...page2.orderBy === "updatedAt" ? { updatedThrough: page2.updatedThrough } : {},
31007
31129
  ...dropped ? {
31008
31130
  responseTruncated: true,
31009
31131
  truncationReason: `response exceeded ${maxBytes} bytes; continue with nextCursor`
@@ -31020,8 +31142,8 @@ function capSessionDiscoveryCompactPage(page, options = {}, maxBytes = SESSION_D
31020
31142
  }
31021
31143
  return result;
31022
31144
  }
31023
- function capSessionDiscoveryPage(page, includeLastMessage) {
31024
- const projected = page.sessions.map((session) => {
31145
+ function capSessionDiscoveryPage(page2, includeLastMessage) {
31146
+ const projected = page2.sessions.map((session) => {
31025
31147
  const title = capSessionDiscoveryText(session.title, 200, session.titleOriginalChars);
31026
31148
  const goal = session.goal ? capSessionDiscoveryText(
31027
31149
  session.goal.text,
@@ -31108,29 +31230,29 @@ function capSessionDiscoveryPage(page, includeLastMessage) {
31108
31230
  }).length : 0;
31109
31231
  const lastKept = kept.at(-1);
31110
31232
  const droppedForByteCap = kept.length < projected.length;
31111
- const sourceLast = lastKept ? page.sessions.find((session) => session.id === lastKept.id) : void 0;
31233
+ const sourceLast = lastKept ? page2.sessions.find((session) => session.id === lastKept.id) : void 0;
31112
31234
  const nextCursor = droppedForByteCap ? sourceLast ? encodeSessionDiscoveryCursor({
31113
- orderBy: page.orderBy,
31235
+ orderBy: page2.orderBy,
31114
31236
  sortRank: sourceLast.sortRank,
31115
31237
  sortRevision: sourceLast.sortRevision,
31116
31238
  sortAt: sourceLast.sortAt,
31117
31239
  id: sourceLast.id,
31118
- snapshotAt: page.snapshotAt,
31119
- snapshotRevision: page.snapshotRevision,
31120
- updatedAfter: page.updatedAfter,
31121
- filterHash: page.filterHash,
31122
- ...page.originSiteId ? { originSiteId: page.originSiteId } : {}
31123
- }) : null : page.nextCursor ? encodeSessionDiscoveryCursor(page.nextCursor) : null;
31240
+ snapshotAt: page2.snapshotAt,
31241
+ snapshotRevision: page2.snapshotRevision,
31242
+ updatedAfter: page2.updatedAfter,
31243
+ filterHash: page2.filterHash,
31244
+ ...page2.originSiteId ? { originSiteId: page2.originSiteId } : {}
31245
+ }) : null : page2.nextCursor ? encodeSessionDiscoveryCursor(page2.nextCursor) : null;
31124
31246
  const result2 = {
31125
31247
  sessions: kept,
31126
- total: page.total,
31127
- hasMore: page.hasMore || droppedForByteCap,
31248
+ total: page2.total,
31249
+ hasMore: page2.hasMore || droppedForByteCap,
31128
31250
  nextCursor,
31129
- orderBy: page.orderBy,
31130
- snapshotAt: page.snapshotAt,
31131
- snapshotRevision: page.snapshotRevision,
31132
- updatedAfter: page.updatedAfter,
31133
- updatedThrough: page.updatedThrough,
31251
+ orderBy: page2.orderBy,
31252
+ snapshotAt: page2.snapshotAt,
31253
+ snapshotRevision: page2.snapshotRevision,
31254
+ updatedAfter: page2.updatedAfter,
31255
+ updatedThrough: page2.updatedThrough,
31134
31256
  ...includeLastMessage ? {
31135
31257
  latestMessagePreviewBudget: {
31136
31258
  bytes: previewBytes,
@@ -67405,7 +67527,8 @@ function registerDocumentRoutes(app, deps) {
67405
67527
  deps,
67406
67528
  await requireAccessGrantAuthorization8(c, deps, workspaceId, "documents:search"),
67407
67529
  "documents:search"
67408
- )
67530
+ ),
67531
+ settings: deps.settings
67409
67532
  }
67410
67533
  );
67411
67534
  await server.connect(transport);
@@ -67492,6 +67615,8 @@ import {
67492
67615
  prepareKnowledgeFile as prepareKnowledgeFile2,
67493
67616
  prepareKnowledgeSave as prepareKnowledgeSave2,
67494
67617
  searchKnowledgeEntries as searchKnowledgeEntries3,
67618
+ KnowledgeVectorFundingError,
67619
+ KnowledgeVectorQueryRejectedError,
67495
67620
  hasPermission as hasPermission13,
67496
67621
  knowledgeContextForAccess as knowledgeContextForAccess2,
67497
67622
  requireAccessGrantAuthorization as requireAccessGrantAuthorization9,
@@ -67531,6 +67656,18 @@ var WriteSettings = ReadSettings.extend({
67531
67656
  var Id = z22.uuid();
67532
67657
  function knowledgeHttpError(error) {
67533
67658
  if (error instanceof HTTPException42) throw error;
67659
+ if (error instanceof KnowledgeVectorFundingError)
67660
+ throw new ApiHttpError(402, {
67661
+ code: "payment_required",
67662
+ message: error.message,
67663
+ details: { code: error.code, keywordAvailable: true }
67664
+ });
67665
+ if (error instanceof KnowledgeVectorQueryRejectedError)
67666
+ throw new ApiHttpError(error.code === "quota" ? 429 : 422, {
67667
+ code: error.code === "quota" ? "limit_exceeded" : "validation_failed",
67668
+ message: error.message,
67669
+ details: { code: error.code, keywordAvailable: true }
67670
+ });
67534
67671
  if (error instanceof z22.ZodError)
67535
67672
  throw new HTTPException42(422, {
67536
67673
  message: error.issues[0]?.message ?? "Invalid Knowledge request"
@@ -67662,7 +67799,9 @@ function registerKnowledgeRoutes(app, deps) {
67662
67799
  deps.db,
67663
67800
  context,
67664
67801
  KnowledgeSavePreparationRequest2.parse(await c.req.json()),
67665
- () => deps.getDocumentServices().embedder
67802
+ () => deps.getDocumentServices().embedder,
67803
+ void 0,
67804
+ deps.settings
67666
67805
  )
67667
67806
  );
67668
67807
  })
@@ -67677,7 +67816,8 @@ function registerKnowledgeRoutes(app, deps) {
67677
67816
  deps.db,
67678
67817
  context,
67679
67818
  KnowledgeEntryListRequest3.parse(await c.req.json()),
67680
- () => deps.getDocumentServices().embedder
67819
+ () => deps.getDocumentServices().embedder,
67820
+ deps.settings
67681
67821
  )
67682
67822
  )
67683
67823
  )
@@ -67724,7 +67864,8 @@ function registerKnowledgeRoutes(app, deps) {
67724
67864
  ...query,
67725
67865
  ...query.limit ? { limit: Number(query.limit) } : {}
67726
67866
  }),
67727
- () => deps.getDocumentServices().embedder
67867
+ () => deps.getDocumentServices().embedder,
67868
+ deps.settings
67728
67869
  )
67729
67870
  );
67730
67871
  })
@@ -70394,7 +70535,8 @@ import { randomUUID as randomUUID7 } from "crypto";
70394
70535
  import {
70395
70536
  environmentsEncryptionKeyBytes as environmentsEncryptionKeyBytes5,
70396
70537
  resolveFirstPartyDelegationSecret as resolveFirstPartyDelegationSecret3,
70397
- resolveStreamTokenSecret as resolveStreamTokenSecret2
70538
+ resolveStreamTokenSecret as resolveStreamTokenSecret2,
70539
+ sandboxWarmRateMicrosPerSecond as sandboxWarmRateMicrosPerSecond3
70398
70540
  } from "@opengeni/config";
70399
70541
  import {
70400
70542
  BROWSER_CONTROL_WEBSOCKET_BEARER_PREFIX,
@@ -70412,6 +70554,8 @@ import {
70412
70554
  BrowserDownloadSaveRequest,
70413
70555
  BrowserDownloadSaveResponse,
70414
70556
  BrowserDownloadListResponse,
70557
+ BrowserDomReadRequest,
70558
+ BrowserDomReadResponse,
70415
70559
  BrowserExternalAuthCommand,
70416
70560
  BrowserOpenTargetRequest,
70417
70561
  BrowserObservation,
@@ -70423,6 +70567,7 @@ import {
70423
70567
  BrowserSessionListResponse,
70424
70568
  BrowserSessionMutationResponse,
70425
70569
  BrowserTargetListResponse,
70570
+ BrowserTargetState,
70426
70571
  CreateBrowserSessionRequest,
70427
70572
  ExternalAuthInteractiveRequest,
70428
70573
  ExternalAuthInteractiveResponse,
@@ -70454,6 +70599,7 @@ import {
70454
70599
  BrowserSessionNotFoundError,
70455
70600
  BrowserSessionOperationConflictError,
70456
70601
  BrowserSessionStateError as BrowserSessionStateError2,
70602
+ SandboxPaidComputeAdmissionError as SandboxPaidComputeAdmissionError3,
70457
70603
  completeBrowserSessionEnd,
70458
70604
  completeExternalAuth,
70459
70605
  completeBrowserDownloadSave,
@@ -70467,6 +70613,7 @@ import {
70467
70613
  dispatchExternalAuth,
70468
70614
  dispatchProtectedAuthFill,
70469
70615
  failBrowserSessionOperation,
70616
+ failPreparedBrowserSessionSuspend,
70470
70617
  failBrowserSessionResume,
70471
70618
  failBrowserSessionResumePreparation,
70472
70619
  failBrowserSessionSuspension,
@@ -72355,6 +72502,68 @@ function registerBrowserSessionRoutes(app, deps) {
72355
72502
  return context.json(result);
72356
72503
  }
72357
72504
  );
72505
+ app.get(
72506
+ "/v1/workspaces/:workspaceId/browser-sessions/:browserSessionId/targets/:targetId/state",
72507
+ async (context) => {
72508
+ const { workspaceId, grant, browserSessionId } = await browserRoutePreamble(
72509
+ context,
72510
+ "sessions:read"
72511
+ );
72512
+ const targetId = requireOpaqueParam(context, "targetId");
72513
+ const result = await withActiveBrowserController(
72514
+ context,
72515
+ grant,
72516
+ workspaceId,
72517
+ browserSessionId,
72518
+ "session.read",
72519
+ "browser.read",
72520
+ async ({ sessionClient }) => await sessionClient.targetState(targetId)
72521
+ );
72522
+ return context.json(BrowserTargetState.parse(result));
72523
+ }
72524
+ );
72525
+ app.post(
72526
+ "/v1/workspaces/:workspaceId/browser-sessions/:browserSessionId/targets/:targetId/dom-read",
72527
+ async (context) => {
72528
+ const { workspaceId, grant, browserSessionId } = await browserRoutePreamble(
72529
+ context,
72530
+ "sessions:read"
72531
+ );
72532
+ const targetId = requireOpaqueParam(context, "targetId");
72533
+ const request = await parseJsonBody2(context, BrowserDomReadRequest);
72534
+ const result = await withActiveBrowserController(
72535
+ context,
72536
+ grant,
72537
+ workspaceId,
72538
+ browserSessionId,
72539
+ "session.read",
72540
+ "browser.read",
72541
+ async ({ sessionClient }) => await sessionClient.readDom(targetId, request)
72542
+ );
72543
+ return context.json(BrowserDomReadResponse.parse(result));
72544
+ }
72545
+ );
72546
+ app.get(
72547
+ "/v1/workspaces/:workspaceId/browser-sessions/:browserSessionId/targets/:targetId/screenshot",
72548
+ async (context) => {
72549
+ const { workspaceId, grant, browserSessionId } = await browserRoutePreamble(
72550
+ context,
72551
+ "sessions:read"
72552
+ );
72553
+ const targetId = requireOpaqueParam(context, "targetId");
72554
+ const captureOptions = parseBrowserScreenshotOptions(new URL(context.req.url).searchParams);
72555
+ const frame = await withActiveBrowserController(
72556
+ context,
72557
+ grant,
72558
+ workspaceId,
72559
+ browserSessionId,
72560
+ "session.read",
72561
+ "browser.read",
72562
+ async ({ sessionClient }) => await sessionClient.capture(targetId, captureOptions)
72563
+ );
72564
+ return browserScreenshotResponse(frame);
72565
+ }
72566
+ );
72358
72567
  app.post(
72359
72568
  "/v1/workspaces/:workspaceId/browser-sessions/:browserSessionId/actions",
72360
72569
  async (context) => {
@@ -73388,6 +73597,7 @@ function registerBrowserSessionRoutes(app, deps) {
73388
73597
  const request = await parseJsonBody2(context, BrowserSessionLifecycleRequest);
73389
73598
  const startedAtMs = performance.now();
73390
73599
  const origin = requestOrigin(context, deps.settings);
73600
+ let preparedForDispatch = false;
73391
73601
  try {
73392
73602
  const before = await getBrowserSessionControlRecord(deps.db, {
73393
73603
  accountId: grant.accountId,
@@ -73436,6 +73646,7 @@ function registerBrowserSessionRoutes(app, deps) {
73436
73646
  observeLifecycleResult(deps.observability, startedAtMs, parsed2);
73437
73647
  return context.json(parsed2, 200);
73438
73648
  }
73649
+ preparedForDispatch = true;
73439
73650
  const record6 = await getBrowserSessionControlRecord(deps.db, {
73440
73651
  accountId: grant.accountId,
73441
73652
  workspaceId,
@@ -73594,6 +73805,15 @@ function registerBrowserSessionRoutes(app, deps) {
73594
73805
  observeLifecycleResult(deps.observability, startedAtMs, parsed);
73595
73806
  return context.json(parsed, 200);
73596
73807
  } catch (error) {
73808
+ if (preparedForDispatch) {
73809
+ await failPreparedBrowserSessionSuspend(deps.db, {
73810
+ accountId: grant.accountId,
73811
+ workspaceId,
73812
+ browserSessionId,
73813
+ operationId: request.operationId,
73814
+ error: interactionFailure(error)
73815
+ });
73816
+ }
73597
73817
  throw browserRouteError(error);
73598
73818
  }
73599
73819
  }
@@ -74082,6 +74302,11 @@ function registerBrowserSessionRoutes(app, deps) {
74082
74302
  const resolved = await handle.routingSession.prime();
74083
74303
  if (expectedPlacement?.kind === "connected_machine") {
74084
74304
  if (resolved.kind !== "selfhosted" || resolved.sandboxId !== expectedPlacement.sandboxId) {
74305
+ if (operation === "browser.create") {
74306
+ throw new HTTPException51(422, {
74307
+ message: "The requested Connected Machine is not this session's current placement. Move the session to that machine before creating an interaction resource."
74308
+ });
74309
+ }
74085
74310
  return await throwBrowserSourcePlacementChanged(
74086
74311
  grant,
74087
74312
  sourceSession.id,
@@ -74797,6 +75022,10 @@ async function ensureInteractionHolder(deps, grant, sourceSession, browserSessio
74797
75022
  holderId: interactionHolderId(browserSessionId),
74798
75023
  subjectId: sourceSession.id,
74799
75024
  backend: placement.lease.backend,
75025
+ warmBilling: {
75026
+ mode: deps.settings.sandboxWarmBillingMode,
75027
+ rateMicrosPerSecond: sandboxWarmRateMicrosPerSecond3(deps.settings, placement.lease.backend)
75028
+ },
74800
75029
  os: placement.lease.os,
74801
75030
  image: sandboxRuntime.image,
74802
75031
  rigVersionId: sourceSession.rigVersionId,
@@ -75086,6 +75315,41 @@ function optionalBoundedInteger(value, minimum, maximum) {
75086
75315
  }
75087
75316
  return parsed;
75088
75317
  }
75318
+ function parseBrowserScreenshotOptions(query) {
75319
+ for (const key of ["fullPage", "format", "quality"]) {
75320
+ if (query.getAll(key).length > 1) {
75321
+ throw new HTTPException51(400, { message: `duplicate browser screenshot ${key}` });
75322
+ }
75323
+ }
75324
+ const fullPage = query.get("fullPage");
75325
+ if (fullPage !== null && fullPage !== "true" && fullPage !== "false") {
75326
+ throw new HTTPException51(400, { message: "invalid browser screenshot fullPage" });
75327
+ }
75328
+ const format = query.get("format");
75329
+ if (format !== null && format !== "jpeg" && format !== "png") {
75330
+ throw new HTTPException51(400, { message: "invalid browser screenshot format" });
75331
+ }
75332
+ const quality = query.get("quality");
75333
+ const parsedQuality = quality === null ? null : optionalBoundedInteger(quality, 1, 100);
75334
+ if (quality !== null && parsedQuality === null) {
75335
+ throw new HTTPException51(400, { message: "invalid browser screenshot quality" });
75336
+ }
75337
+ return {
75338
+ ...fullPage === null ? {} : { fullPage: fullPage === "true" },
75339
+ ...format === null ? {} : { format },
75340
+ ...parsedQuality === null ? {} : { quality: parsedQuality }
75341
+ };
75342
+ }
75343
+ function browserScreenshotResponse(frame) {
75344
+ return new Response(Uint8Array.from(frame.data).buffer, {
75345
+ status: 200,
75346
+ headers: {
75347
+ "cache-control": "no-store",
75348
+ "content-type": frame.mediaType,
75349
+ "x-opengeni-browser-frame": frame.metadataHeader
75350
+ }
75351
+ });
75352
+ }
75089
75353
  function isUuid(value) {
75090
75354
  return typeof value === "string" && /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/iu.test(value);
75091
75355
  }
@@ -75480,6 +75744,8 @@ function browserRouteError(error) {
75480
75744
  const connectedMachineError = interactionControlApiError(error, "browser");
75481
75745
  if (connectedMachineError) return connectedMachineError;
75482
75746
  if (error instanceof HTTPException51) return error;
75747
+ if (error instanceof SandboxPaidComputeAdmissionError3)
75748
+ return new HTTPException51(402, { message: error.message, cause: error });
75483
75749
  if (error instanceof BrowserSessionNotFoundError) {
75484
75750
  return new HTTPException51(404, { message: error.message, cause: error });
75485
75751
  }
@@ -75533,7 +75799,11 @@ function isAbort(error) {
75533
75799
 
75534
75800
  // src/routes/computer-sessions.ts
75535
75801
  import { randomUUID as randomUUID8 } from "crypto";
75536
- import { resolveFirstPartyDelegationSecret as resolveFirstPartyDelegationSecret4, resolveStreamTokenSecret as resolveStreamTokenSecret3 } from "@opengeni/config";
75802
+ import {
75803
+ resolveFirstPartyDelegationSecret as resolveFirstPartyDelegationSecret4,
75804
+ resolveStreamTokenSecret as resolveStreamTokenSecret3,
75805
+ sandboxWarmRateMicrosPerSecond as sandboxWarmRateMicrosPerSecond4
75806
+ } from "@opengeni/config";
75537
75807
  import {
75538
75808
  BROWSER_CONTROL_WEBSOCKET_BEARER_PREFIX as BROWSER_CONTROL_WEBSOCKET_BEARER_PREFIX2,
75539
75809
  BROWSER_CONTROL_PORT as BROWSER_CONTROL_PORT2,
@@ -75560,6 +75830,7 @@ import {
75560
75830
  ComputerSessionNotFoundError,
75561
75831
  ComputerSessionOperationConflictError,
75562
75832
  ComputerSessionStateError,
75833
+ SandboxPaidComputeAdmissionError as SandboxPaidComputeAdmissionError4,
75563
75834
  dispatchComputerSessionOperation,
75564
75835
  failComputerSessionOperation,
75565
75836
  findComputerSessionControlRecordByOperation,
@@ -76447,6 +76718,11 @@ function registerComputerSessionRoutes(app, deps) {
76447
76718
  }
76448
76719
  if (expectedPlacement?.kind === "connected_machine") {
76449
76720
  if (resolved.kind !== "selfhosted" || resolved.sandboxId !== expectedPlacement.sandboxId) {
76721
+ if (operation === "computer.create") {
76722
+ throw new HTTPException52(422, {
76723
+ message: "The requested Connected Machine is not this session's current placement. Move the session to that machine before creating an interaction resource."
76724
+ });
76725
+ }
76450
76726
  return await throwComputerSourcePlacementChanged(
76451
76727
  grant,
76452
76728
  sourceSession.id,
@@ -76733,6 +77009,10 @@ function registerComputerSessionRoutes(app, deps) {
76733
77009
  holderId: interactionHolderId2(computerSessionId),
76734
77010
  subjectId: sourceSession.id,
76735
77011
  backend: placement.lease.backend,
77012
+ warmBilling: {
77013
+ mode: deps.settings.sandboxWarmBillingMode,
77014
+ rateMicrosPerSecond: sandboxWarmRateMicrosPerSecond4(deps.settings, placement.lease.backend)
77015
+ },
76736
77016
  os: placement.lease.os,
76737
77017
  image: sandboxRuntime.image,
76738
77018
  rigVersionId: sourceSession.rigVersionId,
@@ -77055,6 +77335,8 @@ function computerRouteError(error) {
77055
77335
  const connectedMachineError = interactionControlApiError(error, "computer");
77056
77336
  if (connectedMachineError) return connectedMachineError;
77057
77337
  if (error instanceof HTTPException52) return error;
77338
+ if (error instanceof SandboxPaidComputeAdmissionError4)
77339
+ return new HTTPException52(402, { message: error.message, cause: error });
77058
77340
  if (error instanceof ComputerSessionNotFoundError) {
77059
77341
  return new HTTPException52(404, { message: error.message, cause: error });
77060
77342
  }
@@ -77203,6 +77485,92 @@ async function integrationCommitGrant(authorization, permissions, request) {
77203
77485
  };
77204
77486
  }
77205
77487
 
77488
+ // src/routes/github-browser-pages.ts
77489
+ var pageStyle = `
77490
+ :root{color-scheme:dark;--bg:oklch(.155 .012 260);--surface:oklch(.19 .014 260);--raised:oklch(.225 .015 260);--border:oklch(.3 .014 260);--border-strong:oklch(.42 .016 260);--fg:oklch(.955 .005 260);--muted:oklch(.73 .012 260);--blue:oklch(.54 .18 255);--blue-light:oklch(.72 .15 255)}
77491
+ *{box-sizing:border-box}
77492
+ html{min-height:100%;background:var(--bg)}
77493
+ body{margin:0;min-height:100vh;min-height:100dvh;color:var(--fg);background:var(--bg);font-family:"Inter Variable",Inter,ui-sans-serif,system-ui,sans-serif;font-feature-settings:"cv11","ss01","ss03";-webkit-font-smoothing:antialiased}
77494
+ .shell{width:min(100% - 48px,700px);margin:0 auto}
77495
+ header{height:84px;display:flex;align-items:center;justify-content:space-between;gap:16px;border-bottom:1px solid var(--border)}
77496
+ .brand{font-size:16px;font-weight:670;letter-spacing:-.045em;color:var(--fg);text-decoration:none}
77497
+ .context{font-size:12px;color:var(--muted)}
77498
+ main{padding:clamp(48px,10vh,100px) 0 96px}
77499
+ h1{margin:0 0 14px;font-size:clamp(27px,4vw,36px);font-weight:640;letter-spacing:-.05em;line-height:1.17}
77500
+ p{margin:0;color:var(--muted);font-size:14px;line-height:1.65;max-width:59ch}
77501
+ .intro{margin-bottom:32px}
77502
+ .options{display:grid;gap:10px;margin:0 0 24px;padding:0;border:0;max-height:min(390px,52vh);overflow:auto}
77503
+ .option{display:flex;gap:14px;align-items:center;min-height:70px;padding:14px 16px;border:1px solid var(--border);border-radius:10px;background:var(--surface);cursor:pointer;transition:background .15s,border-color .15s}
77504
+ .option:hover{background:var(--raised)}
77505
+ .option:has(input:checked){border-color:var(--blue-light);background:var(--raised)}
77506
+ .option:has(input:focus-visible){outline:2px solid var(--blue-light);outline-offset:3px}
77507
+ .option input{margin:0;flex:none;width:17px;height:17px;accent-color:var(--blue-light)}
77508
+ .option-text{min-width:0;display:grid;gap:3px}
77509
+ .option strong{overflow-wrap:anywhere;font-size:14px;font-weight:600}
77510
+ .option small{font-size:12px;color:var(--muted)}
77511
+ .actions{display:flex;align-items:center;flex-wrap:wrap;gap:12px;margin-top:24px}
77512
+ .button{display:inline-flex;min-height:42px;align-items:center;justify-content:center;padding:0 17px;border:1px solid var(--blue);border-radius:8px;background:var(--blue);color:#fff;font-family:inherit;font-size:13px;font-weight:600;text-decoration:none;cursor:pointer}
77513
+ .button:hover{background:oklch(.6 .18 255)}
77514
+ .button:disabled{cursor:wait;opacity:.7}
77515
+ .secondary{border-color:var(--border);background:transparent;color:var(--fg)}
77516
+ .secondary:hover{background:var(--raised)}
77517
+ :is(button,a):focus-visible{outline:2px solid var(--blue-light);outline-offset:3px}
77518
+ .note{margin-top:30px;padding-top:20px;border-top:1px solid var(--border);font-size:12px}
77519
+ .env-header{display:flex;justify-content:space-between;align-items:center;gap:14px;margin-top:36px}
77520
+ h2{margin:0;font-size:14px;font-weight:600}
77521
+ pre{white-space:pre-wrap;overflow-wrap:anywhere;max-height:380px;overflow:auto;padding:18px;border:1px solid var(--border);border-radius:10px;background:var(--surface);font:12px/1.6 ui-monospace,SFMono-Regular,Menlo,monospace}
77522
+ @media(max-width:480px){.shell{width:calc(100% - 32px)}header{height:70px}main{padding-top:56px}.actions{align-items:stretch;flex-direction:column}.actions form,.actions .button{width:100%}}
77523
+ @media(prefers-reduced-motion:reduce){.option{transition:none}}
77524
+ `;
77525
+ function page(title, content) {
77526
+ return `<!doctype html><html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>${escapeHtml4(title)} \xB7 OpenGeni</title><style>${pageStyle}</style></head><body><div class="shell"><header><span class="brand">OpenGeni</span><span class="context">GitHub connection</span></header><main>${content}</main></div></body></html>`;
77527
+ }
77528
+ function githubInstallationChooserHtml(candidates, state, workspaceId, baseUrl) {
77529
+ const action = `${baseUrl}/v1/workspaces/${encodeURIComponent(workspaceId)}/github/installations/select`;
77530
+ const options = candidates.map(({ installation, authorityKind }, index) => {
77531
+ const account = escapeHtml4(
77532
+ installation.accountLogin ?? `installation ${installation.installationId}`
77533
+ );
77534
+ const label = authorityKind === "personal_owner" ? "Personal account" : "Organization owner";
77535
+ return `<label class="option"><input type="radio" name="installation_id" value="${installation.installationId}" required${candidates.length === 1 && index === 0 ? " checked" : ""}><span class="option-text"><strong>${account}</strong><small>${label}</small></span></label>`;
77536
+ }).join("");
77537
+ return page(
77538
+ "Choose a GitHub account",
77539
+ `<h1>Choose a GitHub account</h1><p class="intro">Connect an account where the OpenGeni GitHub App is already installed, or install it on another account.</p><form id="existing-account" method="get" action="${escapeHtml4(action)}"><input type="hidden" name="state" value="${escapeHtml4(state)}"><fieldset class="options" aria-label="Available GitHub accounts">${options}</fieldset></form><div class="actions"><button class="button" type="submit" form="existing-account">Connect selected account</button><form method="get" action="${escapeHtml4(action)}"><input type="hidden" name="state" value="${escapeHtml4(state)}"><input type="hidden" name="installation_id" value="new"><button class="button secondary" type="submit">Install on another account</button></form></div><p class="note">Only accounts GitHub has confirmed you own or manage appear here.</p>`
77540
+ );
77541
+ }
77542
+ function githubSetupSuccessHtml(account, returnUrl2) {
77543
+ return page(
77544
+ "GitHub connected",
77545
+ `<h1>GitHub connected</h1><p class="intro"><strong>${escapeHtml4(account)}</strong> is now available in this OpenGeni workspace. OpenGeni can access only the repositories allowed for this installation.</p><a class="button" href="${escapeHtml4(returnUrl2)}">Back to OpenGeni</a>`
77546
+ );
77547
+ }
77548
+ function githubSetupPendingHtml() {
77549
+ return page(
77550
+ "GitHub approval pending",
77551
+ `<h1>Waiting for an organization owner</h1><p>An owner needs to approve your GitHub App request before you can connect. OpenGeni has not created a workspace binding.</p>`
77552
+ );
77553
+ }
77554
+ function githubSuccessHtml(envLines) {
77555
+ const escaped = escapeHtml4(envLines.join("\n"));
77556
+ return page(
77557
+ "GitHub App created",
77558
+ `<h1>GitHub App created</h1><p>Add these values to .env, then restart API and worker.</p><div class="env-header"><h2>Environment variables</h2><button class="button secondary" id="copy-env" type="button">Copy env</button></div><pre id="env-lines">${escaped}</pre><script>(()=>{const button=document.getElementById("copy-env");const env=document.getElementById("env-lines");async function copyText(text){if(navigator.clipboard&&window.isSecureContext){await navigator.clipboard.writeText(text);return;}const area=document.createElement("textarea");area.value=text;area.setAttribute("readonly","");area.style.position="fixed";area.style.inset="-9999px";document.body.append(area);area.select();document.execCommand("copy");area.remove();}button?.addEventListener("click",async()=>{try{await copyText(env?.textContent||"");button.textContent="Copied";setTimeout(()=>button.textContent="Copy env",1600);}catch{button.textContent="Copy failed";setTimeout(()=>button.textContent="Copy env",2200);}});})();</script>`
77559
+ );
77560
+ }
77561
+ function escapeHtml4(value) {
77562
+ return value.replace(
77563
+ /[&<>"']/g,
77564
+ (char) => ({
77565
+ "&": "&amp;",
77566
+ "<": "&lt;",
77567
+ ">": "&gt;",
77568
+ '"': "&quot;",
77569
+ "'": "&#39;"
77570
+ })[char] ?? char
77571
+ );
77572
+ }
77573
+
77206
77574
  // src/routes/github.ts
77207
77575
  var githubStateCookie = "opengeni_github_state";
77208
77576
  var githubBindingStateMaxAgeSeconds = 10 * 60;
@@ -78006,28 +78374,6 @@ function requirePublicGitHubRepositoryVerificationPermission(grant) {
78006
78374
  function isSecureRequest(c, deps) {
78007
78375
  return deps.settings.publicBaseUrl?.startsWith("https://") || c.req.header("x-forwarded-proto") === "https" || new URL(c.req.url).protocol === "https:";
78008
78376
  }
78009
- function githubSuccessHtml(envLines) {
78010
- const envText = envLines.join("\n");
78011
- const escaped = escapeHtml4(envText);
78012
- return `<!doctype html><html><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>GitHub App Created</title><style>body{font-family:system-ui,sans-serif;margin:0;min-height:100vh;display:grid;place-items:center;background:#0b0b0d;color:#f4f4f5}main{width:min(760px,calc(100vw - 32px));border:1px solid #27272a;border-radius:8px;padding:28px;background:#111114}h1{margin:0 0 10px;font-size:24px;line-height:1.2}p{margin:0 0 18px;color:#d4d4d8}.env-header{display:flex;align-items:center;justify-content:space-between;gap:12px;margin:22px 0 8px}.env-header h2{margin:0;font-size:13px;line-height:1.2;text-transform:uppercase;letter-spacing:.08em;color:#a1a1aa}pre{white-space:pre-wrap;word-break:break-word;max-height:380px;overflow:auto;background:#09090b;border:1px solid #27272a;border-radius:8px;padding:16px;font-size:13px;line-height:1.5}button{display:inline-flex;align-items:center;justify-content:center;min-height:36px;border-radius:6px;border:1px solid #3f3f46;padding:0 12px;background:#f4f4f5;color:#09090b;font:600 14px system-ui,sans-serif;cursor:pointer}button:disabled{cursor:not-allowed;opacity:.7}</style></head><body><main><h1>GitHub App created</h1><p>Add these values to .env, then restart API and worker.</p><div class="env-header"><h2>Environment variables</h2><button id="copy-env" type="button">Copy env</button></div><pre id="env-lines">${escaped}</pre><script>(()=>{const button=document.getElementById("copy-env");const env=document.getElementById("env-lines");async function copyText(text){if(navigator.clipboard&&window.isSecureContext){await navigator.clipboard.writeText(text);return;}const area=document.createElement("textarea");area.value=text;area.setAttribute("readonly","");area.style.position="fixed";area.style.inset="-9999px";document.body.append(area);area.select();document.execCommand("copy");area.remove();}button?.addEventListener("click",async()=>{try{await copyText(env?.textContent||"");button.textContent="Copied";setTimeout(()=>button.textContent="Copy env",1600);}catch{button.textContent="Copy failed";setTimeout(()=>button.textContent="Copy env",2200);}});})();</script></main></body></html>`;
78013
- }
78014
- function githubSetupSuccessHtml(account, returnUrl2) {
78015
- return `<!doctype html><html><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>GitHub App Connected</title><style>body{font-family:system-ui,sans-serif;margin:0;min-height:100vh;display:grid;place-items:center;background:#0b0b0d;color:#f4f4f5}main{width:min(640px,calc(100vw - 32px));border:1px solid #27272a;border-radius:8px;padding:28px;background:#111114}h1{margin:0 0 10px;font-size:24px;line-height:1.2}p{margin:0 0 18px;color:#d4d4d8}.button{display:inline-flex;align-items:center;justify-content:center;min-height:36px;border-radius:6px;border:1px solid #3f3f46;padding:0 12px;background:#f4f4f5;color:#09090b;font:600 14px system-ui,sans-serif;text-decoration:none}</style></head><body><main><h1>GitHub App connected</h1><p>${escapeHtml4(account)} is now available to this OpenGeni workspace through an explicit repository allowlist.</p><a class="button" href="${escapeHtml4(returnUrl2)}">Back to OpenGeni</a></main></body></html>`;
78016
- }
78017
- function githubInstallationChooserHtml(candidates, state, workspaceId, baseUrl) {
78018
- const action = `${baseUrl}/v1/workspaces/${encodeURIComponent(workspaceId)}/github/installations/select`;
78019
- const options = candidates.map(({ installation, authorityKind }, index) => {
78020
- const account = escapeHtml4(
78021
- installation.accountLogin ?? `installation ${installation.installationId}`
78022
- );
78023
- const label = authorityKind === "personal_owner" ? "Personal account" : "Organization owner";
78024
- return `<label class="option"><input type="radio" name="installation_id" value="${installation.installationId}" required${candidates.length === 1 && index === 0 ? " checked" : ""}><span><strong>${account}</strong><small>${label}</small></span></label>`;
78025
- }).join("");
78026
- return `<!doctype html><html><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>Choose GitHub installation</title><style>body{font-family:system-ui,sans-serif;margin:0;min-height:100vh;display:grid;place-items:center;background:#0b0b0d;color:#f4f4f5}main{width:min(640px,calc(100vw - 32px));border:1px solid #27272a;border-radius:12px;padding:28px;background:#111114}h1{margin:0 0 10px;font-size:24px}p{margin:0 0 18px;color:#d4d4d8}.options{display:grid;gap:8px;margin-bottom:18px}.option{display:flex;align-items:center;gap:12px;border:1px solid #3f3f46;border-radius:8px;padding:12px;cursor:pointer}.option span{display:grid;gap:2px}.option small{color:#a1a1aa}button{min-height:38px;border-radius:7px;border:1px solid #3f3f46;padding:0 14px;background:#f4f4f5;color:#09090b;font:600 14px system-ui,sans-serif;cursor:pointer}.secondary{margin-left:8px;background:transparent;color:#f4f4f5}</style></head><body><main><h1>Choose a GitHub account</h1><p>These accounts already have the OpenGeni GitHub App installed and GitHub proved you are the personal owner or an active organization owner.</p><form method="get" action="${escapeHtml4(action)}"><input type="hidden" name="state" value="${escapeHtml4(state)}"><div class="options">${options}</div><button type="submit">Connect selected</button><button class="secondary" type="submit" name="installation_id" value="new" formnovalidate>Install on another account</button></form></main></body></html>`;
78027
- }
78028
- function githubSetupPendingHtml() {
78029
- return `<!doctype html><html><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>GitHub App Requested</title><style>body{font-family:system-ui,sans-serif;margin:0;min-height:100vh;display:grid;place-items:center;background:#0b0b0d;color:#f4f4f5}main{width:min(640px,calc(100vw - 32px));border:1px solid #27272a;border-radius:8px;padding:28px;background:#111114}h1{margin:0 0 10px;font-size:24px;line-height:1.2}p{margin:0;color:#d4d4d8}</style></head><body><main><h1>GitHub App request sent</h1><p>A GitHub organization owner must approve the installation. OpenGeni has not created a workspace binding.</p></main></body></html>`;
78030
- }
78031
78377
  function parsePositiveInteger(value) {
78032
78378
  if (!value || !/^\d+$/.test(value)) {
78033
78379
  return null;
@@ -78039,18 +78385,6 @@ function isFreshGitHubBindingState(payload) {
78039
78385
  const age = Math.floor(Date.now() / 1e3) - payload.iat;
78040
78386
  return age >= 0 && age < githubBindingStateMaxAgeSeconds;
78041
78387
  }
78042
- function escapeHtml4(value) {
78043
- return value.replace(
78044
- /[&<>"']/g,
78045
- (char) => ({
78046
- "&": "&amp;",
78047
- "<": "&lt;",
78048
- ">": "&gt;",
78049
- '"': "&quot;",
78050
- "'": "&#39;"
78051
- })[char] ?? char
78052
- );
78053
- }
78054
78388
  function openGeniReturnUrl(settings, c, workspaceId, returnPath = null) {
78055
78389
  const baseUrl = openGeniBaseUrl(settings, c) || new URL(c.req.url).origin;
78056
78390
  const safeReturnPath4 = githubSessionReturnPath(returnPath, workspaceId);
@@ -78636,7 +78970,7 @@ function registerPersonalGitHubRoutes(app, deps) {
78636
78970
  const query = ListPersonalGitHubRepositoriesQuery.parse(c.req.query());
78637
78971
  try {
78638
78972
  const selection = await loadRepositorySelectionState(deps, access.grant, connection);
78639
- const page = await listLivePersonalGitHubRepositories(deps, {
78973
+ const page2 = await listLivePersonalGitHubRepositories(deps, {
78640
78974
  accountId: access.grant.accountId,
78641
78975
  workspaceId,
78642
78976
  subjectId: access.grant.subjectId,
@@ -78653,11 +78987,11 @@ function registerPersonalGitHubRoutes(app, deps) {
78653
78987
  );
78654
78988
  return c.json(
78655
78989
  ListPersonalGitHubRepositoriesResponse.parse({
78656
- repositories: page.repositories.map((repository) => ({
78990
+ repositories: page2.repositories.map((repository) => ({
78657
78991
  ...repository,
78658
78992
  selectedAccess: selectedAccessById.get(repository.repositoryId) ?? null
78659
78993
  })),
78660
- nextCursor: page.nextPage,
78994
+ nextCursor: page2.nextPage,
78661
78995
  selection
78662
78996
  })
78663
78997
  );
@@ -79991,11 +80325,11 @@ async function sseSessionStream(db, bus, workspaceId, sessionId, after, signal,
79991
80325
  if (targetSequence !== void 0 && lastSent >= targetSequence) return;
79992
80326
  const previousLastSent = lastSent;
79993
80327
  const limit = targetSequence === void 0 ? SESSION_REPLAY_PAGE_SIZE : Math.min(SESSION_REPLAY_PAGE_SIZE, targetSequence - lastSent);
79994
- const page = await listSessionEvents(db, workspaceId, sessionId, {
80328
+ const page2 = await listSessionEvents(db, workspaceId, sessionId, {
79995
80329
  after: lastSent,
79996
80330
  limit
79997
80331
  });
79998
- const eligible = targetSequence === void 0 ? page : page.filter((event) => event.sequence <= targetSequence);
80332
+ const eligible = targetSequence === void 0 ? page2 : page2.filter((event) => event.sequence <= targetSequence);
79999
80333
  if (eligible.length === 0) {
80000
80334
  if (targetSequence === void 0) return;
80001
80335
  throw new Error(
@@ -80105,16 +80439,16 @@ async function replaySessionEvents(loadPage, send, after, pageSize = SESSION_REP
80105
80439
  let cursor = after;
80106
80440
  while (true) {
80107
80441
  const previousCursor = cursor;
80108
- const page = await loadPage(cursor, pageSize);
80109
- if (page.length === 0) {
80442
+ const page2 = await loadPage(cursor, pageSize);
80443
+ if (page2.length === 0) {
80110
80444
  return;
80111
80445
  }
80112
- for (const event of page.sort((a, b) => a.sequence - b.sequence)) {
80446
+ for (const event of page2.sort((a, b) => a.sequence - b.sequence)) {
80113
80447
  if (event.sequence <= cursor) continue;
80114
80448
  await send(event);
80115
80449
  cursor = event.sequence;
80116
80450
  }
80117
- if (page.length < pageSize) {
80451
+ if (page2.length < pageSize) {
80118
80452
  return;
80119
80453
  }
80120
80454
  if (cursor === previousCursor) {
@@ -80506,14 +80840,14 @@ async function replayWorkspaceControlEvents(loadPage, send, after, pageSize) {
80506
80840
  let cursor = after;
80507
80841
  while (true) {
80508
80842
  const previousCursor = cursor;
80509
- const page = await loadPage(cursor, pageSize);
80510
- if (page.length === 0) return;
80511
- for (const event of page.sort((a, b) => a.sequence - b.sequence)) {
80843
+ const page2 = await loadPage(cursor, pageSize);
80844
+ if (page2.length === 0) return;
80845
+ for (const event of page2.sort((a, b) => a.sequence - b.sequence)) {
80512
80846
  if (event.sequence <= cursor) continue;
80513
80847
  await send(event);
80514
80848
  cursor = event.sequence;
80515
80849
  }
80516
- if (page.length < pageSize) return;
80850
+ if (page2.length < pageSize) return;
80517
80851
  if (cursor === previousCursor) {
80518
80852
  throw new Error(
80519
80853
  `Workspace control replay made no progress after sequence ${cursor}; refusing to repeat a full stale page`
@@ -84605,10 +84939,10 @@ function registerSkillContentRoutes(app, deps) {
84605
84939
  ...query.data.sessionId ? { sessionId: query.data.sessionId } : {}
84606
84940
  }
84607
84941
  );
84608
- const page = rows2.slice(0, query.data.limit);
84609
- const last = page.at(-1);
84942
+ const page2 = rows2.slice(0, query.data.limit);
84943
+ const last = page2.at(-1);
84610
84944
  return c.json({
84611
- skills: page.map(({ files: _files, ...summary }) => summary),
84945
+ skills: page2.map(({ files: _files, ...summary }) => summary),
84612
84946
  nextCursor: rows2.length > query.data.limit && last ? Buffer.from(JSON.stringify({ stableKey: last.stableKey, id: last.id })).toString(
84613
84947
  "base64url"
84614
84948
  ) : null
@@ -85665,6 +85999,8 @@ function registerScheduledTaskRoutes(app, deps) {
85665
85999
  // src/routes/sessions.ts
85666
86000
  import { getRetainedProviderCommand } from "@opengeni/db/retained-provider-commands";
85667
86001
  import { searchSessionMessagesForSubject, SessionMessageSearchCursorError } from "@opengeni/db";
86002
+ import { listSessionEventSlices } from "@opengeni/db/session-event-slices";
86003
+ import * as sessionPreviewSchema from "@opengeni/db/schema";
85668
86004
  import { SessionMessageSearchRequest } from "@opengeni/contracts";
85669
86005
  import { scheduledSessionIds } from "@opengeni/db";
85670
86006
  import { withSiteSessionOrigin as withSiteSessionOrigin2 } from "@opengeni/core";
@@ -87260,9 +87596,9 @@ function registerSessionRoutes(app, deps) {
87260
87596
  }
87261
87597
  const pageView = c.req.query("view") === "page";
87262
87598
  const query = sessionListQuery(c.req.query(), pageView);
87263
- let page;
87599
+ let page2;
87264
87600
  try {
87265
- page = await listSessionsForSubject(db, workspaceId, {
87601
+ page2 = await listSessionsForSubject(db, workspaceId, {
87266
87602
  subjectId: grant.subjectId,
87267
87603
  limit: boundedLimit(query.limit),
87268
87604
  materializeSnapshot: pageView,
@@ -87299,17 +87635,17 @@ function registerSessionRoutes(app, deps) {
87299
87635
  }
87300
87636
  throw error;
87301
87637
  }
87302
- c.header("x-opengeni-pinned-truncated", page.pinnedTruncated === true ? "true" : "false");
87638
+ c.header("x-opengeni-pinned-truncated", page2.pinnedTruncated === true ? "true" : "false");
87303
87639
  const policy = await loadEffectivePolicyContext(deps, workspaceId, grant.subjectId);
87304
87640
  const commandActivity = await backgroundCommandActivityForSessions(db, {
87305
87641
  accountId: grant.accountId,
87306
87642
  workspaceId,
87307
- sessionIds: [...page.pinned, ...page.sessions].map((session) => session.id)
87643
+ sessionIds: [...page2.pinned, ...page2.sessions].map((session) => session.id)
87308
87644
  });
87309
87645
  const scheduleTargets = hasPermission19(grant.permissions, "scheduled_tasks:run") && hasPermission19(grant.permissions, "sessions:control") ? await scheduledSessionIds(
87310
87646
  db,
87311
87647
  workspaceId,
87312
- [...page.pinned, ...page.sessions].map((session) => session.id)
87648
+ [...page2.pinned, ...page2.sessions].map((session) => session.id)
87313
87649
  ) : /* @__PURE__ */ new Set();
87314
87650
  const decorate = (session) => {
87315
87651
  const activity = commandActivity.get(session.id);
@@ -87325,14 +87661,14 @@ function registerSessionRoutes(app, deps) {
87325
87661
  };
87326
87662
  if (pageView) {
87327
87663
  return c.json({
87328
- ...page,
87664
+ ...page2,
87329
87665
  ...query.hasPageFilters ? { filtersApplied: true } : {},
87330
87666
  ...query.originSiteId ? { originSiteId: query.originSiteId } : {},
87331
- pinned: page.pinned.map(decorate),
87332
- sessions: page.sessions.map(decorate)
87667
+ pinned: page2.pinned.map(decorate),
87668
+ sessions: page2.sessions.map(decorate)
87333
87669
  });
87334
87670
  }
87335
- return c.json([...page.pinned, ...page.sessions].map(decorate));
87671
+ return c.json([...page2.pinned, ...page2.sessions].map(decorate));
87336
87672
  });
87337
87673
  app.get("/v1/workspaces/:workspaceId/agent-topology", async (c) => {
87338
87674
  const workspaceId = c.req.param("workspaceId");
@@ -87365,7 +87701,7 @@ function registerSessionRoutes(app, deps) {
87365
87701
  }
87366
87702
  try {
87367
87703
  const orderBy = query.query || query.subject ? "relevance" : "updatedAt";
87368
- const page = await listSessionDiscoverySummaries2(db, workspaceId, {
87704
+ const page2 = await listSessionDiscoverySummaries2(db, workspaceId, {
87369
87705
  limit: query.limit,
87370
87706
  orderBy,
87371
87707
  subjectId: grant.subjectId,
@@ -87384,11 +87720,11 @@ function registerSessionRoutes(app, deps) {
87384
87720
  const ancestorPaths = query.query || query.subject ? await listSessionDiscoveryAncestorPaths(
87385
87721
  db,
87386
87722
  workspaceId,
87387
- page.sessions.map((session) => session.id),
87723
+ page2.sessions.map((session) => session.id),
87388
87724
  authorizationScope ?? void 0,
87389
87725
  grant.subjectId
87390
87726
  ) : /* @__PURE__ */ new Map();
87391
- const sessions = page.sessions.map((session) => {
87727
+ const sessions = page2.sessions.map((session) => {
87392
87728
  const blocker = session.effectiveControl.primaryBlocker;
87393
87729
  return {
87394
87730
  id: session.id,
@@ -87426,11 +87762,11 @@ function registerSessionRoutes(app, deps) {
87426
87762
  });
87427
87763
  const response = {
87428
87764
  sessions,
87429
- total: page.total,
87430
- hasMore: page.hasMore,
87765
+ total: page2.total,
87766
+ hasMore: page2.hasMore,
87431
87767
  humanAdvisoriesEnabled: settings.workDiscoveryEnabled && settings.workDiscoveryHumanAdvisoriesEnabled,
87432
- nextCursor: page.nextCursor ? encodeAgentTopologyCursor({
87433
- cursor: page.nextCursor,
87768
+ nextCursor: page2.nextCursor ? encodeAgentTopologyCursor({
87769
+ cursor: page2.nextCursor,
87434
87770
  parentSessionId: query.parentSessionId === void 0 ? "all" : query.parentSessionId,
87435
87771
  rootSessionId: query.rootSessionId ?? null,
87436
87772
  query: query.query ?? null,
@@ -89211,35 +89547,94 @@ function registerSessionRoutes(app, deps) {
89211
89547
  const compactProjection = compact ? coalesceSessionEventDeltasWithCoverage2(events) : null;
89212
89548
  const projected = compactProjection?.events ?? events;
89213
89549
  const forensicExact = !compact && mode === "forensic" && payloadMode === "full" && dbPage.fullPayloadsExact;
89214
- const page = boundSessionEventHttpPage(projected, {
89550
+ const page2 = boundSessionEventHttpPage(projected, {
89215
89551
  direction,
89216
89552
  eventProjection: payloadMode === "full" ? "exact" : "bounded",
89217
89553
  ...compactProjection ? { coveredThroughBySequence: compactProjection.coveredThroughBySequence } : {}
89218
89554
  });
89219
- const hasMore = dbPage.hasMore || page.truncated;
89220
- c.header("X-OpenGeni-Page-Bytes", String(page.bytes));
89221
- c.header("X-OpenGeni-Page-Max-Bytes", String(Math.max(1024 * 1024, page.bytes)));
89555
+ const hasMore = dbPage.hasMore || page2.truncated;
89556
+ c.header("X-OpenGeni-Page-Bytes", String(page2.bytes));
89557
+ c.header("X-OpenGeni-Page-Max-Bytes", String(Math.max(1024 * 1024, page2.bytes)));
89222
89558
  c.header("X-OpenGeni-Page-Truncated", String(hasMore));
89223
89559
  c.header("X-OpenGeni-Has-More", String(hasMore));
89224
89560
  c.header("X-OpenGeni-Event-Mode", mode);
89225
89561
  c.header("X-OpenGeni-Event-Direction", direction);
89226
89562
  c.header("X-OpenGeni-Payload-Mode", payloadMode);
89227
89563
  c.header("X-OpenGeni-Forensic-Exact", String(forensicExact));
89228
- const coveredFirst = page.events[0]?.sequence;
89229
- const coveredLastEvent = page.events.at(-1);
89564
+ const coveredFirst = page2.events[0]?.sequence;
89565
+ const coveredLastEvent = page2.events.at(-1);
89230
89566
  const coveredLast = coveredLastEvent === void 0 ? void 0 : compactProjection?.coveredThroughBySequence.get(coveredLastEvent.sequence) ?? coveredLastEvent.sequence;
89231
89567
  if (coveredFirst !== void 0) c.header("X-OpenGeni-Covered-First", String(coveredFirst));
89232
89568
  if (coveredLast !== void 0) c.header("X-OpenGeni-Covered-Last", String(coveredLast));
89233
- const truncatedBy = page.truncated ? "http_bytes" : dbPage.truncatedBy;
89569
+ const truncatedBy = page2.truncated ? "http_bytes" : dbPage.truncatedBy;
89234
89570
  if (truncatedBy) c.header("X-OpenGeni-Truncated-By", truncatedBy);
89235
- if (page.nextSequence !== null) {
89571
+ if (page2.nextSequence !== null) {
89236
89572
  c.header(
89237
89573
  direction === "before" ? "X-OpenGeni-Next-Before" : "X-OpenGeni-Next-After",
89238
- String(page.nextSequence)
89574
+ String(page2.nextSequence)
89239
89575
  );
89240
89576
  }
89241
- return c.json(page.events);
89577
+ return c.json(page2.events);
89242
89578
  });
89579
+ app.get(
89580
+ "/v1/workspaces/:workspaceId/sessions/:sessionId/events/:eventId/message-preview",
89581
+ async (c) => {
89582
+ const workspaceId = c.req.param("workspaceId");
89583
+ await requireAccessGrant32(c, deps, workspaceId, "sessions:read");
89584
+ const sessionId = c.req.param("sessionId");
89585
+ await assertSessionExists(db, workspaceId, sessionId);
89586
+ const eventId = c.req.param("eventId");
89587
+ const rawSequence = c.req.query("sequence");
89588
+ const sequence = Number(rawSequence);
89589
+ if (!z27.string().uuid().safeParse(eventId).success || rawSequence === void 0 || !/^\d+$/.test(rawSequence) || !Number.isSafeInteger(sequence) || sequence < 1 || sequence > 2147483647)
89590
+ throw new HTTPException72(400, { message: "Invalid message preview reference" });
89591
+ const notFound = () => new HTTPException72(404, { message: "message not found" });
89592
+ const maxUnits = 12e3;
89593
+ const eventTable = sessionPreviewSchema.sessionEvents;
89594
+ const candidates = await withWorkspaceRls(
89595
+ db,
89596
+ workspaceId,
89597
+ (tx) => tx.select({ kind: sql`jsonb_typeof(${eventTable.payload}->'text')` }).from(eventTable).where(
89598
+ and(
89599
+ eq(eventTable.workspaceId, workspaceId),
89600
+ eq(eventTable.sessionId, sessionId),
89601
+ eq(eventTable.id, eventId),
89602
+ eq(eventTable.sequence, sequence),
89603
+ sql`${eventTable.type} in ('user.message', 'agent.message.completed')`
89604
+ )
89605
+ ).limit(1)
89606
+ );
89607
+ if (candidates[0]?.kind !== "string") throw notFound();
89608
+ const read = (offset2) => listSessionEventSlices(db, workspaceId, sessionId, {
89609
+ sourceSequence: sequence,
89610
+ sourceOffset: offset2,
89611
+ after: sequence - 1,
89612
+ before: sequence + 1,
89613
+ includeTypes: ["user.message", "agent.message.completed"],
89614
+ view: "conversation"
89615
+ });
89616
+ const first = await read(0);
89617
+ const event = first.events[0];
89618
+ const slice = first.slices?.[sequence];
89619
+ if (!event || event.id !== eventId || event.sequence !== sequence || !slice || slice.omitted)
89620
+ throw notFound();
89621
+ if (slice.total > maxUnits) return c.json({ status: "unavailable" });
89622
+ let text = slice.text;
89623
+ let offset = slice.unit === "utf16" ? slice.text.length : Array.from(slice.text).length;
89624
+ if (text.length > maxUnits) return c.json({ status: "unavailable" });
89625
+ while (offset < slice.total) {
89626
+ if (offset === 0) throw notFound();
89627
+ const next = await read(offset);
89628
+ const part = next.slices?.[sequence];
89629
+ if (next.events[0]?.id !== eventId || !part || part.omitted || part.unit !== slice.unit || part.total !== slice.total || part.offset !== offset)
89630
+ throw notFound();
89631
+ text += part.text;
89632
+ offset += slice.unit === "utf16" ? part.text.length : Array.from(part.text).length;
89633
+ if (text.length > maxUnits) return c.json({ status: "unavailable" });
89634
+ }
89635
+ return c.json({ status: "available", text });
89636
+ }
89637
+ );
89243
89638
  app.get("/v1/workspaces/:workspaceId/sessions/:sessionId/events/stream", async (c) => {
89244
89639
  const workspaceId = c.req.param("workspaceId");
89245
89640
  const grant = await requireAccessGrant32(c, deps, workspaceId, "sessions:read");
@@ -90807,6 +91202,8 @@ function sessionAuthorizationOperationForHttp(method, pathname, sessionId) {
90807
91202
  return verb === "POST" ? "session.context.write" : null;
90808
91203
  }
90809
91204
  if (suffix === "/events/stream" && verb === "GET") return "session.stream.read";
91205
+ if (/^\/events\/[^/]+\/message-preview$/.test(suffix) && verb === "GET")
91206
+ return "session.events.read";
90810
91207
  if (suffix === "/events") {
90811
91208
  if (verb === "GET") return "session.events.read";
90812
91209
  if (verb === "POST") return "session.append";
@@ -92641,14 +93038,14 @@ function registerWorkspaceRoutes(app, deps) {
92641
93038
  const limit = boundedLimit(c.req.query("limit"));
92642
93039
  const fetched = await listWorkspaceControlEvents2(deps.db, workspaceId, after, limit + 1);
92643
93040
  const countHasMore = fetched.length > limit;
92644
- const page = boundWorkspaceControlHttpPage(fetched.slice(0, limit));
92645
- const truncated = countHasMore || page.truncated;
92646
- c.header("X-OpenGeni-Page-Bytes", String(page.bytes));
93041
+ const page2 = boundWorkspaceControlHttpPage(fetched.slice(0, limit));
93042
+ const truncated = countHasMore || page2.truncated;
93043
+ c.header("X-OpenGeni-Page-Bytes", String(page2.bytes));
92647
93044
  c.header("X-OpenGeni-Page-Truncated", String(truncated));
92648
- if (page.nextSequence !== null) {
92649
- c.header("X-OpenGeni-Next-After", String(page.nextSequence));
93045
+ if (page2.nextSequence !== null) {
93046
+ c.header("X-OpenGeni-Next-After", String(page2.nextSequence));
92650
93047
  }
92651
- return c.json(page.events);
93048
+ return c.json(page2.events);
92652
93049
  });
92653
93050
  app.get("/v1/workspaces/:workspaceId/control-events/stream", async (c) => {
92654
93051
  const workspaceId = c.req.param("workspaceId");
@@ -93800,10 +94197,10 @@ function preferenceLifecycle(row, generatedAt) {
93800
94197
  if (row.status === "superseded") return "superseded";
93801
94198
  return "historical";
93802
94199
  }
93803
- function preferenceEntries(page, generatedAt) {
93804
- const visibleRevisionIds = new Set(page.rows.map((row) => row.revisionId));
93805
- const visiblePreferenceIds = new Set(page.rows.map((row) => row.preferenceId));
93806
- return page.rows.map((row) => {
94200
+ function preferenceEntries(page2, generatedAt) {
94201
+ const visibleRevisionIds = new Set(page2.rows.map((row) => row.revisionId));
94202
+ const visiblePreferenceIds = new Set(page2.rows.map((row) => row.preferenceId));
94203
+ return page2.rows.map((row) => {
93807
94204
  const scope = row.scope === "organization" ? "organization" : row.scope === "user" ? "personal" : "workspace";
93808
94205
  const lifecycle2 = preferenceLifecycle(row, generatedAt);
93809
94206
  const relationships = [];
@@ -95367,6 +95764,7 @@ import {
95367
95764
  requireAccessGrant as requireAccessGrant41,
95368
95765
  WorkspaceInsightsFilterValidationError
95369
95766
  } from "@opengeni/core";
95767
+ import { currentSessionRlsActorIdentityKey } from "@opengeni/db";
95370
95768
  import { workspaceInsightsMetricObserver } from "@opengeni/observability";
95371
95769
  import { HTTPException as HTTPException85 } from "hono/http-exception";
95372
95770
  function normalizeWorkspaceInsightsQueryFilter(value, field) {
@@ -95379,9 +95777,36 @@ function normalizeWorkspaceInsightsQueryFilter(value, field) {
95379
95777
  throw error;
95380
95778
  }
95381
95779
  }
95780
+ function createInFlightCoalescer() {
95781
+ const inFlight = /* @__PURE__ */ new Map();
95782
+ return {
95783
+ run(key, work) {
95784
+ const pending = inFlight.get(key);
95785
+ if (pending) return pending;
95786
+ const started2 = work().finally(() => {
95787
+ if (inFlight.get(key) === started2) inFlight.delete(key);
95788
+ });
95789
+ inFlight.set(key, started2);
95790
+ return started2;
95791
+ },
95792
+ get size() {
95793
+ return inFlight.size;
95794
+ }
95795
+ };
95796
+ }
95797
+ function workspaceInsightsCoalesceKey(input) {
95798
+ return JSON.stringify([
95799
+ input.workspaceId,
95800
+ input.range,
95801
+ input.provider,
95802
+ input.model,
95803
+ input.rlsActor
95804
+ ]);
95805
+ }
95382
95806
  function registerInsightsRoutes(app, deps) {
95383
95807
  const observeRequest = workspaceInsightsMetricObserver(deps.observability);
95384
95808
  const observePhase = workspaceInsightsPhaseMetricObserver(deps.observability);
95809
+ const coalesce = createInFlightCoalescer();
95385
95810
  app.get("/v1/workspaces/:workspaceId/insights", async (c) => {
95386
95811
  const startedAtMs = performance.now();
95387
95812
  const rangeRaw = c.req.query("range") ?? "week";
@@ -95405,16 +95830,25 @@ function registerInsightsRoutes(app, deps) {
95405
95830
  }
95406
95831
  provider = normalizeWorkspaceInsightsQueryFilter(providerRaw, "provider");
95407
95832
  model = normalizeWorkspaceInsightsQueryFilter(modelRaw, "model");
95408
- const response = await getWorkspaceInsights(
95409
- deps.db,
95410
- deps.settings,
95411
- {
95833
+ const response = await coalesce.run(
95834
+ workspaceInsightsCoalesceKey({
95412
95835
  workspaceId,
95413
95836
  range: rangeParsed.data,
95414
95837
  provider,
95415
- model
95416
- },
95417
- observePhase
95838
+ model,
95839
+ rlsActor: currentSessionRlsActorIdentityKey()
95840
+ }),
95841
+ () => getWorkspaceInsights(
95842
+ deps.db,
95843
+ deps.settings,
95844
+ {
95845
+ workspaceId,
95846
+ range: rangeParsed.data,
95847
+ provider,
95848
+ model
95849
+ },
95850
+ observePhase
95851
+ )
95418
95852
  );
95419
95853
  c.header("cache-control", "private, no-store");
95420
95854
  const result = c.json(WorkspaceInsightsResponse.parse(response));
@@ -98379,7 +98813,8 @@ function registerOrganizationMembershipRoutes(app, deps) {
98379
98813
  await createManagedOrganization(deps.db, {
98380
98814
  subjectId,
98381
98815
  subjectLabel: session.user.email || session.user.name,
98382
- ...payload
98816
+ ...payload,
98817
+ trialCreditsEnabled: deps.settings.productAccessMode === "managed" && deps.settings.verifiedSignupTrialCreditsEnabled
98383
98818
  })
98384
98819
  ),
98385
98820
  201
@@ -99155,7 +99590,7 @@ function registerOrganizationSessionRoutes(app, deps) {
99155
99590
  while (index < workspaces.length && sessions.length < query.limit && reads < ORGANIZATION_SESSION_LIST_MAX_WORKSPACE_READS) {
99156
99591
  const workspace = workspaces[index];
99157
99592
  reads += 1;
99158
- const page = await listWorkspaceSessionPage(c, deps, {
99593
+ const page2 = await listWorkspaceSessionPage(c, deps, {
99159
99594
  workspace,
99160
99595
  readAuthority,
99161
99596
  innerCursor,
@@ -99163,21 +99598,21 @@ function registerOrganizationSessionRoutes(app, deps) {
99163
99598
  limit: query.limit - sessions.length,
99164
99599
  query
99165
99600
  });
99166
- if (page === null) {
99601
+ if (page2 === null) {
99167
99602
  index += 1;
99168
99603
  innerCursor = null;
99169
99604
  pinnedOffset = 0;
99170
99605
  continue;
99171
99606
  }
99172
- sessions.push(...page.sessions);
99173
- if (page.nextPinnedOffset !== null) {
99607
+ sessions.push(...page2.sessions);
99608
+ if (page2.nextPinnedOffset !== null) {
99174
99609
  innerCursor = null;
99175
- pinnedOffset = page.nextPinnedOffset;
99610
+ pinnedOffset = page2.nextPinnedOffset;
99176
99611
  break;
99177
99612
  }
99178
99613
  pinnedOffset = 0;
99179
- if (page.nextCursor) {
99180
- innerCursor = page.nextCursor;
99614
+ if (page2.nextCursor) {
99615
+ innerCursor = page2.nextCursor;
99181
99616
  } else {
99182
99617
  index += 1;
99183
99618
  innerCursor = null;
@@ -99232,24 +99667,24 @@ async function listWorkspaceSessionPage(c, deps, input) {
99232
99667
  personalWorkspaceOwnerException: authorization.canonicalManagedHumanSession
99233
99668
  });
99234
99669
  const withStatus = (rows3) => input.query.status ? rows3.filter((session) => session.status === input.query.status) : rows3;
99235
- let page = await list(input.limit);
99670
+ let page2 = await list(input.limit);
99236
99671
  let rows2;
99237
99672
  let nextPinnedOffset = null;
99238
99673
  if (cursor) {
99239
- rows2 = page.sessions;
99674
+ rows2 = page2.sessions;
99240
99675
  } else {
99241
- const pinned = page.pinned.slice(input.pinnedOffset);
99676
+ const pinned = page2.pinned.slice(input.pinnedOffset);
99242
99677
  if (pinned.length >= input.limit) {
99243
99678
  rows2 = pinned.slice(0, input.limit);
99244
99679
  nextPinnedOffset = input.pinnedOffset + input.limit;
99245
99680
  return { sessions: withStatus(rows2), nextCursor: null, nextPinnedOffset };
99246
99681
  }
99247
- if (pinned.length > 0 && page.sessions.length > input.limit - pinned.length) {
99248
- page = await list(input.limit - pinned.length);
99682
+ if (pinned.length > 0 && page2.sessions.length > input.limit - pinned.length) {
99683
+ page2 = await list(input.limit - pinned.length);
99249
99684
  }
99250
- rows2 = [...pinned, ...page.sessions];
99685
+ rows2 = [...pinned, ...page2.sessions];
99251
99686
  }
99252
- return { sessions: withStatus(rows2), nextCursor: page.nextCursor, nextPinnedOffset };
99687
+ return { sessions: withStatus(rows2), nextCursor: page2.nextCursor, nextPinnedOffset };
99253
99688
  } catch (error) {
99254
99689
  if (error instanceof SessionListAccessError2) {
99255
99690
  throw new HTTPException91(403, { message: error.message });
@@ -99591,7 +100026,8 @@ function registerManagedOnboardingRoutes(app, deps, options = {}) {
99591
100026
  actorSubjectId: `user:${session.user.id}`,
99592
100027
  organizationName,
99593
100028
  operationId: parsed.data.operationId,
99594
- requestFingerprint
100029
+ requestFingerprint,
100030
+ trialCreditsEnabled: deps.settings.productAccessMode === "managed" && deps.settings.verifiedSignupTrialCreditsEnabled
99595
100031
  })
99596
100032
  )
99597
100033
  );
@@ -99843,7 +100279,7 @@ function registerUserResourceAuthorityRoutes(app, deps) {
99843
100279
  "workspace:read"
99844
100280
  );
99845
100281
  try {
99846
- const page = await listManagedHumanUserResourceAuthorities(
100282
+ const page2 = await listManagedHumanUserResourceAuthorities(
99847
100283
  deps,
99848
100284
  access,
99849
100285
  workspaceId,
@@ -99852,7 +100288,7 @@ function registerUserResourceAuthorityRoutes(app, deps) {
99852
100288
  return context.json(
99853
100289
  ListUserResourceAuthoritiesResponse.parse({
99854
100290
  scope: "user",
99855
- ...page
100291
+ ...page2
99856
100292
  })
99857
100293
  );
99858
100294
  } catch (error) {
@@ -101847,16 +102283,16 @@ async function publishSlackAppHome(deps, installation, refresh, renewLease) {
101847
102283
  let cursor;
101848
102284
  do {
101849
102285
  await renewLease();
101850
- const page = await listSessionsForSubject3(deps.db, installation.workspaceId, {
102286
+ const page2 = await listSessionsForSubject3(deps.db, installation.workspaceId, {
101851
102287
  subjectId: grant.subjectId,
101852
102288
  limit: 500,
101853
102289
  ...cursor ? { cursor } : {},
101854
102290
  ...authorizationScope ? { authorizationScope } : {}
101855
102291
  });
101856
- if (!cursor) sessions.push(...page.pinned);
101857
- sessions.push(...page.sessions);
101858
- if (!page.nextCursor) break;
101859
- cursor = decodeSessionListCursor3(page.nextCursor) ?? void 0;
102292
+ if (!cursor) sessions.push(...page2.pinned);
102293
+ sessions.push(...page2.sessions);
102294
+ if (!page2.nextCursor) break;
102295
+ cursor = decodeSessionListCursor3(page2.nextCursor) ?? void 0;
101860
102296
  if (!cursor) throw new Error("Slack App Home session cursor was invalid");
101861
102297
  } while (cursor);
101862
102298
  } catch (error) {
@@ -104167,13 +104603,13 @@ async function resolveSlackDeliveryHome(deps, interaction) {
104167
104603
  }
104168
104604
  async function deliverSlackSessionEvents(deps, interaction, claimHolderId) {
104169
104605
  if (!interaction.sessionId) return;
104170
- const page = await listSessionEventPage3(deps.db, interaction.workspaceId, interaction.sessionId, {
104606
+ const page2 = await listSessionEventPage3(deps.db, interaction.workspaceId, interaction.sessionId, {
104171
104607
  after: interaction.lastDeliveredSessionEventSequence,
104172
104608
  limit: 100,
104173
104609
  includeTypes: [...SLACK_DELIVERY_EVENT_TYPES],
104174
104610
  maxBytes: 256 * 1024
104175
104611
  });
104176
- if (page.events.length === 0) {
104612
+ if (page2.events.length === 0) {
104177
104613
  await releaseSlackInteractionDelivery(deps.db, {
104178
104614
  ...interaction,
104179
104615
  claimHolderId
@@ -104212,10 +104648,10 @@ async function deliverSlackSessionEvents(deps, interaction, claimHolderId) {
104212
104648
  };
104213
104649
  let lastSequence = interaction.lastDeliveredSessionEventSequence;
104214
104650
  let terminal2 = null;
104215
- const orderedEvents = page.events.filter(
104651
+ const orderedEvents = page2.events.filter(
104216
104652
  (event) => (event.turnAssociation === null || event.turnAssociation === "current") && event.duplicateOfEventId === null
104217
104653
  ).sort((left, right) => left.sequence - right.sequence);
104218
- lastSequence = page.events.reduce(
104654
+ lastSequence = page2.events.reduce(
104219
104655
  (latest, event) => Math.max(latest, event.sequence),
104220
104656
  lastSequence
104221
104657
  );
@@ -107113,4 +107549,4 @@ export {
107113
107549
  withDefaultEnabledCapabilityMcpTools,
107114
107550
  workflowIdForSession4 as workflowIdForSession
107115
107551
  };
107116
- //# sourceMappingURL=chunk-24C5LPNE.js.map
107552
+ //# sourceMappingURL=chunk-2SSU5KWV.js.map