@opengeni/api-router 4.1.0 → 4.1.2-canary.36079201993001

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"] },
@@ -329,6 +331,10 @@ var FIRST_PARTY_TOOL_AUTHORIZATION = {
329
331
  sessionRequired: true,
330
332
  allOf: ["workspace:read"]
331
333
  },
334
+ custom_mcp_setup_request: {
335
+ sessionRequired: true,
336
+ allOf: ["workspace:read"]
337
+ },
332
338
  github_connect_link: { allOf: ["github:use"] },
333
339
  github_repositories_list: { allOf: ["github:use"] },
334
340
  social_connections_list: { allOf: ["connections:read"] },
@@ -4600,6 +4606,9 @@ var Name = class {
4600
4606
  return new SQL([this]);
4601
4607
  }
4602
4608
  };
4609
+ function isDriverValueEncoder(value) {
4610
+ return typeof value === "object" && value !== null && "mapToDriverValue" in value && typeof value.mapToDriverValue === "function";
4611
+ }
4603
4612
  var noopDecoder = {
4604
4613
  mapFromDriverValue: (value) => value
4605
4614
  };
@@ -4732,6 +4741,33 @@ Subquery.prototype.getSQL = function() {
4732
4741
  return new SQL([this]);
4733
4742
  };
4734
4743
 
4744
+ // ../../node_modules/.bun/drizzle-orm@0.45.2+1d2c014f134ec9f4/node_modules/drizzle-orm/sql/expressions/conditions.js
4745
+ function bindIfParam(value, column) {
4746
+ if (isDriverValueEncoder(column) && !isSQLWrapper(value) && !is(value, Param) && !is(value, Placeholder) && !is(value, Column) && !is(value, Table) && !is(value, View)) {
4747
+ return new Param(value, column);
4748
+ }
4749
+ return value;
4750
+ }
4751
+ var eq = (left, right) => {
4752
+ return sql`${left} = ${bindIfParam(right, left)}`;
4753
+ };
4754
+ function and(...unfilteredConditions) {
4755
+ const conditions = unfilteredConditions.filter(
4756
+ (c) => c !== void 0
4757
+ );
4758
+ if (conditions.length === 0) {
4759
+ return void 0;
4760
+ }
4761
+ if (conditions.length === 1) {
4762
+ return new SQL(conditions);
4763
+ }
4764
+ return new SQL([
4765
+ new StringChunk("("),
4766
+ sql.join(conditions, new StringChunk(" and ")),
4767
+ new StringChunk(")")
4768
+ ]);
4769
+ }
4770
+
4735
4771
  // src/auth/managed-auth.ts
4736
4772
  import { Pool } from "pg";
4737
4773
 
@@ -7598,7 +7634,14 @@ function registerKnowledgeEntryTools(server, deps, grant, sessionId) {
7598
7634
  inputSchema: KnowledgeSavePreparationRequest.shape
7599
7635
  },
7600
7636
  (input) => run(
7601
- (context) => prepareKnowledgeSave(deps.db, context, input, () => deps.getDocumentServices().embedder)
7637
+ (context) => prepareKnowledgeSave(
7638
+ deps.db,
7639
+ context,
7640
+ input,
7641
+ () => deps.getDocumentServices().embedder,
7642
+ void 0,
7643
+ deps.settings
7644
+ )
7602
7645
  )
7603
7646
  );
7604
7647
  server.registerTool(
@@ -7613,7 +7656,13 @@ function registerKnowledgeEntryTools(server, deps, grant, sessionId) {
7613
7656
  }).extend({ view: z8.enum(["published", "needs_review"]).default("published") }).shape
7614
7657
  },
7615
7658
  (input) => run(
7616
- (context) => searchKnowledgeEntries(deps.db, context, input, () => deps.getDocumentServices().embedder)
7659
+ (context) => searchKnowledgeEntries(
7660
+ deps.db,
7661
+ context,
7662
+ input,
7663
+ () => deps.getDocumentServices().embedder,
7664
+ deps.settings
7665
+ )
7617
7666
  )
7618
7667
  );
7619
7668
  server.registerTool(
@@ -7901,26 +7950,26 @@ async function listWorkspaceGitHubRepositoryBranches(deps, input, services = wor
7901
7950
  ])) {
7902
7951
  throw new GitHubRepositoryBranchAuthorityError("changed");
7903
7952
  }
7904
- const page = await services.listProviderBranches(deps, {
7953
+ const page2 = await services.listProviderBranches(deps, {
7905
7954
  installationId: input.installationId,
7906
7955
  repositoryId: input.repositoryId,
7907
7956
  page: input.query.cursor,
7908
7957
  limit: input.query.limit
7909
7958
  });
7910
- if (!page) {
7959
+ if (!page2) {
7911
7960
  throw new GitHubAppApiError(
7912
7961
  "The configured GitHub provider cannot list exact repository branches"
7913
7962
  );
7914
7963
  }
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) {
7964
+ 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
7965
  throw new GitHubAppApiError("GitHub returned an invalid repository branches page");
7917
7966
  }
7918
7967
  const response = GitHubRepositoryBranchesResponseSchema.safeParse({
7919
- branches: page.branches.map((name) => ({
7968
+ branches: page2.branches.map((name) => ({
7920
7969
  name,
7921
- isDefault: name === page.defaultBranch
7970
+ isDefault: name === page2.defaultBranch
7922
7971
  })),
7923
- nextCursor: page.nextPage
7972
+ nextCursor: page2.nextPage
7924
7973
  });
7925
7974
  if (!response.success) {
7926
7975
  throw new GitHubAppApiError("GitHub returned an invalid repository branches page");
@@ -8009,6 +8058,11 @@ import {
8009
8058
  } from "@opengeni/db";
8010
8059
  import { retryWhileMissing as retryWhileMissing4 } from "@opengeni/storage";
8011
8060
  import { HTTPException as HTTPException12 } from "hono/http-exception";
8061
+ import {
8062
+ isConnectedMachineAbsolutePath,
8063
+ relativeConnectedMachinePath,
8064
+ resolveConnectedMachinePath
8065
+ } from "@opengeni/runtime/sandbox";
8012
8066
 
8013
8067
  // src/sandbox/channel-a.ts
8014
8068
  import {
@@ -8016,6 +8070,7 @@ import {
8016
8070
  hasGitCredentialRepositorySelection,
8017
8071
  hasGitHubRepositorySelection,
8018
8072
  sandboxLifecycleTransitionWaitMs,
8073
+ sandboxWarmRateMicrosPerSecond,
8019
8074
  stableSandboxEnvironmentForRun
8020
8075
  } from "@opengeni/config";
8021
8076
  import { githubAppBotIdentity } from "@opengeni/github";
@@ -8034,6 +8089,7 @@ import {
8034
8089
  SandboxImageConflictError,
8035
8090
  SandboxProviderReadLockUnavailableError,
8036
8091
  SandboxRigConflictError,
8092
+ SandboxPaidComputeAdmissionError,
8037
8093
  withSandboxProviderReadLock
8038
8094
  } from "@opengeni/db";
8039
8095
  import { appendAndPublishEvents } from "@opengeni/events";
@@ -8853,6 +8909,10 @@ async function withChannelAOperation(services, ctx, readOnly, fn) {
8853
8909
  holderId,
8854
8910
  subjectId: session.id,
8855
8911
  backend: session.sandboxBackend,
8912
+ warmBilling: {
8913
+ mode: settings.sandboxWarmBillingMode,
8914
+ rateMicrosPerSecond: sandboxWarmRateMicrosPerSecond(settings, session.sandboxBackend)
8915
+ },
8856
8916
  os: session.sandboxOs,
8857
8917
  image: sandboxRuntime.image,
8858
8918
  rigVersionId: session.rigVersionId,
@@ -9153,6 +9213,8 @@ async function withChannelAOperation(services, ctx, readOnly, fn) {
9153
9213
  }
9154
9214
  function mapChannelAError(error, waitSignal) {
9155
9215
  if (error instanceof HTTPException6) return error;
9216
+ if (error instanceof SandboxPaidComputeAdmissionError)
9217
+ return new HTTPException6(402, { message: error.message, cause: error });
9156
9218
  if (isChannelARequestCancellation(error, waitSignal))
9157
9219
  return new HTTPException6(499, {
9158
9220
  message: "request cancelled",
@@ -9335,6 +9397,7 @@ import {
9335
9397
  retainedGeneratedImageReferenceFromFile,
9336
9398
  retainedGeneratedVideoReferenceFromFile,
9337
9399
  retainedScreenshotReferenceFromFile,
9400
+ retainedSessionScreenshotKindFromObjectKey,
9338
9401
  VideoArtifactPlaybackSource,
9339
9402
  resolveRetainedOutputRange
9340
9403
  } from "@opengeni/contracts";
@@ -9631,7 +9694,13 @@ function buildDocumentsMcpServer(db, accountId, workspaceId, documentServices, o
9631
9694
  {
9632
9695
  type: "text",
9633
9696
  text: JSON.stringify(
9634
- await searchKnowledgeEntries2(db, context, input, () => documentServices.embedder)
9697
+ await searchKnowledgeEntries2(
9698
+ db,
9699
+ context,
9700
+ input,
9701
+ () => documentServices.embedder,
9702
+ options.settings
9703
+ )
9635
9704
  )
9636
9705
  }
9637
9706
  ]
@@ -9917,7 +9986,8 @@ async function prepareWorkspaceToolGatewayForGrantInternal(routeDeps, grant, all
9917
9986
  grant.workspaceId,
9918
9987
  routeDeps.getDocumentServices(),
9919
9988
  {
9920
- knowledge: await knowledgeContextForGateway(routeDeps, grant)
9989
+ knowledge: await knowledgeContextForGateway(routeDeps, grant),
9990
+ settings: gatewaySettings
9921
9991
  }
9922
9992
  )
9923
9993
  )
@@ -11718,12 +11788,15 @@ function retainedGeneratedImageMetadata(artifact) {
11718
11788
  }
11719
11789
  function retainedScreenshotMetadata(artifact) {
11720
11790
  if (artifact.status === "ready" && artifact.sessionId && artifact.retentionExpiresAt.getTime() > Date.now()) {
11791
+ const kind = retainedSessionScreenshotKindFromObjectKey(artifact.file.objectKey);
11792
+ if (!kind) return retainedArtifactUnavailable(artifact.artifactId, "invalid_content");
11721
11793
  const reference = retainedScreenshotReferenceFromFile({
11722
11794
  ...artifact.file,
11723
11795
  sessionId: artifact.sessionId,
11724
11796
  width: artifact.width,
11725
11797
  height: artifact.height,
11726
- expiresAt: artifact.retentionExpiresAt.toISOString()
11798
+ expiresAt: artifact.retentionExpiresAt.toISOString(),
11799
+ kind
11727
11800
  });
11728
11801
  if (reference) return reference;
11729
11802
  }
@@ -11798,13 +11871,12 @@ async function publishSandboxFileArtifactInScope(deps, input) {
11798
11871
  message: "object storage cannot verify immutable artifact files"
11799
11872
  });
11800
11873
  }
11801
- const path = sandboxArtifactRelativePath(input.path);
11802
11874
  const maxArtifactBytes = Math.min(SANDBOX_FILE_ARTIFACT_MAX_BYTES, storage.maxSinglePutSizeBytes);
11803
11875
  if (maxArtifactBytes < 1) {
11804
11876
  throw new HTTPException12(503, { message: "object storage cannot accept artifact files" });
11805
11877
  }
11806
11878
  const readLimit = Math.min(25 * 1024 * 1024, maxArtifactBytes + FILE_READ_SENTINEL_BYTES);
11807
- const read = await withChannelARead(
11879
+ const { path, sandboxPath, read } = await withChannelARead(
11808
11880
  {
11809
11881
  db: deps.db,
11810
11882
  settings: deps.settings,
@@ -11819,7 +11891,7 @@ async function publishSandboxFileArtifactInScope(deps, input) {
11819
11891
  ...input.signal ? { waitSignal: input.signal } : {},
11820
11892
  operation: "artifact.publish"
11821
11893
  },
11822
- ({ service }) => service.fsRead({ path, encoding: "base64", maxBytes: readLimit })
11894
+ ({ service }) => readSandboxArtifactFile(service, input.path, readLimit)
11823
11895
  );
11824
11896
  if (read.truncated || read.sizeBytes > maxArtifactBytes) {
11825
11897
  throw new HTTPException12(413, {
@@ -11929,42 +12001,46 @@ async function publishSandboxFileArtifactInScope(deps, input) {
11929
12001
  });
11930
12002
  return SandboxFileArtifactReceipt.parse({
11931
12003
  type: "sandbox_file",
11932
- sandboxPath: `/workspace/${path}`,
12004
+ sandboxPath,
11933
12005
  filename,
11934
12006
  artifact
11935
12007
  });
11936
12008
  }
11937
- function sandboxArtifactRelativePath(value) {
12009
+ async function readSandboxArtifactFile(service, value, maxBytes) {
12010
+ const root2 = service.capabilities().FileSystem.root;
12011
+ const path = sandboxArtifactRelativePath(value, root2);
12012
+ const sandboxPath = resolveConnectedMachinePath(root2, path);
12013
+ const read = await service.fsRead({ path, encoding: "base64", maxBytes });
12014
+ return { path, sandboxPath, read };
12015
+ }
12016
+ function sandboxArtifactRelativePath(value, workspaceRoot = "/workspace") {
11938
12017
  let path = value.trim();
11939
12018
  if (path.startsWith("sandbox:")) {
11940
12019
  path = path.slice("sandbox:".length);
11941
12020
  }
11942
- if (path === "/workspace" || path === "/workspace/") {
12021
+ if (!path || /[\\/]$/.test(path)) {
11943
12022
  throw new HTTPException12(400, { message: "sandbox artifact path must name a file" });
11944
12023
  }
11945
- if (path.startsWith("/workspace/")) {
11946
- path = path.slice("/workspace/".length);
11947
- } else if (path.startsWith("/")) {
12024
+ let absolute;
12025
+ let relative;
12026
+ try {
12027
+ if (!isConnectedMachineAbsolutePath(workspaceRoot)) throw new Error("Missing workspace root");
12028
+ absolute = resolveConnectedMachinePath(workspaceRoot, path);
12029
+ relative = relativeConnectedMachinePath(workspaceRoot, absolute);
12030
+ } catch {
11948
12031
  throw new HTTPException12(400, {
11949
- message: "sandbox artifact path must be inside /workspace"
12032
+ message: "sandbox artifact path is invalid for this workspace"
11950
12033
  });
11951
12034
  }
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("/")) {
12035
+ if (!relative) {
11960
12036
  throw new HTTPException12(400, {
11961
- message: "sandbox artifact path must canonically name a file inside /workspace"
12037
+ message: "sandbox artifact path must name a file inside the active workspace root"
11962
12038
  });
11963
12039
  }
11964
- if (`/workspace/${normalized}`.length > SANDBOX_ARTIFACT_ABSOLUTE_PATH_MAX_CHARS) {
12040
+ if (absolute.length > SANDBOX_ARTIFACT_ABSOLUTE_PATH_MAX_CHARS) {
11965
12041
  throw new HTTPException12(400, { message: "sandbox artifact path is too long" });
11966
12042
  }
11967
- return normalized;
12043
+ return relative;
11968
12044
  }
11969
12045
  function sandboxArtifactSafeFilename(filename) {
11970
12046
  return sanitizeFilename(filename).slice(0, SANDBOX_ARTIFACT_SAFE_FILENAME_MAX_CHARS);
@@ -15624,16 +15700,16 @@ function capEventPayload(event, perFieldChars) {
15624
15700
  function prettyJsonBytes(value) {
15625
15701
  return Buffer.byteLength(JSON.stringify(value, null, 2), "utf8");
15626
15702
  }
15627
- function setMeasuredBytes(page) {
15628
- let measured = page.bytes;
15703
+ function setMeasuredBytes(page2) {
15704
+ let measured = page2.bytes;
15629
15705
  for (let attempt = 0; attempt < 8; attempt += 1) {
15630
- page.bytes = measured;
15631
- const next = prettyJsonBytes(page);
15706
+ page2.bytes = measured;
15707
+ const next = prettyJsonBytes(page2);
15632
15708
  if (next === measured) return next;
15633
15709
  measured = next;
15634
15710
  }
15635
- page.bytes = measured;
15636
- return prettyJsonBytes(page);
15711
+ page2.bytes = measured;
15712
+ return prettyJsonBytes(page2);
15637
15713
  }
15638
15714
  function boundSessionEventMcpPage(input) {
15639
15715
  const maxBytes = Math.max(8 * 1024, input.maxBytes ?? SESSION_EVENT_MCP_MAX_BYTES);
@@ -15655,7 +15731,7 @@ function boundSessionEventMcpPage(input) {
15655
15731
  if (modelRowsDropped) reasons.push("model_bytes");
15656
15732
  const nextAfter = input.direction === "after" ? last ?? input.after : null;
15657
15733
  const nextBefore = input.direction === "before" ? first ?? input.before : null;
15658
- const page2 = {
15734
+ const page3 = {
15659
15735
  mode: input.mode,
15660
15736
  payloadMode: input.payloadMode,
15661
15737
  direction: input.direction,
@@ -15675,20 +15751,20 @@ function boundSessionEventMcpPage(input) {
15675
15751
  bytes: 0,
15676
15752
  maxBytes
15677
15753
  };
15678
- setMeasuredBytes(page2);
15679
- return page2;
15754
+ setMeasuredBytes(page3);
15755
+ return page3;
15680
15756
  };
15681
- let page = build();
15682
- while (page.bytes > maxBytes && events.length > 0) {
15757
+ let page2 = build();
15758
+ while (page2.bytes > maxBytes && events.length > 0) {
15683
15759
  if (input.direction === "before") events.shift();
15684
15760
  else events.pop();
15685
15761
  modelRowsDropped = true;
15686
- page = build();
15762
+ page2 = build();
15687
15763
  }
15688
- if (page.bytes > maxBytes) {
15764
+ if (page2.bytes > maxBytes) {
15689
15765
  throw new RangeError(`Session-event MCP metadata exceeds its ${maxBytes}-byte envelope`);
15690
15766
  }
15691
- return page;
15767
+ return page2;
15692
15768
  }
15693
15769
  function modelStringProjection(value, maxBytes) {
15694
15770
  const originalBytes = Buffer.byteLength(value, "utf8");
@@ -16145,9 +16221,9 @@ function boundRigDetailMcp(rig, versionsPage, changesPage, maxBytes = RIG_DETAIL
16145
16221
  }
16146
16222
 
16147
16223
  // 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) => {
16224
+ function completeChildReadSequences(page2) {
16225
+ if (!page2.sourceExact || !["conversation", "results"].includes(page2.view)) return [];
16226
+ return page2.events.filter((event) => {
16151
16227
  if (event.sourceOmitted) return false;
16152
16228
  if (event.fragment === void 0) return true;
16153
16229
  if (!event.fragment || typeof event.fragment !== "object") return false;
@@ -17042,7 +17118,7 @@ function boundScheduledTaskMcpPage(input) {
17042
17118
  const rowsDroppedForBytes = droppedTasks.length > 0;
17043
17119
  const hasMore = input.sourceHasMore || rowsDroppedForBytes;
17044
17120
  const nextOffset = hasMore && input.tasks.length > 0 ? input.offset + input.tasks.length : null;
17045
- const page2 = {
17121
+ const page3 = {
17046
17122
  tasks: summaries,
17047
17123
  page: {
17048
17124
  limit: input.limit,
@@ -17070,20 +17146,20 @@ function boundScheduledTaskMcpPage(input) {
17070
17146
  }
17071
17147
  };
17072
17148
  settleMeasuredBytes(
17073
- page2,
17074
- () => page2.projection.bytes,
17149
+ page3,
17150
+ () => page3.projection.bytes,
17075
17151
  (bytes) => {
17076
- page2.projection.bytes = bytes;
17152
+ page3.projection.bytes = bytes;
17077
17153
  }
17078
17154
  );
17079
- return page2;
17155
+ return page3;
17080
17156
  };
17081
- let page = build();
17082
- while (page.projection.bytes > maxBytes && summaries.length > 0) {
17157
+ let page2 = build();
17158
+ while (page2.projection.bytes > maxBytes && summaries.length > 0) {
17083
17159
  summaries = summaries.slice(0, -1);
17084
- page = build();
17160
+ page2 = build();
17085
17161
  }
17086
- return page;
17162
+ return page2;
17087
17163
  }
17088
17164
 
17089
17165
  // src/sandbox/viewer.ts
@@ -17094,6 +17170,7 @@ import {
17094
17170
  hasGitHubRepositorySelection as hasGitHubRepositorySelection2,
17095
17171
  resolveStreamTokenSecret,
17096
17172
  sandboxLifecycleTransitionWaitMs as sandboxLifecycleTransitionWaitMs2,
17173
+ sandboxWarmRateMicrosPerSecond as sandboxWarmRateMicrosPerSecond2,
17097
17174
  stableSandboxEnvironmentForRun as stableSandboxEnvironmentForRun2
17098
17175
  } from "@opengeni/config";
17099
17176
  import { githubAppBotIdentity as githubAppBotIdentity2 } from "@opengeni/github";
@@ -17103,7 +17180,7 @@ import {
17103
17180
  getSandbox as getSandbox2,
17104
17181
  getScheduledScopedRigVersionMetadata as getScheduledScopedRigVersionMetadata2,
17105
17182
  getSandboxSessionEnvelope as getSandboxSessionEnvelope2,
17106
- heartbeatLeaseHolder,
17183
+ heartbeatLeaseHolderStatus,
17107
17184
  loadWorkspaceEnvironmentForRun as loadWorkspaceEnvironmentForRun2,
17108
17185
  markSandboxProviderReady,
17109
17186
  markWarmLeaseInstanceLost as markWarmLeaseInstanceLost2,
@@ -17113,6 +17190,7 @@ import {
17113
17190
  releaseLeaseHolder as releaseLeaseHolder2,
17114
17191
  SandboxLeaseSupersededError as SandboxLeaseSupersededError2,
17115
17192
  SandboxViewerAdmissionBlockedError,
17193
+ SandboxPaidComputeAdmissionError as SandboxPaidComputeAdmissionError2,
17116
17194
  getSessionAuthorityEpoch,
17117
17195
  namedSubjectHasLiveWorkspaceAuthority
17118
17196
  } from "@opengeni/db";
@@ -17242,6 +17320,10 @@ async function attachViewer(services, input) {
17242
17320
  ...attachSubjectId ? { viewerSubjectId: attachSubjectId } : {},
17243
17321
  ...attachAuthorityEpoch !== null ? { viewerAuthorityEpoch: attachAuthorityEpoch } : {},
17244
17322
  backend: session.sandboxBackend,
17323
+ warmBilling: {
17324
+ mode: settings.sandboxWarmBillingMode,
17325
+ rateMicrosPerSecond: sandboxWarmRateMicrosPerSecond2(settings, session.sandboxBackend)
17326
+ },
17245
17327
  os: session.sandboxOs,
17246
17328
  image: sandboxRuntime.image,
17247
17329
  rigVersionId: session.rigVersionId,
@@ -17251,6 +17333,9 @@ async function attachViewer(services, input) {
17251
17333
  ...input.waitSignal ? { waitSignal: input.waitSignal } : {}
17252
17334
  });
17253
17335
  } catch (error) {
17336
+ if (error instanceof SandboxPaidComputeAdmissionError2) {
17337
+ throw new HTTPException19(402, { message: error.message, cause: error });
17338
+ }
17254
17339
  if (error instanceof SandboxViewerAdmissionBlockedError) {
17255
17340
  throw new HTTPException19(error.reason === "balance" ? 402 : 429, {
17256
17341
  message: error.reason === "balance" ? "insufficient OpenGeni credits for an idle sandbox viewer" : "workspace sandbox warm allowance exhausted",
@@ -17437,16 +17522,17 @@ async function ensureSessionGroupReady(services, input) {
17437
17522
  throw lastError ?? new Error("sandbox readiness could not be established");
17438
17523
  }
17439
17524
  async function heartbeatViewer(services, input) {
17440
- const alive = await heartbeatLeaseHolder(services.db, {
17525
+ const status = await heartbeatLeaseHolderStatus(services.db, {
17441
17526
  accountId: input.accountId,
17442
17527
  workspaceId: input.workspaceId,
17443
17528
  sandboxGroupId: input.sandboxGroupId,
17444
17529
  kind: "viewer",
17445
17530
  holderId: input.viewerId,
17446
17531
  leaseTtlMs: services.settings.sandboxLeaseTtlMs,
17447
- expectedEpoch: input.expectedEpoch
17532
+ expectedEpoch: input.expectedEpoch,
17533
+ billingMode: services.settings.sandboxWarmBillingMode
17448
17534
  });
17449
- if (!alive) return false;
17535
+ if (!status.leaseExtended) return status.holderAlive && status.fence === "funding";
17450
17536
  const lease = await readLease2(services.db, input.workspaceId, input.sandboxGroupId);
17451
17537
  if (lease?.liveness === "warm" && lease.leaseEpoch === input.expectedEpoch && lease.instanceId) {
17452
17538
  await renewSandboxProviderExpiration({
@@ -18326,7 +18412,7 @@ var OpenGeniSlackBotClient = class {
18326
18412
  let reactedMessage = null;
18327
18413
  const checkpointCreatedAtMs = restored?.state.createdAtMs ?? Date.now();
18328
18414
  const firstPage = restored?.state.pageCount ?? 0;
18329
- for (let page = firstPage; page < MAX_REACTION_CONTEXT_PAGES; page += 1) {
18415
+ for (let page2 = firstPage; page2 < MAX_REACTION_CONTEXT_PAGES; page2 += 1) {
18330
18416
  const payload = await this.call(headers, "conversations.replies", {
18331
18417
  channel: input.channelId,
18332
18418
  // Slack accepts either the parent timestamp or a message timestamp from
@@ -18350,7 +18436,7 @@ var OpenGeniSlackBotClient = class {
18350
18436
  throw new SlackBotProviderError("reaction_pagination_invalid");
18351
18437
  }
18352
18438
  seenCursors.add(nextCursor);
18353
- const pageCount = page + 1;
18439
+ const pageCount = page2 + 1;
18354
18440
  if (pageCount >= MAX_REACTION_CONTEXT_PAGES) {
18355
18441
  throw new SlackBotProviderError("reaction_pagination_exhausted");
18356
18442
  }
@@ -18576,14 +18662,14 @@ var OpenGeniSlackBotClient = class {
18576
18662
  if (offset > content.length) {
18577
18663
  throw new SlackBotProviderError("invalid_file_offset");
18578
18664
  }
18579
- const page = content.slice(offset, offset + SLACK_FILE_CONTENT_PAGE_CHARS);
18580
- const nextOffset = offset + page.length < content.length ? offset + page.length : null;
18665
+ const page2 = content.slice(offset, offset + SLACK_FILE_CONTENT_PAGE_CHARS);
18666
+ const nextOffset = offset + page2.length < content.length ? offset + page2.length : null;
18581
18667
  return {
18582
18668
  channel: info,
18583
18669
  file,
18584
18670
  contentType,
18585
18671
  offset,
18586
- content: page,
18672
+ content: page2,
18587
18673
  nextOffset,
18588
18674
  truncated: nextOffset !== null
18589
18675
  };
@@ -18985,7 +19071,7 @@ var OpenGeniSlackBotClient = class {
18985
19071
  let cursor = null;
18986
19072
  let matchedTimestamp = null;
18987
19073
  let exhausted = false;
18988
- for (let page = 0; page < MAX_SLACK_POST_RECONCILIATION_PAGES; page += 1) {
19074
+ for (let page2 = 0; page2 < MAX_SLACK_POST_RECONCILIATION_PAGES; page2 += 1) {
18989
19075
  const payload = await this.call(headers, method, {
18990
19076
  channel: input.channelId,
18991
19077
  limit: String(input.threadTimestamp ? MAX_THREAD_PAGE : MAX_HISTORY_PAGE),
@@ -20092,13 +20178,13 @@ function nextSlackFilesListPage(payload, requested, returnedFileCount) {
20092
20178
  }
20093
20179
  const count = paging?.count;
20094
20180
  const total = paging?.total;
20095
- const page = paging?.page;
20181
+ const page2 = paging?.page;
20096
20182
  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)) {
20183
+ 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
20184
  throw new SlackBotProviderError("invalid_files_paging");
20099
20185
  }
20100
- if (page >= pages) return null;
20101
- const nextPage = page + 1;
20186
+ if (page2 >= pages) return null;
20187
+ const nextPage = page2 + 1;
20102
20188
  if (!positiveSafeInt(nextPage)) {
20103
20189
  throw new SlackBotProviderError("invalid_files_paging");
20104
20190
  }
@@ -20459,14 +20545,14 @@ var FikenClient = class {
20459
20545
  resolveCredential;
20460
20546
  expectedConnectionVersion;
20461
20547
  async listCompanies(input = {}) {
20462
- const { payload, page } = await this.request("companies.list", "GET", "/companies", {
20548
+ const { payload, page: page2 } = await this.request("companies.list", "GET", "/companies", {
20463
20549
  query: pageQuery(input)
20464
20550
  });
20465
- return { companies: projectArray(payload), page };
20551
+ return { companies: projectArray(payload), page: page2 };
20466
20552
  }
20467
20553
  async listContacts(input) {
20468
20554
  const slug = this.companySlugFor(input.companySlug);
20469
- const { payload, page } = await this.request(
20555
+ const { payload, page: page2 } = await this.request(
20470
20556
  "contacts.list",
20471
20557
  "GET",
20472
20558
  `/companies/${slug}/contacts`,
@@ -20482,7 +20568,7 @@ var FikenClient = class {
20482
20568
  }
20483
20569
  }
20484
20570
  );
20485
- return { companySlug: slug, contacts: projectArray(payload), page };
20571
+ return { companySlug: slug, contacts: projectArray(payload), page: page2 };
20486
20572
  }
20487
20573
  async createContact(input) {
20488
20574
  const slug = this.companySlugFor(input.companySlug);
@@ -20508,7 +20594,7 @@ var FikenClient = class {
20508
20594
  }
20509
20595
  async listProducts(input) {
20510
20596
  const slug = this.companySlugFor(input.companySlug);
20511
- const { payload, page } = await this.request(
20597
+ const { payload, page: page2 } = await this.request(
20512
20598
  "products.list",
20513
20599
  "GET",
20514
20600
  `/companies/${slug}/products`,
@@ -20520,11 +20606,11 @@ var FikenClient = class {
20520
20606
  }
20521
20607
  }
20522
20608
  );
20523
- return { companySlug: slug, products: projectArray(payload), page };
20609
+ return { companySlug: slug, products: projectArray(payload), page: page2 };
20524
20610
  }
20525
20611
  async listInvoices(input) {
20526
20612
  const slug = this.companySlugFor(input.companySlug);
20527
- const { payload, page } = await this.request(
20613
+ const { payload, page: page2 } = await this.request(
20528
20614
  "invoices.list",
20529
20615
  "GET",
20530
20616
  `/companies/${slug}/invoices`,
@@ -20539,7 +20625,7 @@ var FikenClient = class {
20539
20625
  }
20540
20626
  }
20541
20627
  );
20542
- return { companySlug: slug, invoices: projectArray(payload), page };
20628
+ return { companySlug: slug, invoices: projectArray(payload), page: page2 };
20543
20629
  }
20544
20630
  async getInvoice(input) {
20545
20631
  const slug = this.companySlugFor(input.companySlug);
@@ -20623,7 +20709,7 @@ var FikenClient = class {
20623
20709
  }
20624
20710
  async listBankAccounts(input) {
20625
20711
  const slug = this.companySlugFor(input.companySlug);
20626
- const { payload, page } = await this.request(
20712
+ const { payload, page: page2 } = await this.request(
20627
20713
  "bank_accounts.list",
20628
20714
  "GET",
20629
20715
  `/companies/${slug}/bankAccounts`,
@@ -20634,11 +20720,11 @@ var FikenClient = class {
20634
20720
  }
20635
20721
  }
20636
20722
  );
20637
- return { companySlug: slug, bankAccounts: projectArray(payload), page };
20723
+ return { companySlug: slug, bankAccounts: projectArray(payload), page: page2 };
20638
20724
  }
20639
20725
  async listPurchases(input) {
20640
20726
  const slug = this.companySlugFor(input.companySlug);
20641
- const { payload, page } = await this.request(
20727
+ const { payload, page: page2 } = await this.request(
20642
20728
  "purchases.list",
20643
20729
  "GET",
20644
20730
  `/companies/${slug}/purchases`,
@@ -20651,11 +20737,11 @@ var FikenClient = class {
20651
20737
  }
20652
20738
  }
20653
20739
  );
20654
- return { companySlug: slug, purchases: projectArray(payload), page };
20740
+ return { companySlug: slug, purchases: projectArray(payload), page: page2 };
20655
20741
  }
20656
20742
  async listSales(input) {
20657
20743
  const slug = this.companySlugFor(input.companySlug);
20658
- const { payload, page } = await this.request("sales.list", "GET", `/companies/${slug}/sales`, {
20744
+ const { payload, page: page2 } = await this.request("sales.list", "GET", `/companies/${slug}/sales`, {
20659
20745
  query: {
20660
20746
  ...pageQuery(input),
20661
20747
  ...input.dateGe ? { dateGe: input.dateGe } : {},
@@ -20663,7 +20749,7 @@ var FikenClient = class {
20663
20749
  ...input.settled !== void 0 ? { settled: String(input.settled) } : {}
20664
20750
  }
20665
20751
  });
20666
- return { companySlug: slug, sales: projectArray(payload), page };
20752
+ return { companySlug: slug, sales: projectArray(payload), page: page2 };
20667
20753
  }
20668
20754
  /**
20669
20755
  * The company scope for a call: an explicit slug wins, then the connection's
@@ -20865,11 +20951,11 @@ function boundedInt2(value, max, fallback) {
20865
20951
  return Math.min(value, max);
20866
20952
  }
20867
20953
  function pageFromHeaders(headers) {
20868
- const page = headerInt(headers, "fiken-api-page");
20954
+ const page2 = headerInt(headers, "fiken-api-page");
20869
20955
  const pageSize = headerInt(headers, "fiken-api-page-size");
20870
- if (page === null || pageSize === null) return null;
20956
+ if (page2 === null || pageSize === null) return null;
20871
20957
  return {
20872
- page,
20958
+ page: page2,
20873
20959
  pageSize,
20874
20960
  pageCount: headerInt(headers, "fiken-api-page-count"),
20875
20961
  resultCount: headerInt(headers, "fiken-api-result-count")
@@ -21828,24 +21914,24 @@ async function getAtlassianLiveItem(deps, input) {
21828
21914
  );
21829
21915
  url.searchParams.set("body-format", "storage");
21830
21916
  try {
21831
- const page = objectRecord(
21917
+ const page2 = objectRecord(
21832
21918
  await atlassianApiRequest(deps, { ...input, url, label: "Confluence live page" })
21833
21919
  );
21834
21920
  const source = sources.find(
21835
- (candidate) => candidate.resourceId === optionalString(page.spaceId)
21921
+ (candidate) => candidate.resourceId === optionalString(page2.spaceId)
21836
21922
  );
21837
21923
  if (!source) continue;
21838
21924
  const comments = await readConfluenceLiveComments(deps, input, cloudId);
21839
- const webUi = optionalString(objectRecord(page._links).webui);
21925
+ const webUi = optionalString(objectRecord(page2._links).webui);
21840
21926
  return {
21841
21927
  kind: "confluence_page",
21842
- id: requiredString2(page.id, "page.id"),
21843
- title: requiredString2(page.title, "page.title"),
21928
+ id: requiredString2(page2.id, "page.id"),
21929
+ title: requiredString2(page2.title, "page.title"),
21844
21930
  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) ?? "",
21931
+ status: optionalString(page2.status),
21932
+ createdAt: optionalString(page2.createdAt),
21933
+ updatedAt: optionalString(objectRecord(page2.version).createdAt),
21934
+ content: optionalString(objectRecord(objectRecord(page2.body).storage).value) ?? "",
21849
21935
  comments,
21850
21936
  url: webUi ? new URL(webUi.startsWith("/wiki/") ? webUi : `/wiki${webUi}`, source.siteUrl).toString() : source.siteUrl
21851
21937
  };
@@ -22359,7 +22445,7 @@ async function materializeSchedules(deps, input) {
22359
22445
  async function browseJiraProjects(deps, input, site) {
22360
22446
  const items = [];
22361
22447
  let startAt = 0;
22362
- for (let page = 0; page < 20; page += 1) {
22448
+ for (let page2 = 0; page2 < 20; page2 += 1) {
22363
22449
  const url = new URL(
22364
22450
  `https://api.atlassian.com/ex/jira/${encodeURIComponent(site.cloudId)}/rest/api/3/project/search`
22365
22451
  );
@@ -22402,7 +22488,7 @@ async function browseConfluenceSpaces(deps, input, site) {
22402
22488
  let url = new URL(
22403
22489
  `https://api.atlassian.com/ex/confluence/${encodeURIComponent(site.cloudId)}/wiki/api/v2/spaces?limit=100`
22404
22490
  );
22405
- for (let page = 0; page < 20; page += 1) {
22491
+ for (let page2 = 0; page2 < 20; page2 += 1) {
22406
22492
  const payload = objectRecord(
22407
22493
  await atlassianApiRequest(deps, { ...input, url, label: "Confluence spaces" })
22408
22494
  );
@@ -22802,7 +22888,7 @@ function cqlString(value) {
22802
22888
  async function readJiraLiveComments(deps, input, cloudId) {
22803
22889
  const comments = [];
22804
22890
  let startAt = 0;
22805
- for (let page = 0; page < 20; page += 1) {
22891
+ for (let page2 = 0; page2 < 20; page2 += 1) {
22806
22892
  const url = new URL(
22807
22893
  `https://api.atlassian.com/ex/jira/${encodeURIComponent(cloudId)}/rest/api/3/issue/${encodeURIComponent(input.id)}/comment`
22808
22894
  );
@@ -22830,7 +22916,7 @@ async function readJiraLiveComments(deps, input, cloudId) {
22830
22916
  async function readConfluenceLiveComments(deps, input, cloudId) {
22831
22917
  const comments = [];
22832
22918
  let cursor = null;
22833
- for (let page = 0; page < 20; page += 1) {
22919
+ for (let page2 = 0; page2 < 20; page2 += 1) {
22834
22920
  const url = new URL(
22835
22921
  `https://api.atlassian.com/ex/confluence/${encodeURIComponent(cloudId)}/wiki/api/v2/pages/${encodeURIComponent(input.id)}/footer-comments`
22836
22922
  );
@@ -27285,9 +27371,9 @@ function registerFikenTools(server, deps, grant, sessionId, json) {
27285
27371
  ...pageInputs
27286
27372
  }
27287
27373
  },
27288
- async ({ connectionId, page, pageSize }) => json(
27374
+ async ({ connectionId, page: page2, pageSize }) => json(
27289
27375
  await (await clientFor(connectionId)).listCompanies({
27290
- ...page !== void 0 ? { page } : {},
27376
+ ...page2 !== void 0 ? { page: page2 } : {},
27291
27377
  ...pageSize !== void 0 ? { pageSize } : {}
27292
27378
  })
27293
27379
  )
@@ -27669,7 +27755,7 @@ function registerGoalTools(server, deps, grant, sessionId, json) {
27669
27755
  server.registerTool(
27670
27756
  "goal_update",
27671
27757
  {
27672
- description: "Propose or apply a semantic goal revision under the session's mutation policy. Retain the standing goal unless explicit user direction or meaningful new evidence justifies the declared refinement, adaptation, or replacement. Every rewrite must use the exact expected objective revision and a concise rationale. Root constraints cannot be changed by an agent. A rewrite is not an execution-progress audit fact; use the optional goal_progress tool when such a fact should be recorded.",
27758
+ description: "Maintain your operational goal as user direction or meaningful new evidence clarifies the intended outcome. Changes apply directly unless the user explicitly configured review_changes; refinement, adaptation, and replacement are audit classifications, not approval gates under the default policy. Use the exact expected objective revision and a concise rationale. Updating a goal grants no additional authority and cannot change root constraints. Use goal_progress for an execution-progress audit fact rather than a goal rewrite.",
27673
27759
  inputSchema: {
27674
27760
  text: goalText.optional(),
27675
27761
  successCriteria: successCriteriaSchema.nullable().optional(),
@@ -29223,7 +29309,7 @@ function registerWorkspaceOrchestrationTools(server, deps, grant, can, callerSes
29223
29309
  if (decodedCursor && decodedCursor.updatedAfter !== (normalizedUpdatedAfter ?? null)) {
29224
29310
  throw new Error("sessions_list cursor does not match updatedAfter");
29225
29311
  }
29226
- const page = await listSessionDiscoverySummaries(deps.db, grant.workspaceId, {
29312
+ const page2 = await listSessionDiscoverySummaries(deps.db, grant.workspaceId, {
29227
29313
  limit: boundedSessionDiscoveryLimit(limit),
29228
29314
  ...decodedCursor ? { cursor: decodedCursor } : {},
29229
29315
  includeLastMessage: includeLastMessage === true,
@@ -29243,7 +29329,7 @@ function registerWorkspaceOrchestrationTools(server, deps, grant, can, callerSes
29243
29329
  subjectId: grant.subjectId,
29244
29330
  ...authorizationScope ? { authorizationScope } : {}
29245
29331
  });
29246
- const result = detail === "full" ? capSessionDiscoveryPage(page, includeLastMessage === true) : capSessionDiscoveryCompactPage(page, {
29332
+ const result = detail === "full" ? capSessionDiscoveryPage(page2, includeLastMessage === true) : capSessionDiscoveryCompactPage(page2, {
29247
29333
  includeLastMessage: includeLastMessage === true,
29248
29334
  includeRelatedWork: relatedWorkRequested
29249
29335
  });
@@ -29409,8 +29495,8 @@ function registerWorkspaceOrchestrationTools(server, deps, grant, can, callerSes
29409
29495
  }
29410
29496
  if (view !== "debug" && !auditRequested) {
29411
29497
  const { readSessionEventView } = await import("./session-event-view-MU3HMNV3.js");
29412
- const { listSessionEventSlices } = await import("@opengeni/db/session-event-slices");
29413
- const page2 = await readSessionEventView(
29498
+ const { listSessionEventSlices: listSessionEventSlices2 } = await import("@opengeni/db/session-event-slices");
29499
+ const page3 = await readSessionEventView(
29414
29500
  {
29415
29501
  sessionId,
29416
29502
  view,
@@ -29423,7 +29509,7 @@ function registerWorkspaceOrchestrationTools(server, deps, grant, can, callerSes
29423
29509
  direction: requestedDirection,
29424
29510
  limit
29425
29511
  },
29426
- (options) => listSessionEventSlices(
29512
+ (options) => listSessionEventSlices2(
29427
29513
  deps.db,
29428
29514
  grant.workspaceId,
29429
29515
  sessionId,
@@ -29431,8 +29517,8 @@ function registerWorkspaceOrchestrationTools(server, deps, grant, can, callerSes
29431
29517
  (legacyOptions) => listSessionEventPage(deps.db, grant.workspaceId, sessionId, legacyOptions)
29432
29518
  )
29433
29519
  );
29434
- await acknowledgeReads([{ sessionId, sequences: completeChildReadSequences(page2) }]);
29435
- return json(page2);
29520
+ await acknowledgeReads([{ sessionId, sequences: completeChildReadSequences(page3) }]);
29521
+ return json(page3);
29436
29522
  }
29437
29523
  if (cursor !== void 0 || callId !== void 0 || includeArguments !== void 0 || includeOutput !== void 0) {
29438
29524
  throw new Error("cursor/callId/includeArguments/includeOutput require a non-debug view");
@@ -29477,7 +29563,7 @@ function registerWorkspaceOrchestrationTools(server, deps, grant, can, callerSes
29477
29563
  }
29478
29564
  return json(result);
29479
29565
  }
29480
- const page = boundSessionEventMcpPage({
29566
+ const page2 = boundSessionEventMcpPage({
29481
29567
  events: dbPage.events,
29482
29568
  mode,
29483
29569
  payloadMode,
@@ -29487,12 +29573,12 @@ function registerWorkspaceOrchestrationTools(server, deps, grant, can, callerSes
29487
29573
  after: after ?? 0,
29488
29574
  before: before ?? null
29489
29575
  });
29490
- if (payloadMode === "full" && dbPage.fullPayloadsExact && !page.truncation?.reasons.includes("model_payload")) {
29576
+ if (payloadMode === "full" && dbPage.fullPayloadsExact && !page2.truncation?.reasons.includes("model_payload")) {
29491
29577
  await acknowledgeReads([
29492
- { sessionId, sequences: page.events.map((event) => event.sequence) }
29578
+ { sessionId, sequences: page2.events.map((event) => event.sequence) }
29493
29579
  ]);
29494
29580
  }
29495
- return json(page);
29581
+ return json(page2);
29496
29582
  }
29497
29583
  );
29498
29584
  server.registerTool(
@@ -29548,7 +29634,7 @@ function registerWorkspaceOrchestrationTools(server, deps, grant, can, callerSes
29548
29634
  }
29549
29635
  },
29550
29636
  readTargetEvents: async (target) => {
29551
- const page = await listSessionEventPage(deps.db, workspaceId, target.sessionId, {
29637
+ const page2 = await listSessionEventPage(deps.db, workspaceId, target.sessionId, {
29552
29638
  after: target.afterSequence,
29553
29639
  direction: "after",
29554
29640
  limit: SESSION_WAIT_EVENTS_PER_TARGET,
@@ -29556,10 +29642,10 @@ function registerWorkspaceOrchestrationTools(server, deps, grant, can, callerSes
29556
29642
  includeTypes: targetEventTypes,
29557
29643
  maxBytes: SESSION_EVENT_MCP_MAX_BYTES * 4
29558
29644
  });
29559
- if (!page.fullPayloadsExact) {
29560
- for (const event of page.events) incompleteWaitEvents.add(event.id);
29645
+ if (!page2.fullPayloadsExact) {
29646
+ for (const event of page2.events) incompleteWaitEvents.add(event.id);
29561
29647
  }
29562
- return { events: page.events, hasMore: page.hasMore };
29648
+ return { events: page2.events, hasMore: page2.hasMore };
29563
29649
  },
29564
29650
  readOwnPendingUpdateKinds: ownSessionId === null ? null : async () => (await listOutstandingSessionSystemUpdates(deps.db, workspaceId, ownSessionId)).map((update) => update.kind),
29565
29651
  subscribe: (targetSessionId, onEvents) => deps.bus.subscribe(workspaceId, targetSessionId, onEvents)
@@ -30443,9 +30529,12 @@ function registerCapabilityDiscoveryTools(server, deps, grant, sessionId, json)
30443
30529
  };
30444
30530
  const setupProjections = async (items) => {
30445
30531
  const availableServerIds = /* @__PURE__ */ new Set();
30532
+ const availableFirstPartyTools = /* @__PURE__ */ new Set();
30446
30533
  const acceptedServerIds = /* @__PURE__ */ new Set();
30447
30534
  let accountSelectionKnown = false;
30448
- if (items.some((item) => item.enabled && item.connectionRef)) {
30535
+ if (items.some(
30536
+ (item) => item.enabled && (item.connectionRef || item.surfaceType === "first_party_fiken")
30537
+ )) {
30449
30538
  const claims = exactAgentCommandContext(grant, sessionId);
30450
30539
  const attemptCatalog = await getAttemptToolCatalog2(deps.db, {
30451
30540
  accountId: grant.accountId,
@@ -30453,7 +30542,11 @@ function registerCapabilityDiscoveryTools(server, deps, grant, sessionId, json)
30453
30542
  attemptId: claims.callerAttemptId
30454
30543
  });
30455
30544
  if (attemptCatalog?.sessionId === sessionId && attemptCatalog.turnId === claims.callerTurnId && attemptCatalog.executionGeneration === claims.callerExecutionGeneration) {
30456
- for (const entry2 of attemptCatalog.entries) availableServerIds.add(entry2.identity.serverId);
30545
+ for (const entry2 of attemptCatalog.entries) {
30546
+ availableServerIds.add(entry2.identity.serverId);
30547
+ if (entry2.identity.serverId === "opengeni")
30548
+ availableFirstPartyTools.add(entry2.identity.toolName);
30549
+ }
30457
30550
  const bindings = await getSessionTurnMcpAccountBindings(
30458
30551
  deps.db,
30459
30552
  grant.workspaceId,
@@ -30474,7 +30567,9 @@ function registerCapabilityDiscoveryTools(server, deps, grant, sessionId, json)
30474
30567
  item,
30475
30568
  availableServerIds,
30476
30569
  acceptedServerIds,
30477
- accountSelectionKnown
30570
+ accountSelectionKnown,
30571
+ availableFirstPartyTools,
30572
+ new Set(grant.metadata?.firstPartyMcpTools ?? [])
30478
30573
  )
30479
30574
  )
30480
30575
  );
@@ -30592,8 +30687,93 @@ function registerCapabilityDiscoveryTools(server, deps, grant, sessionId, json)
30592
30687
  });
30593
30688
  }
30594
30689
  );
30690
+ server.registerTool(
30691
+ "custom_mcp_setup_request",
30692
+ {
30693
+ description: "Show a review card for a remote HTTPS MCP server that is not in the workspace catalog. Use only an endpoint supplied by the user or established by reliable documentation; do not invent a URL. Never include query parameters or secrets in this URL; the human can edit it in the protected setup form. The agent cannot add, enable, or contact the server. Search the catalog first and do not propose an already available integration.",
30694
+ inputSchema: {
30695
+ name: z43.string().trim().min(1).max(256),
30696
+ endpointUrl: z43.string().url().max(2048).refine((url) => {
30697
+ const parsed = new URL(url);
30698
+ return parsed.protocol === "https:" && !parsed.username && !parsed.password && !parsed.hash && !parsed.search;
30699
+ }),
30700
+ rationale: z43.string().trim().min(1).max(2e3)
30701
+ }
30702
+ },
30703
+ async ({ name, endpointUrl, rationale }) => {
30704
+ await authorize();
30705
+ const current = await catalog();
30706
+ const existing = current.items.find(
30707
+ (item) => item.kind === "mcp" && item.endpointUrl === endpointUrl && !item.stale
30708
+ );
30709
+ if (existing) {
30710
+ return json({
30711
+ status: "already_in_catalog",
30712
+ capabilityId: existing.id,
30713
+ message: "Use the catalog authorization flow for this server instead."
30714
+ });
30715
+ }
30716
+ const claims = exactAgentCommandContext(grant, sessionId);
30717
+ const payload = ToolAuthNeededPayload.parse({
30718
+ serverId: "opengeni",
30719
+ toolName: "custom_mcp_setup_request",
30720
+ providerDomain: new URL(endpointUrl).hostname,
30721
+ reason: "missing_connection",
30722
+ setupRequest: { kind: "mcp", name, endpointUrl, rationale }
30723
+ });
30724
+ const appended = await appendAndPublishTurnEventsFenced(
30725
+ deps.db,
30726
+ deps.bus,
30727
+ grant.workspaceId,
30728
+ sessionId,
30729
+ claims.callerTurnId,
30730
+ claims.callerExecutionGeneration,
30731
+ claims.callerAttemptId,
30732
+ [{ type: "tool.auth_needed", payload }]
30733
+ );
30734
+ if (!appended.accepted) {
30735
+ throw new Error("The calling turn was replaced before the setup request committed.");
30736
+ }
30737
+ return json({
30738
+ status: "setup_requested",
30739
+ eventId: appended.events[0]?.id ?? null,
30740
+ message: "The human review card was posted. No server was added or contacted."
30741
+ });
30742
+ }
30743
+ );
30595
30744
  }
30596
- async function capabilitySetupProjection(deps, workspaceId, item, availableServerIds, acceptedServerIds, accountSelectionKnown) {
30745
+ async function capabilitySetupProjection(deps, workspaceId, item, availableServerIds, acceptedServerIds, accountSelectionKnown, availableFirstPartyTools, selectedFirstPartyTools) {
30746
+ if (item.surfaceType === "first_party_fiken") {
30747
+ if (!item.enabled || item.metadata.connectionStatus !== "active") {
30748
+ return {
30749
+ status: "authorization_required",
30750
+ action: "connect",
30751
+ detail: item.metadata.connectionStatus === "needs_reauth" ? "The workspace Fiken connection needs reconnection." : "Connect Fiken through the protected setup form."
30752
+ };
30753
+ }
30754
+ const selected = FIRST_PARTY_MCP_TOOL_NAMES2.filter(
30755
+ (name) => name.startsWith("fiken_") && selectedFirstPartyTools.has(name)
30756
+ );
30757
+ if (selected.length === 0) {
30758
+ return {
30759
+ status: "authorization_required",
30760
+ action: "enable",
30761
+ 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."
30762
+ };
30763
+ }
30764
+ if (selected.some((name) => availableFirstPartyTools.has(name))) {
30765
+ return {
30766
+ status: "ready",
30767
+ action: null,
30768
+ detail: "Fiken is connected and has tools available in this turn."
30769
+ };
30770
+ }
30771
+ return {
30772
+ status: "unavailable",
30773
+ action: null,
30774
+ 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."
30775
+ };
30776
+ }
30597
30777
  if (item.id === "api:github-app" || item.surfaceType === "first_party_github") {
30598
30778
  const missing = githubAppMissingSettings(deps.settings);
30599
30779
  if (missing.length > 0) {
@@ -30947,9 +31127,9 @@ function decodeSessionDiscoveryCursor(value) {
30947
31127
  throw new Error("sessions_list cursor is invalid");
30948
31128
  }
30949
31129
  }
30950
- function capSessionDiscoveryCompactPage(page, options = {}, maxBytes = SESSION_DISCOVERY_PAGE_MAX_BYTES) {
31130
+ function capSessionDiscoveryCompactPage(page2, options = {}, maxBytes = SESSION_DISCOVERY_PAGE_MAX_BYTES) {
30951
31131
  let previewBytes = 0;
30952
- const projected = page.sessions.map((session) => {
31132
+ const projected = page2.sessions.map((session) => {
30953
31133
  const row = {
30954
31134
  ...compactSessionMcpListRow(session, options.includeRelatedWork),
30955
31135
  ...options.includeLastMessage && session.queuedPromptCount > 0 ? { queuedPromptCount: session.queuedPromptCount } : {}
@@ -30986,24 +31166,24 @@ function capSessionDiscoveryCompactPage(page, options = {}, maxBytes = SESSION_D
30986
31166
  let kept = projected;
30987
31167
  const build = () => {
30988
31168
  const dropped = kept.length < projected.length;
30989
- const last = page.sessions[kept.length - 1];
31169
+ const last = page2.sessions[kept.length - 1];
30990
31170
  const cursor = dropped && last ? {
30991
- orderBy: page.orderBy,
31171
+ orderBy: page2.orderBy,
30992
31172
  sortRank: last.sortRank,
30993
31173
  sortRevision: last.sortRevision,
30994
31174
  sortAt: last.sortAt,
30995
31175
  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;
31176
+ snapshotAt: page2.snapshotAt,
31177
+ snapshotRevision: page2.snapshotRevision,
31178
+ updatedAfter: page2.updatedAfter,
31179
+ filterHash: page2.filterHash,
31180
+ ...page2.originSiteId ? { originSiteId: page2.originSiteId } : {}
31181
+ } : page2.nextCursor;
31002
31182
  return {
31003
31183
  sessions: kept,
31004
- total: page.total,
31184
+ total: page2.total,
31005
31185
  nextCursor: cursor ? encodeSessionDiscoveryCursor(cursor) : null,
31006
- ...page.orderBy === "updatedAt" ? { updatedThrough: page.updatedThrough } : {},
31186
+ ...page2.orderBy === "updatedAt" ? { updatedThrough: page2.updatedThrough } : {},
31007
31187
  ...dropped ? {
31008
31188
  responseTruncated: true,
31009
31189
  truncationReason: `response exceeded ${maxBytes} bytes; continue with nextCursor`
@@ -31020,8 +31200,8 @@ function capSessionDiscoveryCompactPage(page, options = {}, maxBytes = SESSION_D
31020
31200
  }
31021
31201
  return result;
31022
31202
  }
31023
- function capSessionDiscoveryPage(page, includeLastMessage) {
31024
- const projected = page.sessions.map((session) => {
31203
+ function capSessionDiscoveryPage(page2, includeLastMessage) {
31204
+ const projected = page2.sessions.map((session) => {
31025
31205
  const title = capSessionDiscoveryText(session.title, 200, session.titleOriginalChars);
31026
31206
  const goal = session.goal ? capSessionDiscoveryText(
31027
31207
  session.goal.text,
@@ -31108,29 +31288,29 @@ function capSessionDiscoveryPage(page, includeLastMessage) {
31108
31288
  }).length : 0;
31109
31289
  const lastKept = kept.at(-1);
31110
31290
  const droppedForByteCap = kept.length < projected.length;
31111
- const sourceLast = lastKept ? page.sessions.find((session) => session.id === lastKept.id) : void 0;
31291
+ const sourceLast = lastKept ? page2.sessions.find((session) => session.id === lastKept.id) : void 0;
31112
31292
  const nextCursor = droppedForByteCap ? sourceLast ? encodeSessionDiscoveryCursor({
31113
- orderBy: page.orderBy,
31293
+ orderBy: page2.orderBy,
31114
31294
  sortRank: sourceLast.sortRank,
31115
31295
  sortRevision: sourceLast.sortRevision,
31116
31296
  sortAt: sourceLast.sortAt,
31117
31297
  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;
31298
+ snapshotAt: page2.snapshotAt,
31299
+ snapshotRevision: page2.snapshotRevision,
31300
+ updatedAfter: page2.updatedAfter,
31301
+ filterHash: page2.filterHash,
31302
+ ...page2.originSiteId ? { originSiteId: page2.originSiteId } : {}
31303
+ }) : null : page2.nextCursor ? encodeSessionDiscoveryCursor(page2.nextCursor) : null;
31124
31304
  const result2 = {
31125
31305
  sessions: kept,
31126
- total: page.total,
31127
- hasMore: page.hasMore || droppedForByteCap,
31306
+ total: page2.total,
31307
+ hasMore: page2.hasMore || droppedForByteCap,
31128
31308
  nextCursor,
31129
- orderBy: page.orderBy,
31130
- snapshotAt: page.snapshotAt,
31131
- snapshotRevision: page.snapshotRevision,
31132
- updatedAfter: page.updatedAfter,
31133
- updatedThrough: page.updatedThrough,
31309
+ orderBy: page2.orderBy,
31310
+ snapshotAt: page2.snapshotAt,
31311
+ snapshotRevision: page2.snapshotRevision,
31312
+ updatedAfter: page2.updatedAfter,
31313
+ updatedThrough: page2.updatedThrough,
31134
31314
  ...includeLastMessage ? {
31135
31315
  latestMessagePreviewBudget: {
31136
31316
  bytes: previewBytes,
@@ -67405,7 +67585,8 @@ function registerDocumentRoutes(app, deps) {
67405
67585
  deps,
67406
67586
  await requireAccessGrantAuthorization8(c, deps, workspaceId, "documents:search"),
67407
67587
  "documents:search"
67408
- )
67588
+ ),
67589
+ settings: deps.settings
67409
67590
  }
67410
67591
  );
67411
67592
  await server.connect(transport);
@@ -67492,6 +67673,8 @@ import {
67492
67673
  prepareKnowledgeFile as prepareKnowledgeFile2,
67493
67674
  prepareKnowledgeSave as prepareKnowledgeSave2,
67494
67675
  searchKnowledgeEntries as searchKnowledgeEntries3,
67676
+ KnowledgeVectorFundingError,
67677
+ KnowledgeVectorQueryRejectedError,
67495
67678
  hasPermission as hasPermission13,
67496
67679
  knowledgeContextForAccess as knowledgeContextForAccess2,
67497
67680
  requireAccessGrantAuthorization as requireAccessGrantAuthorization9,
@@ -67531,6 +67714,18 @@ var WriteSettings = ReadSettings.extend({
67531
67714
  var Id = z22.uuid();
67532
67715
  function knowledgeHttpError(error) {
67533
67716
  if (error instanceof HTTPException42) throw error;
67717
+ if (error instanceof KnowledgeVectorFundingError)
67718
+ throw new ApiHttpError(402, {
67719
+ code: "payment_required",
67720
+ message: error.message,
67721
+ details: { code: error.code, keywordAvailable: true }
67722
+ });
67723
+ if (error instanceof KnowledgeVectorQueryRejectedError)
67724
+ throw new ApiHttpError(error.code === "quota" ? 429 : 422, {
67725
+ code: error.code === "quota" ? "limit_exceeded" : "validation_failed",
67726
+ message: error.message,
67727
+ details: { code: error.code, keywordAvailable: true }
67728
+ });
67534
67729
  if (error instanceof z22.ZodError)
67535
67730
  throw new HTTPException42(422, {
67536
67731
  message: error.issues[0]?.message ?? "Invalid Knowledge request"
@@ -67662,7 +67857,9 @@ function registerKnowledgeRoutes(app, deps) {
67662
67857
  deps.db,
67663
67858
  context,
67664
67859
  KnowledgeSavePreparationRequest2.parse(await c.req.json()),
67665
- () => deps.getDocumentServices().embedder
67860
+ () => deps.getDocumentServices().embedder,
67861
+ void 0,
67862
+ deps.settings
67666
67863
  )
67667
67864
  );
67668
67865
  })
@@ -67677,7 +67874,8 @@ function registerKnowledgeRoutes(app, deps) {
67677
67874
  deps.db,
67678
67875
  context,
67679
67876
  KnowledgeEntryListRequest3.parse(await c.req.json()),
67680
- () => deps.getDocumentServices().embedder
67877
+ () => deps.getDocumentServices().embedder,
67878
+ deps.settings
67681
67879
  )
67682
67880
  )
67683
67881
  )
@@ -67724,7 +67922,8 @@ function registerKnowledgeRoutes(app, deps) {
67724
67922
  ...query,
67725
67923
  ...query.limit ? { limit: Number(query.limit) } : {}
67726
67924
  }),
67727
- () => deps.getDocumentServices().embedder
67925
+ () => deps.getDocumentServices().embedder,
67926
+ deps.settings
67728
67927
  )
67729
67928
  );
67730
67929
  })
@@ -70394,7 +70593,8 @@ import { randomUUID as randomUUID7 } from "crypto";
70394
70593
  import {
70395
70594
  environmentsEncryptionKeyBytes as environmentsEncryptionKeyBytes5,
70396
70595
  resolveFirstPartyDelegationSecret as resolveFirstPartyDelegationSecret3,
70397
- resolveStreamTokenSecret as resolveStreamTokenSecret2
70596
+ resolveStreamTokenSecret as resolveStreamTokenSecret2,
70597
+ sandboxWarmRateMicrosPerSecond as sandboxWarmRateMicrosPerSecond3
70398
70598
  } from "@opengeni/config";
70399
70599
  import {
70400
70600
  BROWSER_CONTROL_WEBSOCKET_BEARER_PREFIX,
@@ -70412,6 +70612,8 @@ import {
70412
70612
  BrowserDownloadSaveRequest,
70413
70613
  BrowserDownloadSaveResponse,
70414
70614
  BrowserDownloadListResponse,
70615
+ BrowserDomReadRequest,
70616
+ BrowserDomReadResponse,
70415
70617
  BrowserExternalAuthCommand,
70416
70618
  BrowserOpenTargetRequest,
70417
70619
  BrowserObservation,
@@ -70423,6 +70625,7 @@ import {
70423
70625
  BrowserSessionListResponse,
70424
70626
  BrowserSessionMutationResponse,
70425
70627
  BrowserTargetListResponse,
70628
+ BrowserTargetState,
70426
70629
  CreateBrowserSessionRequest,
70427
70630
  ExternalAuthInteractiveRequest,
70428
70631
  ExternalAuthInteractiveResponse,
@@ -70454,6 +70657,7 @@ import {
70454
70657
  BrowserSessionNotFoundError,
70455
70658
  BrowserSessionOperationConflictError,
70456
70659
  BrowserSessionStateError as BrowserSessionStateError2,
70660
+ SandboxPaidComputeAdmissionError as SandboxPaidComputeAdmissionError3,
70457
70661
  completeBrowserSessionEnd,
70458
70662
  completeExternalAuth,
70459
70663
  completeBrowserDownloadSave,
@@ -70467,6 +70671,8 @@ import {
70467
70671
  dispatchExternalAuth,
70468
70672
  dispatchProtectedAuthFill,
70469
70673
  failBrowserSessionOperation,
70674
+ failPreparedBrowserSessionEnd,
70675
+ failPreparedBrowserSessionSuspend,
70470
70676
  failBrowserSessionResume,
70471
70677
  failBrowserSessionResumePreparation,
70472
70678
  failBrowserSessionSuspension,
@@ -72355,6 +72561,68 @@ function registerBrowserSessionRoutes(app, deps) {
72355
72561
  return context.json(result);
72356
72562
  }
72357
72563
  );
72564
+ app.get(
72565
+ "/v1/workspaces/:workspaceId/browser-sessions/:browserSessionId/targets/:targetId/state",
72566
+ async (context) => {
72567
+ const { workspaceId, grant, browserSessionId } = await browserRoutePreamble(
72568
+ context,
72569
+ "sessions:read"
72570
+ );
72571
+ const targetId = requireOpaqueParam(context, "targetId");
72572
+ const result = await withActiveBrowserController(
72573
+ context,
72574
+ grant,
72575
+ workspaceId,
72576
+ browserSessionId,
72577
+ "session.read",
72578
+ "browser.read",
72579
+ async ({ sessionClient }) => await sessionClient.targetState(targetId)
72580
+ );
72581
+ return context.json(BrowserTargetState.parse(result));
72582
+ }
72583
+ );
72584
+ app.post(
72585
+ "/v1/workspaces/:workspaceId/browser-sessions/:browserSessionId/targets/:targetId/dom-read",
72586
+ async (context) => {
72587
+ const { workspaceId, grant, browserSessionId } = await browserRoutePreamble(
72588
+ context,
72589
+ "sessions:read"
72590
+ );
72591
+ const targetId = requireOpaqueParam(context, "targetId");
72592
+ const request = await parseJsonBody2(context, BrowserDomReadRequest);
72593
+ const result = await withActiveBrowserController(
72594
+ context,
72595
+ grant,
72596
+ workspaceId,
72597
+ browserSessionId,
72598
+ "session.read",
72599
+ "browser.read",
72600
+ async ({ sessionClient }) => await sessionClient.readDom(targetId, request)
72601
+ );
72602
+ return context.json(BrowserDomReadResponse.parse(result));
72603
+ }
72604
+ );
72605
+ app.get(
72606
+ "/v1/workspaces/:workspaceId/browser-sessions/:browserSessionId/targets/:targetId/screenshot",
72607
+ async (context) => {
72608
+ const { workspaceId, grant, browserSessionId } = await browserRoutePreamble(
72609
+ context,
72610
+ "sessions:read"
72611
+ );
72612
+ const targetId = requireOpaqueParam(context, "targetId");
72613
+ const captureOptions = parseBrowserScreenshotOptions(new URL(context.req.url).searchParams);
72614
+ const frame = await withActiveBrowserController(
72615
+ context,
72616
+ grant,
72617
+ workspaceId,
72618
+ browserSessionId,
72619
+ "session.read",
72620
+ "browser.read",
72621
+ async ({ sessionClient }) => await sessionClient.capture(targetId, captureOptions)
72622
+ );
72623
+ return browserScreenshotResponse(frame);
72624
+ }
72625
+ );
72358
72626
  app.post(
72359
72627
  "/v1/workspaces/:workspaceId/browser-sessions/:browserSessionId/actions",
72360
72628
  async (context) => {
@@ -73388,6 +73656,7 @@ function registerBrowserSessionRoutes(app, deps) {
73388
73656
  const request = await parseJsonBody2(context, BrowserSessionLifecycleRequest);
73389
73657
  const startedAtMs = performance.now();
73390
73658
  const origin = requestOrigin(context, deps.settings);
73659
+ let preparedForDispatch = false;
73391
73660
  try {
73392
73661
  const before = await getBrowserSessionControlRecord(deps.db, {
73393
73662
  accountId: grant.accountId,
@@ -73436,6 +73705,7 @@ function registerBrowserSessionRoutes(app, deps) {
73436
73705
  observeLifecycleResult(deps.observability, startedAtMs, parsed2);
73437
73706
  return context.json(parsed2, 200);
73438
73707
  }
73708
+ preparedForDispatch = true;
73439
73709
  const record6 = await getBrowserSessionControlRecord(deps.db, {
73440
73710
  accountId: grant.accountId,
73441
73711
  workspaceId,
@@ -73594,6 +73864,15 @@ function registerBrowserSessionRoutes(app, deps) {
73594
73864
  observeLifecycleResult(deps.observability, startedAtMs, parsed);
73595
73865
  return context.json(parsed, 200);
73596
73866
  } catch (error) {
73867
+ if (preparedForDispatch) {
73868
+ await failPreparedBrowserSessionSuspend(deps.db, {
73869
+ accountId: grant.accountId,
73870
+ workspaceId,
73871
+ browserSessionId,
73872
+ operationId: request.operationId,
73873
+ error: interactionFailure(error)
73874
+ });
73875
+ }
73597
73876
  throw browserRouteError(error);
73598
73877
  }
73599
73878
  }
@@ -73844,6 +74123,7 @@ function registerBrowserSessionRoutes(app, deps) {
73844
74123
  const request = await parseJsonBody2(context, BrowserSessionLifecycleRequest);
73845
74124
  const startedAtMs = performance.now();
73846
74125
  const origin = requestOrigin(context, deps.settings);
74126
+ let endPreparation = null;
73847
74127
  try {
73848
74128
  const before = await getBrowserSessionControlRecord(deps.db, {
73849
74129
  accountId: grant.accountId,
@@ -73857,7 +74137,10 @@ function registerBrowserSessionRoutes(app, deps) {
73857
74137
  workspaceId,
73858
74138
  browserSessionId,
73859
74139
  operationId: request.operationId,
73860
- actorSubjectId: grant.subjectId
74140
+ actorSubjectId: grant.subjectId,
74141
+ expectedLifecycle: before.session.lifecycle,
74142
+ expectedFailureCode: before.session.failureCode,
74143
+ expectedControllerGeneration: before.session.controller?.controllerGeneration ?? null
73861
74144
  });
73862
74145
  if (isTerminalOperation(prepared.operation.state)) {
73863
74146
  if (prepared.operation.state === "completed") {
@@ -73873,6 +74156,13 @@ function registerBrowserSessionRoutes(app, deps) {
73873
74156
  observeLifecycleResult(deps.observability, startedAtMs, parsed2);
73874
74157
  return context.json(parsed2, 200);
73875
74158
  }
74159
+ if (before.session.lifecycle !== "ending") {
74160
+ endPreparation = {
74161
+ restoreLifecycle: before.session.lifecycle,
74162
+ restoreFailureCode: before.session.failureCode,
74163
+ expectedControllerGeneration: before.session.controller?.controllerGeneration ?? null
74164
+ };
74165
+ }
73876
74166
  const record6 = await getBrowserSessionControlRecord(deps.db, {
73877
74167
  accountId: grant.accountId,
73878
74168
  workspaceId,
@@ -73962,6 +74252,16 @@ function registerBrowserSessionRoutes(app, deps) {
73962
74252
  observeLifecycleResult(deps.observability, startedAtMs, parsed);
73963
74253
  return context.json(parsed, 200);
73964
74254
  } catch (error) {
74255
+ if (endPreparation) {
74256
+ await failPreparedBrowserSessionEnd(deps.db, {
74257
+ accountId: grant.accountId,
74258
+ workspaceId,
74259
+ browserSessionId,
74260
+ operationId: request.operationId,
74261
+ ...endPreparation,
74262
+ error: interactionFailure(error)
74263
+ });
74264
+ }
73965
74265
  throw browserRouteError(error);
73966
74266
  }
73967
74267
  }
@@ -74082,6 +74382,11 @@ function registerBrowserSessionRoutes(app, deps) {
74082
74382
  const resolved = await handle.routingSession.prime();
74083
74383
  if (expectedPlacement?.kind === "connected_machine") {
74084
74384
  if (resolved.kind !== "selfhosted" || resolved.sandboxId !== expectedPlacement.sandboxId) {
74385
+ if (operation === "browser.create") {
74386
+ throw new HTTPException51(422, {
74387
+ message: "The requested Connected Machine is not this session's current placement. Move the session to that machine before creating an interaction resource."
74388
+ });
74389
+ }
74085
74390
  return await throwBrowserSourcePlacementChanged(
74086
74391
  grant,
74087
74392
  sourceSession.id,
@@ -74797,6 +75102,10 @@ async function ensureInteractionHolder(deps, grant, sourceSession, browserSessio
74797
75102
  holderId: interactionHolderId(browserSessionId),
74798
75103
  subjectId: sourceSession.id,
74799
75104
  backend: placement.lease.backend,
75105
+ warmBilling: {
75106
+ mode: deps.settings.sandboxWarmBillingMode,
75107
+ rateMicrosPerSecond: sandboxWarmRateMicrosPerSecond3(deps.settings, placement.lease.backend)
75108
+ },
74800
75109
  os: placement.lease.os,
74801
75110
  image: sandboxRuntime.image,
74802
75111
  rigVersionId: sourceSession.rigVersionId,
@@ -74994,6 +75303,7 @@ async function settleBrowserSessionSuspensionCaptureFailure(deps, input, error)
74994
75303
  return await failBrowserSessionSuspension(deps.db, {
74995
75304
  ...input,
74996
75305
  ...outcomeUnknown ? { state: "outcome_unknown" } : {},
75306
+ missingControllerSession: error.status === 404 && isMissingBrowserControllerSession(error),
74997
75307
  error: error.error
74998
75308
  });
74999
75309
  }
@@ -75086,6 +75396,41 @@ function optionalBoundedInteger(value, minimum, maximum) {
75086
75396
  }
75087
75397
  return parsed;
75088
75398
  }
75399
+ function parseBrowserScreenshotOptions(query) {
75400
+ for (const key of ["fullPage", "format", "quality"]) {
75401
+ if (query.getAll(key).length > 1) {
75402
+ throw new HTTPException51(400, { message: `duplicate browser screenshot ${key}` });
75403
+ }
75404
+ }
75405
+ const fullPage = query.get("fullPage");
75406
+ if (fullPage !== null && fullPage !== "true" && fullPage !== "false") {
75407
+ throw new HTTPException51(400, { message: "invalid browser screenshot fullPage" });
75408
+ }
75409
+ const format = query.get("format");
75410
+ if (format !== null && format !== "jpeg" && format !== "png") {
75411
+ throw new HTTPException51(400, { message: "invalid browser screenshot format" });
75412
+ }
75413
+ const quality = query.get("quality");
75414
+ const parsedQuality = quality === null ? null : optionalBoundedInteger(quality, 1, 100);
75415
+ if (quality !== null && parsedQuality === null) {
75416
+ throw new HTTPException51(400, { message: "invalid browser screenshot quality" });
75417
+ }
75418
+ return {
75419
+ ...fullPage === null ? {} : { fullPage: fullPage === "true" },
75420
+ ...format === null ? {} : { format },
75421
+ ...parsedQuality === null ? {} : { quality: parsedQuality }
75422
+ };
75423
+ }
75424
+ function browserScreenshotResponse(frame) {
75425
+ return new Response(Uint8Array.from(frame.data).buffer, {
75426
+ status: 200,
75427
+ headers: {
75428
+ "cache-control": "no-store",
75429
+ "content-type": frame.mediaType,
75430
+ "x-opengeni-browser-frame": frame.metadataHeader
75431
+ }
75432
+ });
75433
+ }
75089
75434
  function isUuid(value) {
75090
75435
  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
75436
  }
@@ -75480,6 +75825,8 @@ function browserRouteError(error) {
75480
75825
  const connectedMachineError = interactionControlApiError(error, "browser");
75481
75826
  if (connectedMachineError) return connectedMachineError;
75482
75827
  if (error instanceof HTTPException51) return error;
75828
+ if (error instanceof SandboxPaidComputeAdmissionError3)
75829
+ return new HTTPException51(402, { message: error.message, cause: error });
75483
75830
  if (error instanceof BrowserSessionNotFoundError) {
75484
75831
  return new HTTPException51(404, { message: error.message, cause: error });
75485
75832
  }
@@ -75533,7 +75880,11 @@ function isAbort(error) {
75533
75880
 
75534
75881
  // src/routes/computer-sessions.ts
75535
75882
  import { randomUUID as randomUUID8 } from "crypto";
75536
- import { resolveFirstPartyDelegationSecret as resolveFirstPartyDelegationSecret4, resolveStreamTokenSecret as resolveStreamTokenSecret3 } from "@opengeni/config";
75883
+ import {
75884
+ resolveFirstPartyDelegationSecret as resolveFirstPartyDelegationSecret4,
75885
+ resolveStreamTokenSecret as resolveStreamTokenSecret3,
75886
+ sandboxWarmRateMicrosPerSecond as sandboxWarmRateMicrosPerSecond4
75887
+ } from "@opengeni/config";
75537
75888
  import {
75538
75889
  BROWSER_CONTROL_WEBSOCKET_BEARER_PREFIX as BROWSER_CONTROL_WEBSOCKET_BEARER_PREFIX2,
75539
75890
  BROWSER_CONTROL_PORT as BROWSER_CONTROL_PORT2,
@@ -75560,6 +75911,7 @@ import {
75560
75911
  ComputerSessionNotFoundError,
75561
75912
  ComputerSessionOperationConflictError,
75562
75913
  ComputerSessionStateError,
75914
+ SandboxPaidComputeAdmissionError as SandboxPaidComputeAdmissionError4,
75563
75915
  dispatchComputerSessionOperation,
75564
75916
  failComputerSessionOperation,
75565
75917
  findComputerSessionControlRecordByOperation,
@@ -76447,6 +76799,11 @@ function registerComputerSessionRoutes(app, deps) {
76447
76799
  }
76448
76800
  if (expectedPlacement?.kind === "connected_machine") {
76449
76801
  if (resolved.kind !== "selfhosted" || resolved.sandboxId !== expectedPlacement.sandboxId) {
76802
+ if (operation === "computer.create") {
76803
+ throw new HTTPException52(422, {
76804
+ message: "The requested Connected Machine is not this session's current placement. Move the session to that machine before creating an interaction resource."
76805
+ });
76806
+ }
76450
76807
  return await throwComputerSourcePlacementChanged(
76451
76808
  grant,
76452
76809
  sourceSession.id,
@@ -76733,6 +77090,10 @@ function registerComputerSessionRoutes(app, deps) {
76733
77090
  holderId: interactionHolderId2(computerSessionId),
76734
77091
  subjectId: sourceSession.id,
76735
77092
  backend: placement.lease.backend,
77093
+ warmBilling: {
77094
+ mode: deps.settings.sandboxWarmBillingMode,
77095
+ rateMicrosPerSecond: sandboxWarmRateMicrosPerSecond4(deps.settings, placement.lease.backend)
77096
+ },
76736
77097
  os: placement.lease.os,
76737
77098
  image: sandboxRuntime.image,
76738
77099
  rigVersionId: sourceSession.rigVersionId,
@@ -77055,6 +77416,8 @@ function computerRouteError(error) {
77055
77416
  const connectedMachineError = interactionControlApiError(error, "computer");
77056
77417
  if (connectedMachineError) return connectedMachineError;
77057
77418
  if (error instanceof HTTPException52) return error;
77419
+ if (error instanceof SandboxPaidComputeAdmissionError4)
77420
+ return new HTTPException52(402, { message: error.message, cause: error });
77058
77421
  if (error instanceof ComputerSessionNotFoundError) {
77059
77422
  return new HTTPException52(404, { message: error.message, cause: error });
77060
77423
  }
@@ -77203,6 +77566,92 @@ async function integrationCommitGrant(authorization, permissions, request) {
77203
77566
  };
77204
77567
  }
77205
77568
 
77569
+ // src/routes/github-browser-pages.ts
77570
+ var pageStyle = `
77571
+ :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)}
77572
+ *{box-sizing:border-box}
77573
+ html{min-height:100%;background:var(--bg)}
77574
+ 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}
77575
+ .shell{width:min(100% - 48px,700px);margin:0 auto}
77576
+ header{height:84px;display:flex;align-items:center;justify-content:space-between;gap:16px;border-bottom:1px solid var(--border)}
77577
+ .brand{font-size:16px;font-weight:670;letter-spacing:-.045em;color:var(--fg);text-decoration:none}
77578
+ .context{font-size:12px;color:var(--muted)}
77579
+ main{padding:clamp(48px,10vh,100px) 0 96px}
77580
+ h1{margin:0 0 14px;font-size:clamp(27px,4vw,36px);font-weight:640;letter-spacing:-.05em;line-height:1.17}
77581
+ p{margin:0;color:var(--muted);font-size:14px;line-height:1.65;max-width:59ch}
77582
+ .intro{margin-bottom:32px}
77583
+ .options{display:grid;gap:10px;margin:0 0 24px;padding:0;border:0;max-height:min(390px,52vh);overflow:auto}
77584
+ .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}
77585
+ .option:hover{background:var(--raised)}
77586
+ .option:has(input:checked){border-color:var(--blue-light);background:var(--raised)}
77587
+ .option:has(input:focus-visible){outline:2px solid var(--blue-light);outline-offset:3px}
77588
+ .option input{margin:0;flex:none;width:17px;height:17px;accent-color:var(--blue-light)}
77589
+ .option-text{min-width:0;display:grid;gap:3px}
77590
+ .option strong{overflow-wrap:anywhere;font-size:14px;font-weight:600}
77591
+ .option small{font-size:12px;color:var(--muted)}
77592
+ .actions{display:flex;align-items:center;flex-wrap:wrap;gap:12px;margin-top:24px}
77593
+ .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}
77594
+ .button:hover{background:oklch(.6 .18 255)}
77595
+ .button:disabled{cursor:wait;opacity:.7}
77596
+ .secondary{border-color:var(--border);background:transparent;color:var(--fg)}
77597
+ .secondary:hover{background:var(--raised)}
77598
+ :is(button,a):focus-visible{outline:2px solid var(--blue-light);outline-offset:3px}
77599
+ .note{margin-top:30px;padding-top:20px;border-top:1px solid var(--border);font-size:12px}
77600
+ .env-header{display:flex;justify-content:space-between;align-items:center;gap:14px;margin-top:36px}
77601
+ h2{margin:0;font-size:14px;font-weight:600}
77602
+ 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}
77603
+ @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%}}
77604
+ @media(prefers-reduced-motion:reduce){.option{transition:none}}
77605
+ `;
77606
+ function page(title, content) {
77607
+ 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>`;
77608
+ }
77609
+ function githubInstallationChooserHtml(candidates, state, workspaceId, baseUrl) {
77610
+ const action = `${baseUrl}/v1/workspaces/${encodeURIComponent(workspaceId)}/github/installations/select`;
77611
+ const options = candidates.map(({ installation, authorityKind }, index) => {
77612
+ const account = escapeHtml4(
77613
+ installation.accountLogin ?? `installation ${installation.installationId}`
77614
+ );
77615
+ const label = authorityKind === "personal_owner" ? "Personal account" : "Organization owner";
77616
+ 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>`;
77617
+ }).join("");
77618
+ return page(
77619
+ "Choose a GitHub account",
77620
+ `<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>`
77621
+ );
77622
+ }
77623
+ function githubSetupSuccessHtml(account, returnUrl2) {
77624
+ return page(
77625
+ "GitHub connected",
77626
+ `<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>`
77627
+ );
77628
+ }
77629
+ function githubSetupPendingHtml() {
77630
+ return page(
77631
+ "GitHub approval pending",
77632
+ `<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>`
77633
+ );
77634
+ }
77635
+ function githubSuccessHtml(envLines) {
77636
+ const escaped = escapeHtml4(envLines.join("\n"));
77637
+ return page(
77638
+ "GitHub App created",
77639
+ `<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>`
77640
+ );
77641
+ }
77642
+ function escapeHtml4(value) {
77643
+ return value.replace(
77644
+ /[&<>"']/g,
77645
+ (char) => ({
77646
+ "&": "&amp;",
77647
+ "<": "&lt;",
77648
+ ">": "&gt;",
77649
+ '"': "&quot;",
77650
+ "'": "&#39;"
77651
+ })[char] ?? char
77652
+ );
77653
+ }
77654
+
77206
77655
  // src/routes/github.ts
77207
77656
  var githubStateCookie = "opengeni_github_state";
77208
77657
  var githubBindingStateMaxAgeSeconds = 10 * 60;
@@ -78006,28 +78455,6 @@ function requirePublicGitHubRepositoryVerificationPermission(grant) {
78006
78455
  function isSecureRequest(c, deps) {
78007
78456
  return deps.settings.publicBaseUrl?.startsWith("https://") || c.req.header("x-forwarded-proto") === "https" || new URL(c.req.url).protocol === "https:";
78008
78457
  }
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
78458
  function parsePositiveInteger(value) {
78032
78459
  if (!value || !/^\d+$/.test(value)) {
78033
78460
  return null;
@@ -78039,18 +78466,6 @@ function isFreshGitHubBindingState(payload) {
78039
78466
  const age = Math.floor(Date.now() / 1e3) - payload.iat;
78040
78467
  return age >= 0 && age < githubBindingStateMaxAgeSeconds;
78041
78468
  }
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
78469
  function openGeniReturnUrl(settings, c, workspaceId, returnPath = null) {
78055
78470
  const baseUrl = openGeniBaseUrl(settings, c) || new URL(c.req.url).origin;
78056
78471
  const safeReturnPath4 = githubSessionReturnPath(returnPath, workspaceId);
@@ -78636,7 +79051,7 @@ function registerPersonalGitHubRoutes(app, deps) {
78636
79051
  const query = ListPersonalGitHubRepositoriesQuery.parse(c.req.query());
78637
79052
  try {
78638
79053
  const selection = await loadRepositorySelectionState(deps, access.grant, connection);
78639
- const page = await listLivePersonalGitHubRepositories(deps, {
79054
+ const page2 = await listLivePersonalGitHubRepositories(deps, {
78640
79055
  accountId: access.grant.accountId,
78641
79056
  workspaceId,
78642
79057
  subjectId: access.grant.subjectId,
@@ -78653,11 +79068,11 @@ function registerPersonalGitHubRoutes(app, deps) {
78653
79068
  );
78654
79069
  return c.json(
78655
79070
  ListPersonalGitHubRepositoriesResponse.parse({
78656
- repositories: page.repositories.map((repository) => ({
79071
+ repositories: page2.repositories.map((repository) => ({
78657
79072
  ...repository,
78658
79073
  selectedAccess: selectedAccessById.get(repository.repositoryId) ?? null
78659
79074
  })),
78660
- nextCursor: page.nextPage,
79075
+ nextCursor: page2.nextPage,
78661
79076
  selection
78662
79077
  })
78663
79078
  );
@@ -79991,11 +80406,11 @@ async function sseSessionStream(db, bus, workspaceId, sessionId, after, signal,
79991
80406
  if (targetSequence !== void 0 && lastSent >= targetSequence) return;
79992
80407
  const previousLastSent = lastSent;
79993
80408
  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, {
80409
+ const page2 = await listSessionEvents(db, workspaceId, sessionId, {
79995
80410
  after: lastSent,
79996
80411
  limit
79997
80412
  });
79998
- const eligible = targetSequence === void 0 ? page : page.filter((event) => event.sequence <= targetSequence);
80413
+ const eligible = targetSequence === void 0 ? page2 : page2.filter((event) => event.sequence <= targetSequence);
79999
80414
  if (eligible.length === 0) {
80000
80415
  if (targetSequence === void 0) return;
80001
80416
  throw new Error(
@@ -80105,16 +80520,16 @@ async function replaySessionEvents(loadPage, send, after, pageSize = SESSION_REP
80105
80520
  let cursor = after;
80106
80521
  while (true) {
80107
80522
  const previousCursor = cursor;
80108
- const page = await loadPage(cursor, pageSize);
80109
- if (page.length === 0) {
80523
+ const page2 = await loadPage(cursor, pageSize);
80524
+ if (page2.length === 0) {
80110
80525
  return;
80111
80526
  }
80112
- for (const event of page.sort((a, b) => a.sequence - b.sequence)) {
80527
+ for (const event of page2.sort((a, b) => a.sequence - b.sequence)) {
80113
80528
  if (event.sequence <= cursor) continue;
80114
80529
  await send(event);
80115
80530
  cursor = event.sequence;
80116
80531
  }
80117
- if (page.length < pageSize) {
80532
+ if (page2.length < pageSize) {
80118
80533
  return;
80119
80534
  }
80120
80535
  if (cursor === previousCursor) {
@@ -80506,14 +80921,14 @@ async function replayWorkspaceControlEvents(loadPage, send, after, pageSize) {
80506
80921
  let cursor = after;
80507
80922
  while (true) {
80508
80923
  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)) {
80924
+ const page2 = await loadPage(cursor, pageSize);
80925
+ if (page2.length === 0) return;
80926
+ for (const event of page2.sort((a, b) => a.sequence - b.sequence)) {
80512
80927
  if (event.sequence <= cursor) continue;
80513
80928
  await send(event);
80514
80929
  cursor = event.sequence;
80515
80930
  }
80516
- if (page.length < pageSize) return;
80931
+ if (page2.length < pageSize) return;
80517
80932
  if (cursor === previousCursor) {
80518
80933
  throw new Error(
80519
80934
  `Workspace control replay made no progress after sequence ${cursor}; refusing to repeat a full stale page`
@@ -84605,10 +85020,10 @@ function registerSkillContentRoutes(app, deps) {
84605
85020
  ...query.data.sessionId ? { sessionId: query.data.sessionId } : {}
84606
85021
  }
84607
85022
  );
84608
- const page = rows2.slice(0, query.data.limit);
84609
- const last = page.at(-1);
85023
+ const page2 = rows2.slice(0, query.data.limit);
85024
+ const last = page2.at(-1);
84610
85025
  return c.json({
84611
- skills: page.map(({ files: _files, ...summary }) => summary),
85026
+ skills: page2.map(({ files: _files, ...summary }) => summary),
84612
85027
  nextCursor: rows2.length > query.data.limit && last ? Buffer.from(JSON.stringify({ stableKey: last.stableKey, id: last.id })).toString(
84613
85028
  "base64url"
84614
85029
  ) : null
@@ -85665,6 +86080,8 @@ function registerScheduledTaskRoutes(app, deps) {
85665
86080
  // src/routes/sessions.ts
85666
86081
  import { getRetainedProviderCommand } from "@opengeni/db/retained-provider-commands";
85667
86082
  import { searchSessionMessagesForSubject, SessionMessageSearchCursorError } from "@opengeni/db";
86083
+ import { listSessionEventSlices } from "@opengeni/db/session-event-slices";
86084
+ import * as sessionPreviewSchema from "@opengeni/db/schema";
85668
86085
  import { SessionMessageSearchRequest } from "@opengeni/contracts";
85669
86086
  import { scheduledSessionIds } from "@opengeni/db";
85670
86087
  import { withSiteSessionOrigin as withSiteSessionOrigin2 } from "@opengeni/core";
@@ -87260,9 +87677,9 @@ function registerSessionRoutes(app, deps) {
87260
87677
  }
87261
87678
  const pageView = c.req.query("view") === "page";
87262
87679
  const query = sessionListQuery(c.req.query(), pageView);
87263
- let page;
87680
+ let page2;
87264
87681
  try {
87265
- page = await listSessionsForSubject(db, workspaceId, {
87682
+ page2 = await listSessionsForSubject(db, workspaceId, {
87266
87683
  subjectId: grant.subjectId,
87267
87684
  limit: boundedLimit(query.limit),
87268
87685
  materializeSnapshot: pageView,
@@ -87299,17 +87716,17 @@ function registerSessionRoutes(app, deps) {
87299
87716
  }
87300
87717
  throw error;
87301
87718
  }
87302
- c.header("x-opengeni-pinned-truncated", page.pinnedTruncated === true ? "true" : "false");
87719
+ c.header("x-opengeni-pinned-truncated", page2.pinnedTruncated === true ? "true" : "false");
87303
87720
  const policy = await loadEffectivePolicyContext(deps, workspaceId, grant.subjectId);
87304
87721
  const commandActivity = await backgroundCommandActivityForSessions(db, {
87305
87722
  accountId: grant.accountId,
87306
87723
  workspaceId,
87307
- sessionIds: [...page.pinned, ...page.sessions].map((session) => session.id)
87724
+ sessionIds: [...page2.pinned, ...page2.sessions].map((session) => session.id)
87308
87725
  });
87309
87726
  const scheduleTargets = hasPermission19(grant.permissions, "scheduled_tasks:run") && hasPermission19(grant.permissions, "sessions:control") ? await scheduledSessionIds(
87310
87727
  db,
87311
87728
  workspaceId,
87312
- [...page.pinned, ...page.sessions].map((session) => session.id)
87729
+ [...page2.pinned, ...page2.sessions].map((session) => session.id)
87313
87730
  ) : /* @__PURE__ */ new Set();
87314
87731
  const decorate = (session) => {
87315
87732
  const activity = commandActivity.get(session.id);
@@ -87325,14 +87742,14 @@ function registerSessionRoutes(app, deps) {
87325
87742
  };
87326
87743
  if (pageView) {
87327
87744
  return c.json({
87328
- ...page,
87745
+ ...page2,
87329
87746
  ...query.hasPageFilters ? { filtersApplied: true } : {},
87330
87747
  ...query.originSiteId ? { originSiteId: query.originSiteId } : {},
87331
- pinned: page.pinned.map(decorate),
87332
- sessions: page.sessions.map(decorate)
87748
+ pinned: page2.pinned.map(decorate),
87749
+ sessions: page2.sessions.map(decorate)
87333
87750
  });
87334
87751
  }
87335
- return c.json([...page.pinned, ...page.sessions].map(decorate));
87752
+ return c.json([...page2.pinned, ...page2.sessions].map(decorate));
87336
87753
  });
87337
87754
  app.get("/v1/workspaces/:workspaceId/agent-topology", async (c) => {
87338
87755
  const workspaceId = c.req.param("workspaceId");
@@ -87365,7 +87782,7 @@ function registerSessionRoutes(app, deps) {
87365
87782
  }
87366
87783
  try {
87367
87784
  const orderBy = query.query || query.subject ? "relevance" : "updatedAt";
87368
- const page = await listSessionDiscoverySummaries2(db, workspaceId, {
87785
+ const page2 = await listSessionDiscoverySummaries2(db, workspaceId, {
87369
87786
  limit: query.limit,
87370
87787
  orderBy,
87371
87788
  subjectId: grant.subjectId,
@@ -87384,11 +87801,11 @@ function registerSessionRoutes(app, deps) {
87384
87801
  const ancestorPaths = query.query || query.subject ? await listSessionDiscoveryAncestorPaths(
87385
87802
  db,
87386
87803
  workspaceId,
87387
- page.sessions.map((session) => session.id),
87804
+ page2.sessions.map((session) => session.id),
87388
87805
  authorizationScope ?? void 0,
87389
87806
  grant.subjectId
87390
87807
  ) : /* @__PURE__ */ new Map();
87391
- const sessions = page.sessions.map((session) => {
87808
+ const sessions = page2.sessions.map((session) => {
87392
87809
  const blocker = session.effectiveControl.primaryBlocker;
87393
87810
  return {
87394
87811
  id: session.id,
@@ -87426,11 +87843,11 @@ function registerSessionRoutes(app, deps) {
87426
87843
  });
87427
87844
  const response = {
87428
87845
  sessions,
87429
- total: page.total,
87430
- hasMore: page.hasMore,
87846
+ total: page2.total,
87847
+ hasMore: page2.hasMore,
87431
87848
  humanAdvisoriesEnabled: settings.workDiscoveryEnabled && settings.workDiscoveryHumanAdvisoriesEnabled,
87432
- nextCursor: page.nextCursor ? encodeAgentTopologyCursor({
87433
- cursor: page.nextCursor,
87849
+ nextCursor: page2.nextCursor ? encodeAgentTopologyCursor({
87850
+ cursor: page2.nextCursor,
87434
87851
  parentSessionId: query.parentSessionId === void 0 ? "all" : query.parentSessionId,
87435
87852
  rootSessionId: query.rootSessionId ?? null,
87436
87853
  query: query.query ?? null,
@@ -89211,35 +89628,94 @@ function registerSessionRoutes(app, deps) {
89211
89628
  const compactProjection = compact ? coalesceSessionEventDeltasWithCoverage2(events) : null;
89212
89629
  const projected = compactProjection?.events ?? events;
89213
89630
  const forensicExact = !compact && mode === "forensic" && payloadMode === "full" && dbPage.fullPayloadsExact;
89214
- const page = boundSessionEventHttpPage(projected, {
89631
+ const page2 = boundSessionEventHttpPage(projected, {
89215
89632
  direction,
89216
89633
  eventProjection: payloadMode === "full" ? "exact" : "bounded",
89217
89634
  ...compactProjection ? { coveredThroughBySequence: compactProjection.coveredThroughBySequence } : {}
89218
89635
  });
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)));
89636
+ const hasMore = dbPage.hasMore || page2.truncated;
89637
+ c.header("X-OpenGeni-Page-Bytes", String(page2.bytes));
89638
+ c.header("X-OpenGeni-Page-Max-Bytes", String(Math.max(1024 * 1024, page2.bytes)));
89222
89639
  c.header("X-OpenGeni-Page-Truncated", String(hasMore));
89223
89640
  c.header("X-OpenGeni-Has-More", String(hasMore));
89224
89641
  c.header("X-OpenGeni-Event-Mode", mode);
89225
89642
  c.header("X-OpenGeni-Event-Direction", direction);
89226
89643
  c.header("X-OpenGeni-Payload-Mode", payloadMode);
89227
89644
  c.header("X-OpenGeni-Forensic-Exact", String(forensicExact));
89228
- const coveredFirst = page.events[0]?.sequence;
89229
- const coveredLastEvent = page.events.at(-1);
89645
+ const coveredFirst = page2.events[0]?.sequence;
89646
+ const coveredLastEvent = page2.events.at(-1);
89230
89647
  const coveredLast = coveredLastEvent === void 0 ? void 0 : compactProjection?.coveredThroughBySequence.get(coveredLastEvent.sequence) ?? coveredLastEvent.sequence;
89231
89648
  if (coveredFirst !== void 0) c.header("X-OpenGeni-Covered-First", String(coveredFirst));
89232
89649
  if (coveredLast !== void 0) c.header("X-OpenGeni-Covered-Last", String(coveredLast));
89233
- const truncatedBy = page.truncated ? "http_bytes" : dbPage.truncatedBy;
89650
+ const truncatedBy = page2.truncated ? "http_bytes" : dbPage.truncatedBy;
89234
89651
  if (truncatedBy) c.header("X-OpenGeni-Truncated-By", truncatedBy);
89235
- if (page.nextSequence !== null) {
89652
+ if (page2.nextSequence !== null) {
89236
89653
  c.header(
89237
89654
  direction === "before" ? "X-OpenGeni-Next-Before" : "X-OpenGeni-Next-After",
89238
- String(page.nextSequence)
89655
+ String(page2.nextSequence)
89239
89656
  );
89240
89657
  }
89241
- return c.json(page.events);
89658
+ return c.json(page2.events);
89242
89659
  });
89660
+ app.get(
89661
+ "/v1/workspaces/:workspaceId/sessions/:sessionId/events/:eventId/message-preview",
89662
+ async (c) => {
89663
+ const workspaceId = c.req.param("workspaceId");
89664
+ await requireAccessGrant32(c, deps, workspaceId, "sessions:read");
89665
+ const sessionId = c.req.param("sessionId");
89666
+ await assertSessionExists(db, workspaceId, sessionId);
89667
+ const eventId = c.req.param("eventId");
89668
+ const rawSequence = c.req.query("sequence");
89669
+ const sequence = Number(rawSequence);
89670
+ if (!z27.string().uuid().safeParse(eventId).success || rawSequence === void 0 || !/^\d+$/.test(rawSequence) || !Number.isSafeInteger(sequence) || sequence < 1 || sequence > 2147483647)
89671
+ throw new HTTPException72(400, { message: "Invalid message preview reference" });
89672
+ const notFound = () => new HTTPException72(404, { message: "message not found" });
89673
+ const maxUnits = 12e3;
89674
+ const eventTable = sessionPreviewSchema.sessionEvents;
89675
+ const candidates = await withWorkspaceRls(
89676
+ db,
89677
+ workspaceId,
89678
+ (tx) => tx.select({ kind: sql`jsonb_typeof(${eventTable.payload}->'text')` }).from(eventTable).where(
89679
+ and(
89680
+ eq(eventTable.workspaceId, workspaceId),
89681
+ eq(eventTable.sessionId, sessionId),
89682
+ eq(eventTable.id, eventId),
89683
+ eq(eventTable.sequence, sequence),
89684
+ sql`${eventTable.type} in ('user.message', 'agent.message.completed')`
89685
+ )
89686
+ ).limit(1)
89687
+ );
89688
+ if (candidates[0]?.kind !== "string") throw notFound();
89689
+ const read = (offset2) => listSessionEventSlices(db, workspaceId, sessionId, {
89690
+ sourceSequence: sequence,
89691
+ sourceOffset: offset2,
89692
+ after: sequence - 1,
89693
+ before: sequence + 1,
89694
+ includeTypes: ["user.message", "agent.message.completed"],
89695
+ view: "conversation"
89696
+ });
89697
+ const first = await read(0);
89698
+ const event = first.events[0];
89699
+ const slice = first.slices?.[sequence];
89700
+ if (!event || event.id !== eventId || event.sequence !== sequence || !slice || slice.omitted)
89701
+ throw notFound();
89702
+ if (slice.total > maxUnits) return c.json({ status: "unavailable" });
89703
+ let text = slice.text;
89704
+ let offset = slice.unit === "utf16" ? slice.text.length : Array.from(slice.text).length;
89705
+ if (text.length > maxUnits) return c.json({ status: "unavailable" });
89706
+ while (offset < slice.total) {
89707
+ if (offset === 0) throw notFound();
89708
+ const next = await read(offset);
89709
+ const part = next.slices?.[sequence];
89710
+ if (next.events[0]?.id !== eventId || !part || part.omitted || part.unit !== slice.unit || part.total !== slice.total || part.offset !== offset)
89711
+ throw notFound();
89712
+ text += part.text;
89713
+ offset += slice.unit === "utf16" ? part.text.length : Array.from(part.text).length;
89714
+ if (text.length > maxUnits) return c.json({ status: "unavailable" });
89715
+ }
89716
+ return c.json({ status: "available", text });
89717
+ }
89718
+ );
89243
89719
  app.get("/v1/workspaces/:workspaceId/sessions/:sessionId/events/stream", async (c) => {
89244
89720
  const workspaceId = c.req.param("workspaceId");
89245
89721
  const grant = await requireAccessGrant32(c, deps, workspaceId, "sessions:read");
@@ -90807,6 +91283,8 @@ function sessionAuthorizationOperationForHttp(method, pathname, sessionId) {
90807
91283
  return verb === "POST" ? "session.context.write" : null;
90808
91284
  }
90809
91285
  if (suffix === "/events/stream" && verb === "GET") return "session.stream.read";
91286
+ if (/^\/events\/[^/]+\/message-preview$/.test(suffix) && verb === "GET")
91287
+ return "session.events.read";
90810
91288
  if (suffix === "/events") {
90811
91289
  if (verb === "GET") return "session.events.read";
90812
91290
  if (verb === "POST") return "session.append";
@@ -92641,14 +93119,14 @@ function registerWorkspaceRoutes(app, deps) {
92641
93119
  const limit = boundedLimit(c.req.query("limit"));
92642
93120
  const fetched = await listWorkspaceControlEvents2(deps.db, workspaceId, after, limit + 1);
92643
93121
  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));
93122
+ const page2 = boundWorkspaceControlHttpPage(fetched.slice(0, limit));
93123
+ const truncated = countHasMore || page2.truncated;
93124
+ c.header("X-OpenGeni-Page-Bytes", String(page2.bytes));
92647
93125
  c.header("X-OpenGeni-Page-Truncated", String(truncated));
92648
- if (page.nextSequence !== null) {
92649
- c.header("X-OpenGeni-Next-After", String(page.nextSequence));
93126
+ if (page2.nextSequence !== null) {
93127
+ c.header("X-OpenGeni-Next-After", String(page2.nextSequence));
92650
93128
  }
92651
- return c.json(page.events);
93129
+ return c.json(page2.events);
92652
93130
  });
92653
93131
  app.get("/v1/workspaces/:workspaceId/control-events/stream", async (c) => {
92654
93132
  const workspaceId = c.req.param("workspaceId");
@@ -93800,10 +94278,10 @@ function preferenceLifecycle(row, generatedAt) {
93800
94278
  if (row.status === "superseded") return "superseded";
93801
94279
  return "historical";
93802
94280
  }
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) => {
94281
+ function preferenceEntries(page2, generatedAt) {
94282
+ const visibleRevisionIds = new Set(page2.rows.map((row) => row.revisionId));
94283
+ const visiblePreferenceIds = new Set(page2.rows.map((row) => row.preferenceId));
94284
+ return page2.rows.map((row) => {
93807
94285
  const scope = row.scope === "organization" ? "organization" : row.scope === "user" ? "personal" : "workspace";
93808
94286
  const lifecycle2 = preferenceLifecycle(row, generatedAt);
93809
94287
  const relationships = [];
@@ -95367,6 +95845,7 @@ import {
95367
95845
  requireAccessGrant as requireAccessGrant41,
95368
95846
  WorkspaceInsightsFilterValidationError
95369
95847
  } from "@opengeni/core";
95848
+ import { currentSessionRlsActorIdentityKey } from "@opengeni/db";
95370
95849
  import { workspaceInsightsMetricObserver } from "@opengeni/observability";
95371
95850
  import { HTTPException as HTTPException85 } from "hono/http-exception";
95372
95851
  function normalizeWorkspaceInsightsQueryFilter(value, field) {
@@ -95379,9 +95858,36 @@ function normalizeWorkspaceInsightsQueryFilter(value, field) {
95379
95858
  throw error;
95380
95859
  }
95381
95860
  }
95861
+ function createInFlightCoalescer() {
95862
+ const inFlight = /* @__PURE__ */ new Map();
95863
+ return {
95864
+ run(key, work) {
95865
+ const pending = inFlight.get(key);
95866
+ if (pending) return pending;
95867
+ const started2 = work().finally(() => {
95868
+ if (inFlight.get(key) === started2) inFlight.delete(key);
95869
+ });
95870
+ inFlight.set(key, started2);
95871
+ return started2;
95872
+ },
95873
+ get size() {
95874
+ return inFlight.size;
95875
+ }
95876
+ };
95877
+ }
95878
+ function workspaceInsightsCoalesceKey(input) {
95879
+ return JSON.stringify([
95880
+ input.workspaceId,
95881
+ input.range,
95882
+ input.provider,
95883
+ input.model,
95884
+ input.rlsActor
95885
+ ]);
95886
+ }
95382
95887
  function registerInsightsRoutes(app, deps) {
95383
95888
  const observeRequest = workspaceInsightsMetricObserver(deps.observability);
95384
95889
  const observePhase = workspaceInsightsPhaseMetricObserver(deps.observability);
95890
+ const coalesce = createInFlightCoalescer();
95385
95891
  app.get("/v1/workspaces/:workspaceId/insights", async (c) => {
95386
95892
  const startedAtMs = performance.now();
95387
95893
  const rangeRaw = c.req.query("range") ?? "week";
@@ -95405,16 +95911,25 @@ function registerInsightsRoutes(app, deps) {
95405
95911
  }
95406
95912
  provider = normalizeWorkspaceInsightsQueryFilter(providerRaw, "provider");
95407
95913
  model = normalizeWorkspaceInsightsQueryFilter(modelRaw, "model");
95408
- const response = await getWorkspaceInsights(
95409
- deps.db,
95410
- deps.settings,
95411
- {
95914
+ const response = await coalesce.run(
95915
+ workspaceInsightsCoalesceKey({
95412
95916
  workspaceId,
95413
95917
  range: rangeParsed.data,
95414
95918
  provider,
95415
- model
95416
- },
95417
- observePhase
95919
+ model,
95920
+ rlsActor: currentSessionRlsActorIdentityKey()
95921
+ }),
95922
+ () => getWorkspaceInsights(
95923
+ deps.db,
95924
+ deps.settings,
95925
+ {
95926
+ workspaceId,
95927
+ range: rangeParsed.data,
95928
+ provider,
95929
+ model
95930
+ },
95931
+ observePhase
95932
+ )
95418
95933
  );
95419
95934
  c.header("cache-control", "private, no-store");
95420
95935
  const result = c.json(WorkspaceInsightsResponse.parse(response));
@@ -98379,7 +98894,8 @@ function registerOrganizationMembershipRoutes(app, deps) {
98379
98894
  await createManagedOrganization(deps.db, {
98380
98895
  subjectId,
98381
98896
  subjectLabel: session.user.email || session.user.name,
98382
- ...payload
98897
+ ...payload,
98898
+ trialCreditsEnabled: deps.settings.productAccessMode === "managed" && deps.settings.verifiedSignupTrialCreditsEnabled
98383
98899
  })
98384
98900
  ),
98385
98901
  201
@@ -99155,7 +99671,7 @@ function registerOrganizationSessionRoutes(app, deps) {
99155
99671
  while (index < workspaces.length && sessions.length < query.limit && reads < ORGANIZATION_SESSION_LIST_MAX_WORKSPACE_READS) {
99156
99672
  const workspace = workspaces[index];
99157
99673
  reads += 1;
99158
- const page = await listWorkspaceSessionPage(c, deps, {
99674
+ const page2 = await listWorkspaceSessionPage(c, deps, {
99159
99675
  workspace,
99160
99676
  readAuthority,
99161
99677
  innerCursor,
@@ -99163,21 +99679,21 @@ function registerOrganizationSessionRoutes(app, deps) {
99163
99679
  limit: query.limit - sessions.length,
99164
99680
  query
99165
99681
  });
99166
- if (page === null) {
99682
+ if (page2 === null) {
99167
99683
  index += 1;
99168
99684
  innerCursor = null;
99169
99685
  pinnedOffset = 0;
99170
99686
  continue;
99171
99687
  }
99172
- sessions.push(...page.sessions);
99173
- if (page.nextPinnedOffset !== null) {
99688
+ sessions.push(...page2.sessions);
99689
+ if (page2.nextPinnedOffset !== null) {
99174
99690
  innerCursor = null;
99175
- pinnedOffset = page.nextPinnedOffset;
99691
+ pinnedOffset = page2.nextPinnedOffset;
99176
99692
  break;
99177
99693
  }
99178
99694
  pinnedOffset = 0;
99179
- if (page.nextCursor) {
99180
- innerCursor = page.nextCursor;
99695
+ if (page2.nextCursor) {
99696
+ innerCursor = page2.nextCursor;
99181
99697
  } else {
99182
99698
  index += 1;
99183
99699
  innerCursor = null;
@@ -99232,24 +99748,24 @@ async function listWorkspaceSessionPage(c, deps, input) {
99232
99748
  personalWorkspaceOwnerException: authorization.canonicalManagedHumanSession
99233
99749
  });
99234
99750
  const withStatus = (rows3) => input.query.status ? rows3.filter((session) => session.status === input.query.status) : rows3;
99235
- let page = await list(input.limit);
99751
+ let page2 = await list(input.limit);
99236
99752
  let rows2;
99237
99753
  let nextPinnedOffset = null;
99238
99754
  if (cursor) {
99239
- rows2 = page.sessions;
99755
+ rows2 = page2.sessions;
99240
99756
  } else {
99241
- const pinned = page.pinned.slice(input.pinnedOffset);
99757
+ const pinned = page2.pinned.slice(input.pinnedOffset);
99242
99758
  if (pinned.length >= input.limit) {
99243
99759
  rows2 = pinned.slice(0, input.limit);
99244
99760
  nextPinnedOffset = input.pinnedOffset + input.limit;
99245
99761
  return { sessions: withStatus(rows2), nextCursor: null, nextPinnedOffset };
99246
99762
  }
99247
- if (pinned.length > 0 && page.sessions.length > input.limit - pinned.length) {
99248
- page = await list(input.limit - pinned.length);
99763
+ if (pinned.length > 0 && page2.sessions.length > input.limit - pinned.length) {
99764
+ page2 = await list(input.limit - pinned.length);
99249
99765
  }
99250
- rows2 = [...pinned, ...page.sessions];
99766
+ rows2 = [...pinned, ...page2.sessions];
99251
99767
  }
99252
- return { sessions: withStatus(rows2), nextCursor: page.nextCursor, nextPinnedOffset };
99768
+ return { sessions: withStatus(rows2), nextCursor: page2.nextCursor, nextPinnedOffset };
99253
99769
  } catch (error) {
99254
99770
  if (error instanceof SessionListAccessError2) {
99255
99771
  throw new HTTPException91(403, { message: error.message });
@@ -99591,7 +100107,8 @@ function registerManagedOnboardingRoutes(app, deps, options = {}) {
99591
100107
  actorSubjectId: `user:${session.user.id}`,
99592
100108
  organizationName,
99593
100109
  operationId: parsed.data.operationId,
99594
- requestFingerprint
100110
+ requestFingerprint,
100111
+ trialCreditsEnabled: deps.settings.productAccessMode === "managed" && deps.settings.verifiedSignupTrialCreditsEnabled
99595
100112
  })
99596
100113
  )
99597
100114
  );
@@ -99843,7 +100360,7 @@ function registerUserResourceAuthorityRoutes(app, deps) {
99843
100360
  "workspace:read"
99844
100361
  );
99845
100362
  try {
99846
- const page = await listManagedHumanUserResourceAuthorities(
100363
+ const page2 = await listManagedHumanUserResourceAuthorities(
99847
100364
  deps,
99848
100365
  access,
99849
100366
  workspaceId,
@@ -99852,7 +100369,7 @@ function registerUserResourceAuthorityRoutes(app, deps) {
99852
100369
  return context.json(
99853
100370
  ListUserResourceAuthoritiesResponse.parse({
99854
100371
  scope: "user",
99855
- ...page
100372
+ ...page2
99856
100373
  })
99857
100374
  );
99858
100375
  } catch (error) {
@@ -101847,16 +102364,16 @@ async function publishSlackAppHome(deps, installation, refresh, renewLease) {
101847
102364
  let cursor;
101848
102365
  do {
101849
102366
  await renewLease();
101850
- const page = await listSessionsForSubject3(deps.db, installation.workspaceId, {
102367
+ const page2 = await listSessionsForSubject3(deps.db, installation.workspaceId, {
101851
102368
  subjectId: grant.subjectId,
101852
102369
  limit: 500,
101853
102370
  ...cursor ? { cursor } : {},
101854
102371
  ...authorizationScope ? { authorizationScope } : {}
101855
102372
  });
101856
- if (!cursor) sessions.push(...page.pinned);
101857
- sessions.push(...page.sessions);
101858
- if (!page.nextCursor) break;
101859
- cursor = decodeSessionListCursor3(page.nextCursor) ?? void 0;
102373
+ if (!cursor) sessions.push(...page2.pinned);
102374
+ sessions.push(...page2.sessions);
102375
+ if (!page2.nextCursor) break;
102376
+ cursor = decodeSessionListCursor3(page2.nextCursor) ?? void 0;
101860
102377
  if (!cursor) throw new Error("Slack App Home session cursor was invalid");
101861
102378
  } while (cursor);
101862
102379
  } catch (error) {
@@ -104167,13 +104684,13 @@ async function resolveSlackDeliveryHome(deps, interaction) {
104167
104684
  }
104168
104685
  async function deliverSlackSessionEvents(deps, interaction, claimHolderId) {
104169
104686
  if (!interaction.sessionId) return;
104170
- const page = await listSessionEventPage3(deps.db, interaction.workspaceId, interaction.sessionId, {
104687
+ const page2 = await listSessionEventPage3(deps.db, interaction.workspaceId, interaction.sessionId, {
104171
104688
  after: interaction.lastDeliveredSessionEventSequence,
104172
104689
  limit: 100,
104173
104690
  includeTypes: [...SLACK_DELIVERY_EVENT_TYPES],
104174
104691
  maxBytes: 256 * 1024
104175
104692
  });
104176
- if (page.events.length === 0) {
104693
+ if (page2.events.length === 0) {
104177
104694
  await releaseSlackInteractionDelivery(deps.db, {
104178
104695
  ...interaction,
104179
104696
  claimHolderId
@@ -104212,10 +104729,10 @@ async function deliverSlackSessionEvents(deps, interaction, claimHolderId) {
104212
104729
  };
104213
104730
  let lastSequence = interaction.lastDeliveredSessionEventSequence;
104214
104731
  let terminal2 = null;
104215
- const orderedEvents = page.events.filter(
104732
+ const orderedEvents = page2.events.filter(
104216
104733
  (event) => (event.turnAssociation === null || event.turnAssociation === "current") && event.duplicateOfEventId === null
104217
104734
  ).sort((left, right) => left.sequence - right.sequence);
104218
- lastSequence = page.events.reduce(
104735
+ lastSequence = page2.events.reduce(
104219
104736
  (latest, event) => Math.max(latest, event.sequence),
104220
104737
  lastSequence
104221
104738
  );
@@ -107113,4 +107630,4 @@ export {
107113
107630
  withDefaultEnabledCapabilityMcpTools,
107114
107631
  workflowIdForSession4 as workflowIdForSession
107115
107632
  };
107116
- //# sourceMappingURL=chunk-24C5LPNE.js.map
107633
+ //# sourceMappingURL=chunk-PTO4RZT5.js.map