@opengeni/api-router 5.4.1 → 5.5.0-canary.37090756480001

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.
@@ -3894,7 +3894,15 @@ import {
3894
3894
  CLIENT_ERROR_REPORT_MAX_BYTES,
3895
3895
  CLIENT_ERROR_REVISION_PATTERN,
3896
3896
  CLIENT_ERROR_ROUTE_PATTERN,
3897
- CLIENT_ERRORS_PATH
3897
+ CLIENT_ERRORS_PATH,
3898
+ CLIENT_PAGES,
3899
+ CLIENT_REQUEST_ACTIONS,
3900
+ CLIENT_REQUEST_FAILURE_REASONS,
3901
+ CLIENT_SIGNALS,
3902
+ CLIENT_STREAM_EVENTS,
3903
+ CLIENT_STREAMS,
3904
+ CLIENT_WEB_VITAL_MAX_VALUE,
3905
+ CLIENT_WEB_VITAL_METRICS
3898
3906
  } from "@opengeni/contracts/client-error-report";
3899
3907
  import { bodyLimit } from "hono/body-limit";
3900
3908
  import { z as z9 } from "zod";
@@ -3968,6 +3976,31 @@ var ClientErrorReport = z9.object({
3968
3976
  route: z9.string().regex(CLIENT_ERROR_ROUTE_PATTERN),
3969
3977
  revision: z9.string().regex(CLIENT_ERROR_REVISION_PATTERN)
3970
3978
  }).strict();
3979
+ var route = z9.string().regex(CLIENT_ERROR_ROUTE_PATTERN);
3980
+ var revision = z9.string().regex(CLIENT_ERROR_REVISION_PATTERN);
3981
+ var ClientSignalReport = z9.discriminatedUnion("signal", [
3982
+ z9.object({
3983
+ signal: z9.literal("request_failure"),
3984
+ action: z9.enum(CLIENT_REQUEST_ACTIONS),
3985
+ reason: z9.enum(CLIENT_REQUEST_FAILURE_REASONS),
3986
+ route,
3987
+ revision
3988
+ }).strict(),
3989
+ z9.object({
3990
+ signal: z9.literal("stream"),
3991
+ stream: z9.enum(CLIENT_STREAMS),
3992
+ event: z9.enum(CLIENT_STREAM_EVENTS),
3993
+ route,
3994
+ revision
3995
+ }).strict(),
3996
+ z9.object({
3997
+ signal: z9.literal("web_vital"),
3998
+ metric: z9.enum(CLIENT_WEB_VITAL_METRICS),
3999
+ page: z9.enum(CLIENT_PAGES),
4000
+ value: z9.number().finite().nonnegative(),
4001
+ revision
4002
+ }).strict().refine((report) => report.value <= CLIENT_WEB_VITAL_MAX_VALUE[report.metric])
4003
+ ]);
3971
4004
  var CLIENT_ERRORS_METRIC = {
3972
4005
  name: "opengeni_client_errors_total",
3973
4006
  help: "Web client errors reported by browsers, by closed kind. Admission is bounded per API process, so this is a lower bound."
@@ -3976,10 +4009,56 @@ var CLIENT_ERROR_REJECTIONS_METRIC = {
3976
4009
  name: "opengeni_client_error_reports_rejected_total",
3977
4010
  help: 'Web client error reports the API refused, by closed reason and kind ("unknown" when the body was not read or not valid).'
3978
4011
  };
4012
+ var CLIENT_REQUEST_FAILURES_METRIC = {
4013
+ name: "opengeni_client_request_failures_total",
4014
+ help: "Key web client requests that failed before any HTTP response, by closed action and reason (network, timeout, offline). Admission is bounded per API process, so this is a lower bound."
4015
+ };
4016
+ var CLIENT_STREAM_EVENTS_METRIC = {
4017
+ name: "opengeni_client_stream_events_total",
4018
+ help: "Web client live-stream health events, by stream (session, workspace) and closed event (reconnect, reconnect_exhausted, long_disconnect). Admission is bounded per API process, so this is a lower bound."
4019
+ };
4020
+ var CLIENT_WEB_VITAL_METRIC = {
4021
+ name: "opengeni_client_web_vital",
4022
+ help: "Web vitals reported by browsers, by metric and closed page label. lcp, inp and ttfb are seconds; cls is the unitless layout-shift score."
4023
+ };
4024
+ var CLIENT_WEB_VITAL_BUCKETS = [
4025
+ 0.01,
4026
+ 0.025,
4027
+ 0.05,
4028
+ 0.1,
4029
+ 0.2,
4030
+ 0.25,
4031
+ 0.5,
4032
+ 0.8,
4033
+ 1,
4034
+ 1.8,
4035
+ 2.5,
4036
+ 4,
4037
+ 6,
4038
+ 10,
4039
+ 20,
4040
+ 60
4041
+ ];
4042
+ function createClientSignalAdmission(options = {}) {
4043
+ return createKeyedAdmission(options);
4044
+ }
4045
+ function createClientWebVitalAdmission(options = {}) {
4046
+ return createKeyedAdmission({ capacity: 1200, refillPerSecond: 20, ...options });
4047
+ }
4048
+ function signalAdmissionKey(report) {
4049
+ switch (report.signal) {
4050
+ case "request_failure":
4051
+ return `request_failure:${report.action}`;
4052
+ case "stream":
4053
+ return `stream:${report.stream}:${report.event}`;
4054
+ case "web_vital":
4055
+ return `web_vital:${report.metric}`;
4056
+ }
4057
+ }
3979
4058
  function createClientErrorAdmission(options = {}) {
3980
4059
  return createKeyedAdmission(options);
3981
4060
  }
3982
- function parseClientErrorReport(body3) {
4061
+ function parseClientBeaconReport(body3) {
3983
4062
  if (body3 === null || new TextEncoder().encode(body3).byteLength > CLIENT_ERROR_REPORT_MAX_BYTES) {
3984
4063
  return null;
3985
4064
  }
@@ -3989,8 +4068,13 @@ function parseClientErrorReport(body3) {
3989
4068
  } catch {
3990
4069
  return null;
3991
4070
  }
3992
- const parsed = ClientErrorReport.safeParse(value);
3993
- return parsed.success ? parsed.data : null;
4071
+ const error = ClientErrorReport.safeParse(value);
4072
+ if (error.success) return error.data;
4073
+ const signal = ClientSignalReport.safeParse(value);
4074
+ return signal.success ? signal.data : null;
4075
+ }
4076
+ function isClientSignalReport(report) {
4077
+ return "signal" in report;
3994
4078
  }
3995
4079
  function isClientErrorReportRequest(method, pathname) {
3996
4080
  return method === "POST" && pathname === CLIENT_ERRORS_PATH;
@@ -3998,6 +4082,8 @@ function isClientErrorReportRequest(method, pathname) {
3998
4082
  function registerClientErrorRoutes(app, deps) {
3999
4083
  const { observability, settings } = deps;
4000
4084
  const admission = deps.admission ?? createClientErrorAdmission();
4085
+ const signalAdmission = deps.signalAdmission ?? createClientSignalAdmission();
4086
+ const webVitalAdmission = deps.webVitalAdmission ?? createClientWebVitalAdmission();
4001
4087
  for (const kind of CLIENT_ERROR_KINDS) {
4002
4088
  observability.incrementCounter({ ...CLIENT_ERRORS_METRIC, labels: { kind }, amount: 0 });
4003
4089
  observability.incrementCounter({
@@ -4006,6 +4092,31 @@ function registerClientErrorRoutes(app, deps) {
4006
4092
  amount: 0
4007
4093
  });
4008
4094
  }
4095
+ for (const action of CLIENT_REQUEST_ACTIONS) {
4096
+ for (const reason of CLIENT_REQUEST_FAILURE_REASONS) {
4097
+ observability.incrementCounter({
4098
+ ...CLIENT_REQUEST_FAILURES_METRIC,
4099
+ labels: { action, reason },
4100
+ amount: 0
4101
+ });
4102
+ }
4103
+ }
4104
+ for (const stream of CLIENT_STREAMS) {
4105
+ for (const event of CLIENT_STREAM_EVENTS) {
4106
+ observability.incrementCounter({
4107
+ ...CLIENT_STREAM_EVENTS_METRIC,
4108
+ labels: { stream, event },
4109
+ amount: 0
4110
+ });
4111
+ }
4112
+ }
4113
+ for (const signal of CLIENT_SIGNALS) {
4114
+ observability.incrementCounter({
4115
+ ...CLIENT_ERROR_REJECTIONS_METRIC,
4116
+ labels: { reason: "rate_limited", kind: signal },
4117
+ amount: 0
4118
+ });
4119
+ }
4009
4120
  for (const reason of CLIENT_ERROR_REJECTION_REASONS) {
4010
4121
  if (reason === "rate_limited") continue;
4011
4122
  observability.incrementCounter({
@@ -4033,8 +4144,16 @@ function registerClientErrorRoutes(app, deps) {
4033
4144
  if (origin !== void 0 && !isAllowedBrowserOrigin(origin, settings)) {
4034
4145
  return reject(c, "origin", 403);
4035
4146
  }
4036
- const report = parseClientErrorReport(await c.req.text().catch(() => null));
4147
+ const report = parseClientBeaconReport(await c.req.text().catch(() => null));
4037
4148
  if (!report) return reject(c, "invalid", 400);
4149
+ if (isClientSignalReport(report)) {
4150
+ const bucket = report.signal === "web_vital" ? webVitalAdmission : signalAdmission;
4151
+ if (!bucket.admit(signalAdmissionKey(report))) {
4152
+ return reject(c, "rate_limited", 429, report.signal);
4153
+ }
4154
+ recordClientSignal(observability, report);
4155
+ return c.body(null, 204);
4156
+ }
4038
4157
  if (!admission.admit(report.kind)) return reject(c, "rate_limited", 429, report.kind);
4039
4158
  observability.incrementCounter({ ...CLIENT_ERRORS_METRIC, labels: { kind: report.kind } });
4040
4159
  observability.warn("Web client error reported", {
@@ -4047,6 +4166,46 @@ function registerClientErrorRoutes(app, deps) {
4047
4166
  }
4048
4167
  );
4049
4168
  }
4169
+ function recordClientSignal(observability, report) {
4170
+ switch (report.signal) {
4171
+ case "request_failure":
4172
+ observability.incrementCounter({
4173
+ ...CLIENT_REQUEST_FAILURES_METRIC,
4174
+ labels: { action: report.action, reason: report.reason }
4175
+ });
4176
+ observability.warn("Web client request failed before a response", {
4177
+ surface: "web",
4178
+ op: report.action,
4179
+ reason: report.reason,
4180
+ clientRoute: report.route,
4181
+ clientRevision: report.revision
4182
+ });
4183
+ return;
4184
+ case "stream":
4185
+ observability.incrementCounter({
4186
+ ...CLIENT_STREAM_EVENTS_METRIC,
4187
+ labels: { stream: report.stream, event: report.event }
4188
+ });
4189
+ if (report.event !== "reconnect") {
4190
+ observability.warn("Web client live stream degraded", {
4191
+ surface: "web",
4192
+ op: report.stream,
4193
+ reason: report.event,
4194
+ clientRoute: report.route,
4195
+ clientRevision: report.revision
4196
+ });
4197
+ }
4198
+ return;
4199
+ case "web_vital":
4200
+ observability.observeHistogram({
4201
+ ...CLIENT_WEB_VITAL_METRIC,
4202
+ labels: { metric: report.metric, page: report.page },
4203
+ value: report.value,
4204
+ buckets: CLIENT_WEB_VITAL_BUCKETS
4205
+ });
4206
+ return;
4207
+ }
4208
+ }
4050
4209
 
4051
4210
  // src/routes/analytics-consent.ts
4052
4211
  import {
@@ -4263,6 +4422,11 @@ var FIRST_PARTY_TOOL_AUTHORIZATION = {
4263
4422
  browser_act: { sessionRequired: true, allOf: ["sessions:control"] },
4264
4423
  browser_clipboard: { sessionRequired: true, allOf: ["sessions:read"] },
4265
4424
  browser_debug: { sessionRequired: true, allOf: ["sessions:read"] },
4425
+ browser_downloads: { sessionRequired: true, allOf: ["sessions:read"] },
4426
+ browser_download_save: {
4427
+ sessionRequired: true,
4428
+ allOf: ["sessions:control", "files:upload"]
4429
+ },
4266
4430
  browser_auth: { sessionRequired: true, allOf: ["sessions:control"] },
4267
4431
  interaction_request_human: {
4268
4432
  sessionRequired: true,
@@ -4726,9 +4890,10 @@ import {
4726
4890
  } from "@opengeni/core";
4727
4891
  function projectClientModel(model) {
4728
4892
  const anonymousProvider = model.credentialSource.kind === "deployment" && model.credentialSource.mechanism === "none";
4893
+ const directProvider = model.providerId.startsWith("workspace-openai-") || model.providerId.startsWith("workspace-azure-openai-");
4729
4894
  const organizationProvider = model.credentialSource.kind === "organization_connection";
4730
- const source = organizationProvider || model.providerId === "workspace-anthropic" || model.providerId === "workspace-claude-subscription" || model.providerId === OPENROUTER_PROVIDER_ID || model.providerId === WORKSPACE_OPENROUTER_PROVIDER_ID ? void 0 : model.credentialSource.kind === "connected_subscription" ? model.credentialSource.provider === "xai" ? "supergrok" : "codex" : model.credentialSource.kind === "workspace_connection" ? "workspace_gateway" : anonymousProvider ? void 0 : "opengeni";
4731
- const publicProvider = anonymousProvider || organizationProvider || model.providerId === "workspace-anthropic" || model.providerId === "workspace-claude-subscription" ? { provider: model.providerId, providerLabel: model.providerLabel } : model.providerId === OPENROUTER_PROVIDER_ID ? { provider: "openrouter", providerLabel: "OpenRouter" } : model.providerId === WORKSPACE_OPENROUTER_PROVIDER_ID ? { provider: "workspace-openrouter", providerLabel: "Your OpenRouter" } : source === "codex" ? { provider: "codex", providerLabel: "Codex" } : source === "supergrok" ? { provider: "supergrok", providerLabel: "SuperGrok" } : source === "workspace_gateway" ? { provider: "workspace-gateway", providerLabel: "Your Gateway" } : { provider: "opengeni", providerLabel: "OpenGeni" };
4895
+ const source = directProvider || organizationProvider || model.providerId === "workspace-anthropic" || model.providerId === "workspace-claude-subscription" || model.providerId === OPENROUTER_PROVIDER_ID || model.providerId === WORKSPACE_OPENROUTER_PROVIDER_ID ? void 0 : model.credentialSource.kind === "connected_subscription" ? model.credentialSource.provider === "xai" ? "supergrok" : "codex" : model.credentialSource.kind === "workspace_connection" ? "workspace_gateway" : anonymousProvider ? void 0 : "opengeni";
4896
+ const publicProvider = directProvider || anonymousProvider || organizationProvider || model.providerId === "workspace-anthropic" || model.providerId === "workspace-claude-subscription" ? { provider: model.providerId, providerLabel: model.providerLabel } : model.providerId === OPENROUTER_PROVIDER_ID ? { provider: "openrouter", providerLabel: "OpenRouter" } : model.providerId === WORKSPACE_OPENROUTER_PROVIDER_ID ? { provider: "workspace-openrouter", providerLabel: "Your OpenRouter" } : source === "codex" ? { provider: "codex", providerLabel: "Codex" } : source === "supergrok" ? { provider: "supergrok", providerLabel: "SuperGrok" } : source === "workspace_gateway" ? { provider: "workspace-gateway", providerLabel: "Your Gateway" } : { provider: "opengeni", providerLabel: "OpenGeni" };
4732
4897
  return ClientModel.parse({
4733
4898
  id: model.id,
4734
4899
  label: model.label,
@@ -5704,7 +5869,8 @@ import {
5704
5869
  agentConfigDeploymentPolicy as agentConfigDeploymentPolicy3,
5705
5870
  configuredAllowedReasoningEfforts,
5706
5871
  resolveFirstPartyMcpToolPolicy as resolveFirstPartyMcpToolPolicy2,
5707
- resolveVoiceInputProviderRegistry as resolveVoiceInputProviderRegistry2
5872
+ resolveVoiceInputProviderRegistry as resolveVoiceInputProviderRegistry2,
5873
+ UnsupportedLatencyModeError as UnsupportedLatencyModeError2
5708
5874
  } from "@opengeni/config";
5709
5875
  import {
5710
5876
  AGENT_CAPABILITY_IDS,
@@ -5784,7 +5950,11 @@ import { getCookie as getCookie3 } from "hono/cookie";
5784
5950
  import { HTTPException as HTTPException107 } from "hono/http-exception";
5785
5951
 
5786
5952
  // src/http/api-error.ts
5787
- import { AgentConfigError, AllowanceExhaustedRefusal } from "@opengeni/contracts";
5953
+ import {
5954
+ AgentConfigError,
5955
+ AllowanceExhaustedRefusal,
5956
+ ModelUnavailableError
5957
+ } from "@opengeni/contracts";
5788
5958
  import { WorkspaceControlBusyError } from "@opengeni/db";
5789
5959
  import { HTTPException as HTTPException14 } from "hono/http-exception";
5790
5960
  var ApiHttpError = class extends HTTPException14 {
@@ -5840,6 +6010,17 @@ function agentConfigHttpError(error) {
5840
6010
  }
5841
6011
  });
5842
6012
  }
6013
+ function modelUnavailableHttpError(error) {
6014
+ const cause = error instanceof ModelUnavailableError ? error : error instanceof HTTPException14 && error.cause instanceof ModelUnavailableError ? error.cause : null;
6015
+ if (!cause) return null;
6016
+ return new ApiHttpError(422, {
6017
+ code: "validation_failed",
6018
+ message: cause.message,
6019
+ retryable: false,
6020
+ outcomeUnknown: false,
6021
+ details: { code: cause.code, modelId: cause.modelId }
6022
+ });
6023
+ }
5843
6024
 
5844
6025
  // src/http/request-body.ts
5845
6026
  import { ZodError } from "zod";
@@ -6056,19 +6237,19 @@ function ipv6Groups(address) {
6056
6237
  // src/http/unmatched-route.ts
6057
6238
  import { matchedRoutes } from "hono/route";
6058
6239
  var PROBED_METHODS = ["GET", "POST", "PUT", "PATCH", "DELETE"];
6059
- function isTerminalRoute(route) {
6060
- if (route.method.toUpperCase() !== "ALL") return true;
6061
- return typeof route.handler === "function" && route.handler.length < 2;
6240
+ function isTerminalRoute(route2) {
6241
+ if (route2.method.toUpperCase() !== "ALL") return true;
6242
+ return typeof route2.handler === "function" && route2.handler.length < 2;
6062
6243
  }
6063
6244
  function routesFor(app, method, path) {
6064
6245
  const result = app.router.match(method, path);
6065
- return result[0].map(([[, route]]) => route);
6246
+ return result[0].map(([[, route2]]) => route2);
6066
6247
  }
6067
6248
  function unmatchedRoute(app, c) {
6068
6249
  const method = c.req.method.toUpperCase();
6069
6250
  if (method === "OPTIONS") return null;
6070
6251
  try {
6071
- if (matchedRoutes(c).some((route) => isTerminalRoute(route))) {
6252
+ if (matchedRoutes(c).some((route2) => isTerminalRoute(route2))) {
6072
6253
  return null;
6073
6254
  }
6074
6255
  const effectiveMethod = method === "HEAD" ? "GET" : method;
@@ -6114,9 +6295,9 @@ async function isContractRefusal(response) {
6114
6295
  import { matchedRoutes as matchedRoutes2 } from "hono/route";
6115
6296
  function registeredHandlerRoutePath(context) {
6116
6297
  try {
6117
- for (const route of matchedRoutes2(context)) {
6118
- if (typeof route.handler !== "function" || route.handler.length >= 2) continue;
6119
- const label = boundedRegisteredRouteLabel(route.path);
6298
+ for (const route2 of matchedRoutes2(context)) {
6299
+ if (typeof route2.handler !== "function" || route2.handler.length >= 2) continue;
6300
+ const label = boundedRegisteredRouteLabel(route2.path);
6120
6301
  if (label) return label;
6121
6302
  }
6122
6303
  } catch {
@@ -10684,6 +10865,7 @@ function ticketBodyTooLarge() {
10684
10865
  // src/mcp/knowledge-entries.ts
10685
10866
  import {
10686
10867
  AgentInstructionSaveRequest,
10868
+ WORKSPACE_INSTRUCTION_POLICY_CONTENT_MAX_CHARS,
10687
10869
  WorkspaceInstructionPolicyTarget,
10688
10870
  KnowledgeEntryListRequest,
10689
10871
  KnowledgeSavePreparationRequest,
@@ -10750,7 +10932,7 @@ function registerKnowledgeEntryTools(server, deps, grant, sessionId) {
10750
10932
  };
10751
10933
  }
10752
10934
  const state = nestedPostgresSqlState3(error);
10753
- const message = surface === "instruction" ? state === "40001" ? "This instruction changed. Read its current content and baseline, then retry the same intended edit against that exact state." : state === "23505" ? "This instruction operation ID was already used with different input. Reuse it only for an exact retry." : state === "42501" ? "The instruction change is unavailable in this task's scope or Agent learning policy. Off disables authoring, not the task. Do not ask for approval to continue." : state === "22023" || state === "23514" ? "Invalid instruction change. Read the current instruction, use append for a new rule, or edit with one localized exact oldText match to update or remove text. Agents cannot replace the complete instruction. The resulting instruction must stay within 600 characters." : "Workspace instructions are temporarily unavailable. Retry the same operation ID only if the prior change may have succeeded." : state === "40001" ? "This entry changed. Read its current revision and retry your correction with the current version." : state === "23505" ? "This operation ID was already used with different input. Reuse it only for an exact retry." : state === "42501" ? "The entry, reference, or write is unavailable in this task's scope and Agent learning policy. Off disables saving, not your task. Do not ask for an approval to continue." : state === "22023" || state === "23514" ? "Invalid Knowledge entry or relationship. Check the referenced IDs and revision before retrying." : "Knowledge is temporarily unavailable. Your task can continue; retry the same operation ID if saving may have succeeded.";
10935
+ const message = surface === "instruction" ? state === "40001" ? "This instruction changed. Read its current content and baseline, then retry the same intended edit against that exact state." : state === "23505" ? "This instruction operation ID was already used with different input. Reuse it only for an exact retry." : state === "42501" ? "The instruction change is unavailable in this task's scope or Agent learning policy. Off disables authoring, not the task. Do not ask for approval to continue." : state === "22023" || state === "23514" ? `Invalid instruction change. Read the current instruction, use append for a new rule, or edit with one localized exact oldText match to update or remove text. Agents cannot replace the complete instruction. Each supplied text and the resulting instruction must stay within ${WORKSPACE_INSTRUCTION_POLICY_CONTENT_MAX_CHARS} characters, the same limit as the human editor.` : "Workspace instructions are temporarily unavailable. Retry the same operation ID only if the prior change may have succeeded." : state === "40001" ? "This entry changed. Read its current revision and retry your correction with the current version." : state === "23505" ? "This operation ID was already used with different input. Reuse it only for an exact retry." : state === "42501" ? "The entry, reference, or write is unavailable in this task's scope and Agent learning policy. Off disables saving, not your task. Do not ask for an approval to continue." : state === "22023" || state === "23514" ? "Invalid Knowledge entry or relationship. Check the referenced IDs and revision before retrying." : "Knowledge is temporarily unavailable. Your task can continue; retry the same operation ID if saving may have succeeded.";
10754
10936
  return {
10755
10937
  isError: true,
10756
10938
  content: [
@@ -10935,7 +11117,7 @@ function registerKnowledgeEntryTools(server, deps, grant, sessionId) {
10935
11117
  server.registerTool(
10936
11118
  "instruction_policy_save",
10937
11119
  {
10938
- description: "Change a concise standing workspace instruction through this task's Agent learning policy. Use for short always-on behavior such as 'Keep replies concise; expand when asked', within the intended workspace scope; active instructions enter applicable prompts without Knowledge retrieval. Read the current policy first and submit its exact baseline. Use editMode=append by default for a new rule; it preserves the current content and adds one blank-line separator. Use edit with a localized oldText that occurs exactly once and newText to update or remove only that passage. Agents cannot replace the complete instruction; direct the user to the manual workspace-instruction editor for a whole-policy rewrite. Each supplied text and the resulting instruction are limited to 600 characters. Facts and incidents belong in knowledge_save and reusable procedures or context-specific preferences in skill_save. Preserve personal scope rather than creating a workspace-wide rule. Review first saves an inactive revision and returns pending; continue the task without an approval question. Report the actual receipt; Knowledge is not a workaround for Off, pending review, unavailable scope or size limits.",
11120
+ description: `Change a concise standing workspace instruction through this task's Agent learning policy. Use for short always-on behavior such as 'Keep replies concise; expand when asked', within the intended workspace scope; active instructions enter applicable prompts without Knowledge retrieval. Read the current policy first and submit its exact baseline. Use editMode=append by default for a new rule; it preserves the current content and adds one blank-line separator. Use edit with a localized oldText that occurs exactly once and newText to update or remove only that passage. Agents cannot replace the complete instruction; direct the user to the manual workspace-instruction editor for a whole-policy rewrite. Each supplied text and the resulting instruction are limited to ${WORKSPACE_INSTRUCTION_POLICY_CONTENT_MAX_CHARS} characters, the same limit as the human editor. Facts and incidents belong in knowledge_save and reusable procedures or context-specific preferences in skill_save. Preserve personal scope rather than creating a workspace-wide rule. Review first saves an inactive revision and returns pending; continue the task without an approval question. Report the actual receipt; Knowledge is not a workaround for Off, pending review, unavailable scope or size limits.`,
10939
11121
  inputSchema: AgentInstructionSaveRequest.shape
10940
11122
  },
10941
11123
  (input) => run((context) => saveAgentInstruction(deps.db, context, input), "instruction")
@@ -33915,7 +34097,9 @@ function registerWorkspaceOrchestrationTools(server, deps, grant, can, callerSes
33915
34097
  "Model for the worker. Omit to inherit the exact calling turn's model, including its Codex subscription billing path."
33916
34098
  ),
33917
34099
  reasoningEffort: z43.string().optional().describe("Omit to inherit the exact calling turn's reasoning effort."),
33918
- latencyMode: z43.enum(["standard", "priority", "fast"]).optional().describe("Omit to inherit the exact calling turn's latency mode."),
34100
+ latencyMode: z43.enum(["standard", "priority", "fast"]).optional().describe(
34101
+ "Omit for standard latency, even when model and reasoning are inherited. Fast or priority must be selected explicitly and supported by the model."
34102
+ ),
33919
34103
  sandboxBackend: z43.string().optional(),
33920
34104
  // Model-only structural coupling: workingDir cannot exist without a
33921
34105
  // targetSandboxId because both live inside one optional object. The
@@ -35249,11 +35433,11 @@ function normalizeSessionDiscoveryRevision(value, label) {
35249
35433
  if (!SESSION_DISCOVERY_REVISION.test(value)) {
35250
35434
  throw new Error(`sessions_list ${label} must be a decimal activity revision`);
35251
35435
  }
35252
- const revision = BigInt(value);
35253
- if (revision > SESSION_DISCOVERY_REVISION_MAX) {
35436
+ const revision2 = BigInt(value);
35437
+ if (revision2 > SESSION_DISCOVERY_REVISION_MAX) {
35254
35438
  throw new Error(`sessions_list ${label} exceeds the database activity revision range`);
35255
35439
  }
35256
- return revision.toString();
35440
+ return revision2.toString();
35257
35441
  }
35258
35442
  function decodeSessionDiscoveryCursor(value) {
35259
35443
  try {
@@ -65662,6 +65846,7 @@ function registerOrganizationIntegrationPolicyRoutes(app, deps) {
65662
65846
 
65663
65847
  // src/routes/connections.ts
65664
65848
  import { createHash as createHash18, createHmac as createHmac6 } from "crypto";
65849
+ import { verifyDirectModelAccess } from "@opengeni/core";
65665
65850
  import {
65666
65851
  createConnectionIdempotently,
65667
65852
  upsertWorkspaceProviderApiKeyConnection as upsertWorkspaceProviderApiKeyConnection2,
@@ -65755,6 +65940,7 @@ import {
65755
65940
  import {
65756
65941
  API_INTEGRATION_OAUTH_CREDENTIAL_ROLE as API_INTEGRATION_OAUTH_CREDENTIAL_ROLE3,
65757
65942
  ApiIntegrationOAuthStartRequest,
65943
+ DirectModelProviderMetadata,
65758
65944
  ConnectionResponse,
65759
65945
  CreateConnectionRequest,
65760
65946
  FikenInstallRequest as FikenInstallRequest2,
@@ -67213,6 +67399,28 @@ function registerConnectionRoutes(app, deps) {
67213
67399
  const grant = access.grant;
67214
67400
  const beforeCommit = externalContinuationCommitAuthorizer2(access);
67215
67401
  const payload = await parseRequestJson(c, CreateConnectionRequest);
67402
+ if (payload.metadata?.directModelProvider !== void 0 || String(payload.metadata?.credentialRole ?? "").startsWith("direct_")) {
67403
+ const config = DirectModelProviderMetadata.safeParse(payload.metadata?.directModelProvider);
67404
+ const domain = config.success ? new URL(config.data.endpoint ?? "https://api.openai.com/v1").hostname : null;
67405
+ if (!config.success || payload.subjectId !== null || payload.kind !== "api_key" || canonicalProviderDomain(payload.providerDomain) !== domain || payload.metadata?.credentialRole !== `direct_${config.data.provider}` || typeof payload.credential.apiKey !== "string" || !payload.credential.apiKey.trim() || !payload.operationId || payload.expiresAt) {
67406
+ throw new HTTPException44(422, {
67407
+ message: "Invalid OpenAI or Azure OpenAI connection: provide a key, model, and official provider endpoint"
67408
+ });
67409
+ }
67410
+ payload.metadata = { ...payload.metadata, directModelProvider: config.data };
67411
+ if (payload.verifyModelAccess) {
67412
+ await verifyDirectModelAccess(
67413
+ config.data,
67414
+ payload.credential.apiKey,
67415
+ deps.directModelFetch
67416
+ );
67417
+ }
67418
+ } else if (payload.verifyModelAccess) {
67419
+ throw new HTTPException44(422, {
67420
+ message: "Model checks require an OpenAI or Azure OpenAI connection"
67421
+ });
67422
+ }
67423
+ const directModelKey = payload.metadata?.directModelProvider !== void 0;
67216
67424
  const persist = async (db2) => {
67217
67425
  assertNotReservedSlackBotMetadata(payload.metadata);
67218
67426
  assertNotReservedFikenMetadata(payload.metadata);
@@ -67248,7 +67456,8 @@ function registerConnectionRoutes(app, deps) {
67248
67456
  kind: payload.kind,
67249
67457
  metadata: payload.metadata
67250
67458
  });
67251
- if (!workspaceProviderKind) assertBrokeredApiKeyCredential(payload.kind, payload.credential);
67459
+ if (!workspaceProviderKind && !directModelKey)
67460
+ assertBrokeredApiKeyCredential(payload.kind, payload.credential);
67252
67461
  const connection = workspaceProviderKind ? await (async () => {
67253
67462
  const provider = workspaceProviderApiKeyConnectionSpec(workspaceProviderKind);
67254
67463
  if (!payload.operationId) {
@@ -67336,7 +67545,7 @@ function registerConnectionRoutes(app, deps) {
67336
67545
  kind: payload.kind,
67337
67546
  metadata: payload.metadata
67338
67547
  });
67339
- return modelKey ? commit(db) : withOrganizationIntegrationAcquisition8(
67548
+ return modelKey || directModelKey ? commit(db) : withOrganizationIntegrationAcquisition8(
67340
67549
  db,
67341
67550
  { accountId: grant.accountId, workspaceId },
67342
67551
  [null],
@@ -67857,6 +68066,11 @@ function registerConnectionRoutes(app, deps) {
67857
68066
  c.req.param("connectionId"),
67858
68067
  grant.subjectId
67859
68068
  );
68069
+ if (initial?.metadata.directModelProvider !== void 0 || payload.metadata?.directModelProvider !== void 0 || String(payload.metadata?.credentialRole ?? "").startsWith("direct_")) {
68070
+ throw new HTTPException44(422, {
68071
+ message: "Disconnect and reconnect OpenAI or Azure OpenAI to replace its key or deployment"
68072
+ });
68073
+ }
67860
68074
  if (!initial || !workspaceProviderApiKeyConnectionKind(initial)) {
67861
68075
  await db2.execute(
67862
68076
  sql`select id from connections where workspace_id = ${workspaceId}::uuid and id = ${c.req.param("connectionId")}::uuid and (subject_id is null or subject_id = ${grant.subjectId}) for update`
@@ -69219,6 +69433,7 @@ import {
69219
69433
  } from "@opengeni/core";
69220
69434
  import { withOrganizationIntegrationPolicyFence as withOrganizationIntegrationPolicyFence5 } from "@opengeni/db/organization-integration-policy";
69221
69435
  import {
69436
+ ApiIntegrationConnectionReferenceError,
69222
69437
  ApiIntegrationInstallationVersionConflictError,
69223
69438
  buildConnectionTokenResolver as buildConnectionTokenResolver6,
69224
69439
  getApiIntegrationUninstallPreview,
@@ -69304,14 +69519,14 @@ async function resolveDefinition(input) {
69304
69519
  );
69305
69520
  }
69306
69521
  const identity2 = integrationInstallationIdentity("openapi", definition.id, definition.source.url);
69307
- const revision = compileOpenApiRevision(document, {
69522
+ const revision2 = compileOpenApiRevision(document, {
69308
69523
  definitionId: definition.id,
69309
69524
  sourceUrl: definition.source.url,
69310
69525
  baseUrl: definition.baseUrl,
69311
69526
  provider: definition.provider.id,
69312
69527
  ...definition.source.kind === "openapi" && definition.source.schemaMode ? { schemaMode: definition.source.schemaMode } : {}
69313
69528
  });
69314
- const baseUrl = firstOpenApiServerUrl(revision);
69529
+ const baseUrl = firstOpenApiServerUrl(revision2);
69315
69530
  const providerDomain = definition.provider.domain;
69316
69531
  const auth = {
69317
69532
  kind: "oauth2",
@@ -69323,7 +69538,7 @@ async function resolveDefinition(input) {
69323
69538
  definitionId: definition.id,
69324
69539
  definitionProvenance: "curated",
69325
69540
  identity: identity2,
69326
- revision,
69541
+ revision: revision2,
69327
69542
  provider: definition.provider.id,
69328
69543
  providerDomain,
69329
69544
  baseUrl,
@@ -69348,12 +69563,12 @@ async function resolveOpenApi(input, sourceUrl, baseUrl) {
69348
69563
  new URL(sourceUrl).hostname,
69349
69564
  sourceUrl
69350
69565
  );
69351
- const revision = compileOpenApiRevision(document, {
69566
+ const revision2 = compileOpenApiRevision(document, {
69352
69567
  definitionId: identity2.sourceDerivedDefinitionId,
69353
69568
  sourceUrl,
69354
69569
  ...baseUrl ? { baseUrl } : {}
69355
69570
  });
69356
- const resolvedBaseUrl = firstOpenApiServerUrl(revision);
69571
+ const resolvedBaseUrl = firstOpenApiServerUrl(revision2);
69357
69572
  const providerDomain = new URL(resolvedBaseUrl).hostname.toLowerCase();
69358
69573
  const discovered = discoverOpenApiAuth(document);
69359
69574
  const auth = authPreview(discovered, providerDomain, input.connection);
@@ -69362,13 +69577,13 @@ async function resolveOpenApi(input, sourceUrl, baseUrl) {
69362
69577
  definitionId: identity2.sourceDerivedDefinitionId,
69363
69578
  definitionProvenance: "workspace",
69364
69579
  identity: identity2,
69365
- revision,
69580
+ revision: revision2,
69366
69581
  provider: null,
69367
69582
  providerDomain,
69368
69583
  baseUrl: resolvedBaseUrl,
69369
69584
  sourceUrl,
69370
- name: revision.title,
69371
- description: revision.description ?? null,
69585
+ name: revision2.title,
69586
+ description: revision2.description ?? null,
69372
69587
  auth,
69373
69588
  connection: input.connection,
69374
69589
  requiredScopes: auth.kind === "oauth2" ? auth.scopes : [],
@@ -69388,9 +69603,9 @@ function resolveInlineOpenApiDocument(input, source) {
69388
69603
  source.sourceKey,
69389
69604
  `document:${source.sourceKey}`
69390
69605
  );
69391
- let revision;
69606
+ let revision2;
69392
69607
  try {
69393
- revision = compileOpenApiRevision(document, {
69608
+ revision2 = compileOpenApiRevision(document, {
69394
69609
  definitionId: identity2.sourceDerivedDefinitionId,
69395
69610
  ...source.baseUrl ? { baseUrl: source.baseUrl } : {}
69396
69611
  });
@@ -69404,7 +69619,7 @@ function resolveInlineOpenApiDocument(input, source) {
69404
69619
  }
69405
69620
  throw error;
69406
69621
  }
69407
- const resolvedBaseUrl = firstOpenApiServerUrl(revision);
69622
+ const resolvedBaseUrl = firstOpenApiServerUrl(revision2);
69408
69623
  const providerDomain = new URL(resolvedBaseUrl).hostname.toLowerCase();
69409
69624
  const discovered = discoverOpenApiAuth(document);
69410
69625
  const auth = authPreview(discovered, providerDomain, input.connection);
@@ -69418,13 +69633,13 @@ function resolveInlineOpenApiDocument(input, source) {
69418
69633
  definitionId: identity2.sourceDerivedDefinitionId,
69419
69634
  definitionProvenance: "workspace",
69420
69635
  identity: identity2,
69421
- revision,
69636
+ revision: revision2,
69422
69637
  provider: null,
69423
69638
  providerDomain,
69424
69639
  baseUrl: resolvedBaseUrl,
69425
69640
  sourceUrl: null,
69426
- name: revision.title,
69427
- description: revision.description ?? null,
69641
+ name: revision2.title,
69642
+ description: revision2.description ?? null,
69428
69643
  auth,
69429
69644
  connection: input.connection,
69430
69645
  requiredScopes: auth.kind === "oauth2" ? auth.scopes : [],
@@ -69440,7 +69655,7 @@ async function resolveGraphql(input, endpoint, name) {
69440
69655
  authority: input.authority,
69441
69656
  ...input.credentialResolver ? { credentialResolver: input.credentialResolver } : {}
69442
69657
  });
69443
- const revision = compileGraphqlRevision(introspection, {
69658
+ const revision2 = compileGraphqlRevision(introspection, {
69444
69659
  definitionId: identity2.sourceDerivedDefinitionId,
69445
69660
  endpoint,
69446
69661
  sourceUrl: endpoint,
@@ -69452,13 +69667,13 @@ async function resolveGraphql(input, endpoint, name) {
69452
69667
  definitionId: identity2.sourceDerivedDefinitionId,
69453
69668
  definitionProvenance: "workspace",
69454
69669
  identity: identity2,
69455
- revision,
69670
+ revision: revision2,
69456
69671
  provider: null,
69457
69672
  providerDomain,
69458
69673
  baseUrl: endpoint,
69459
69674
  sourceUrl: endpoint,
69460
- name: revision.title,
69461
- description: revision.description ?? null,
69675
+ name: revision2.title,
69676
+ description: revision2.description ?? null,
69462
69677
  auth,
69463
69678
  connection: input.connection,
69464
69679
  requiredScopes: input.connection?.scopes ?? [],
@@ -69521,8 +69736,8 @@ function integrationInstallationIdentity(protocol, label, locator2) {
69521
69736
  serverId: `api_${protocol}_${slug.replaceAll("-", "_").slice(0, 48)}_${hash}`
69522
69737
  };
69523
69738
  }
69524
- function firstOpenApiServerUrl(revision) {
69525
- const first = Object.values(revision.bindings)[0]?.serverUrl;
69739
+ function firstOpenApiServerUrl(revision2) {
69740
+ const first = Object.values(revision2.bindings)[0]?.serverUrl;
69526
69741
  if (!first) throw new Error("OpenAPI revision has no executable server URL");
69527
69742
  return first;
69528
69743
  }
@@ -69780,6 +69995,11 @@ function registerApiIntegrationRoutes(app, deps, overrides = {}) {
69780
69995
  payload.expectedInstanceVersion === void 0 ? 201 : 200
69781
69996
  );
69782
69997
  } catch (error) {
69998
+ if (error instanceof ApiIntegrationConnectionReferenceError) {
69999
+ throw new HTTPException47(error.reason === "not_found" ? 404 : 422, {
70000
+ message: error.reason === "not_found" ? "connection not found" : error.message
70001
+ });
70002
+ }
69783
70003
  if (error instanceof IntegrationFacetBindingVersionConflictError || error instanceof IntegrationFacetBindingVersionRequiredError || error instanceof IntegrationFacetBindingOwnershipConflictError) {
69784
70004
  throw new HTTPException47(409, {
69785
70005
  message: "The Integration instance changed or is shared by another owner. Refresh its details before updating it."
@@ -74503,14 +74723,17 @@ import {
74503
74723
  OrganizationUsageQuery,
74504
74724
  OrganizationUsageWorkspacePageQuery
74505
74725
  } from "@opengeni/contracts";
74726
+ import { OrganizationModelUsageQuery } from "@opengeni/contracts/organization-model-usage";
74506
74727
  import { configuredEntitlements } from "@opengeni/config";
74507
74728
  import {
74508
74729
  applyCreditLedgerEntry,
74509
74730
  getBillingBalance,
74510
74731
  getBillingCustomer,
74732
+ getCreditLedgerEntry,
74511
74733
  hasCreditLedgerEntry,
74512
74734
  isStripeWebhookProcessed,
74513
74735
  listUsageEvents,
74736
+ getOrganizationModelUsage,
74514
74737
  getOrganizationUsageSummary,
74515
74738
  getOrganizationUsageWorkspacePage,
74516
74739
  withSessionRlsActorContext as withSessionRlsActorContext8,
@@ -74580,6 +74803,21 @@ function registerBillingRoutes(app, deps) {
74580
74803
  async () => c.json(await getOrganizationUsageWorkspacePage(deps.db, { accountId, ...parsed.data }))
74581
74804
  );
74582
74805
  });
74806
+ app.get("/v1/billing/usage-models", async (c) => {
74807
+ const context = await requireAccessContext5(c, deps);
74808
+ const accountId = requireSelectedAccount(context, c.req.query("accountId"), "billing:read");
74809
+ const parsed = OrganizationModelUsageQuery.safeParse(c.req.query());
74810
+ if (!parsed.success)
74811
+ throw new HTTPException56(400, {
74812
+ message: parsed.error.issues[0]?.message ?? "invalid model usage query"
74813
+ });
74814
+ return await withBillingUsageActor(
74815
+ deps,
74816
+ context,
74817
+ accountId,
74818
+ async () => c.json(await getOrganizationModelUsage(deps.db, { accountId, ...parsed.data }))
74819
+ );
74820
+ });
74583
74821
  app.get("/v1/billing/entitlements", async (c) => {
74584
74822
  const context = await requireAccessContext5(c, deps);
74585
74823
  const accountId = requireSelectedAccount(context, c.req.query("accountId"), "billing:read");
@@ -74615,6 +74853,7 @@ function registerBillingRoutes(app, deps) {
74615
74853
  amountMicros,
74616
74854
  creditsProductId: deps.settings.stripeCreditsProductId,
74617
74855
  publicBaseUrl: deps.settings.publicBaseUrl,
74856
+ webBaseUrl: deps.settings.webBaseUrl,
74618
74857
  successUrl: body3.successUrl,
74619
74858
  cancelUrl: body3.cancelUrl,
74620
74859
  idempotencyKey
@@ -74649,6 +74888,7 @@ function registerBillingRoutes(app, deps) {
74649
74888
  stripeBillingPortalSessionCreateParams({
74650
74889
  customerId,
74651
74890
  publicBaseUrl: deps.settings.publicBaseUrl,
74891
+ webBaseUrl: deps.settings.webBaseUrl,
74652
74892
  returnUrl: parsed.data.returnUrl
74653
74893
  })
74654
74894
  );
@@ -74708,18 +74948,21 @@ function registerBillingRoutes(app, deps) {
74708
74948
  function stripeCheckoutSessionCreateParams(input) {
74709
74949
  const successUrl = checkoutReturnUrl(
74710
74950
  input.publicBaseUrl,
74951
+ input.webBaseUrl,
74711
74952
  input.successUrl,
74712
74953
  "/billing?checkout=success",
74713
74954
  "successUrl"
74714
74955
  );
74715
74956
  const cancelUrl = checkoutReturnUrl(
74716
74957
  input.publicBaseUrl,
74958
+ input.webBaseUrl,
74717
74959
  input.cancelUrl,
74718
74960
  "/billing?checkout=cancelled",
74719
74961
  "cancelUrl"
74720
74962
  );
74721
74963
  return {
74722
74964
  mode: "payment",
74965
+ allow_promotion_codes: true,
74723
74966
  customer: input.customerId,
74724
74967
  customer_update: {
74725
74968
  address: "auto",
@@ -74751,14 +74994,16 @@ function stripeCheckoutSessionCreateParams(input) {
74751
74994
  opengeni_account_id: input.accountId,
74752
74995
  opengeni_credit_amount_usd: (input.amountCents / 100).toFixed(2),
74753
74996
  opengeni_credit_micros: String(input.amountMicros),
74754
- opengeni_credit_idempotency_key: input.idempotencyKey
74997
+ opengeni_credit_idempotency_key: input.idempotencyKey,
74998
+ opengeni_credit_coupon_v1: "1"
74755
74999
  },
74756
75000
  payment_intent_data: {
74757
75001
  metadata: {
74758
75002
  opengeni_account_id: input.accountId,
74759
75003
  opengeni_credit_amount_usd: (input.amountCents / 100).toFixed(2),
74760
75004
  opengeni_credit_micros: String(input.amountMicros),
74761
- opengeni_credit_idempotency_key: input.idempotencyKey
75005
+ opengeni_credit_idempotency_key: input.idempotencyKey,
75006
+ opengeni_credit_coupon_v1: "1"
74762
75007
  }
74763
75008
  },
74764
75009
  invoice_creation: {
@@ -74768,7 +75013,8 @@ function stripeCheckoutSessionCreateParams(input) {
74768
75013
  opengeni_account_id: input.accountId,
74769
75014
  opengeni_credit_amount_usd: (input.amountCents / 100).toFixed(2),
74770
75015
  opengeni_credit_micros: String(input.amountMicros),
74771
- opengeni_credit_idempotency_key: input.idempotencyKey
75016
+ opengeni_credit_idempotency_key: input.idempotencyKey,
75017
+ opengeni_credit_coupon_v1: "1"
74772
75018
  }
74773
75019
  }
74774
75020
  }
@@ -74777,23 +75023,32 @@ function stripeCheckoutSessionCreateParams(input) {
74777
75023
  function stripeBillingPortalSessionCreateParams(input) {
74778
75024
  return {
74779
75025
  customer: input.customerId,
74780
- return_url: checkoutReturnUrl(input.publicBaseUrl, input.returnUrl, "/billing", "returnUrl")
75026
+ return_url: checkoutReturnUrl(
75027
+ input.publicBaseUrl,
75028
+ input.webBaseUrl,
75029
+ input.returnUrl,
75030
+ "/billing",
75031
+ "returnUrl"
75032
+ )
74781
75033
  };
74782
75034
  }
74783
- function checkoutReturnUrl(publicBaseUrl, candidate, fallbackPath, field) {
75035
+ function checkoutReturnUrl(publicBaseUrl, webBaseUrl, candidate, fallbackPath, field) {
74784
75036
  if (!publicBaseUrl) {
74785
75037
  throw new HTTPException56(500, {
74786
75038
  message: "OPENGENI_PUBLIC_BASE_URL is required for Stripe redirects"
74787
75039
  });
74788
75040
  }
74789
- const base = new URL(publicBaseUrl);
75041
+ const base = new URL(webBaseUrl ?? publicBaseUrl);
74790
75042
  const fallback = new URL(fallbackPath, base).toString();
74791
75043
  if (!candidate) {
74792
75044
  return fallback;
74793
75045
  }
74794
75046
  const parsed = new URL(candidate);
74795
- if (parsed.origin !== base.origin) {
74796
- throw new HTTPException56(400, { message: `${field} must use the OpenGeni public origin` });
75047
+ const allowedOrigins2 = /* @__PURE__ */ new Set([new URL(publicBaseUrl).origin, base.origin]);
75048
+ if (!allowedOrigins2.has(parsed.origin)) {
75049
+ throw new HTTPException56(400, {
75050
+ message: `${field} must use the OpenGeni public${allowedOrigins2.size > 1 ? " or web" : ""} origin`
75051
+ });
74797
75052
  }
74798
75053
  return parsed.toString();
74799
75054
  }
@@ -74845,13 +75100,17 @@ function stripeCheckoutCreditDecision(session) {
74845
75100
  if (session.mode !== "payment") {
74846
75101
  return { action: "ignore", reason: "not_payment_mode" };
74847
75102
  }
74848
- if (session.payment_status !== "paid") {
75103
+ if (session.payment_status !== "paid" && !isFreeCouponCheckout(session)) {
74849
75104
  return { action: "ignore", reason: "payment_not_paid" };
74850
75105
  }
74851
- return {
74852
- action: "grant",
74853
- credit: creditMetadata(session.metadata, `Stripe checkout session ${session.id}`)
74854
- };
75106
+ const credit = creditMetadata(session.metadata, `Stripe checkout session ${session.id}`);
75107
+ if (session.metadata?.opengeni_credit_coupon_v1 === "1" && (session.currency !== "usd" || session.amount_subtotal !== credit.amountMicros / 1e4 || typeof session.amount_total !== "number" || !Number.isSafeInteger(session.amount_total) || session.amount_total < 0)) {
75108
+ throw new Error(`Stripe checkout session ${session.id} has invalid credit package totals`);
75109
+ }
75110
+ return { action: "grant", credit };
75111
+ }
75112
+ function isFreeCouponCheckout(session) {
75113
+ return (session.payment_status === "paid" || session.payment_status === "no_payment_required") && session.status === "complete" && session.amount_total === 0 && typeof session.amount_subtotal === "number" && session.amount_subtotal > 0 && (session.total_details?.amount_discount ?? 0) >= session.amount_subtotal;
74855
75114
  }
74856
75115
  async function handleCheckoutSessionPayment(deps, event) {
74857
75116
  const session = event.data.object;
@@ -74866,6 +75125,7 @@ async function handleCheckoutSessionPayment(deps, event) {
74866
75125
  return;
74867
75126
  }
74868
75127
  const credit = decision.credit;
75128
+ const freeCouponCheckout = isFreeCouponCheckout(session);
74869
75129
  if (!await getManagedAccount2(deps.db, credit.accountId)) {
74870
75130
  console.info(
74871
75131
  "[api] stripe webhook ignored a checkout session for an account not in this deployment",
@@ -74887,19 +75147,23 @@ async function handleCheckoutSessionPayment(deps, event) {
74887
75147
  }
74888
75148
  await applyCreditLedgerEntry(deps.db, {
74889
75149
  accountId: credit.accountId,
74890
- type: "credit_topup",
75150
+ type: freeCouponCheckout ? "grant" : "credit_topup",
74891
75151
  amountMicros: credit.amountMicros,
74892
- sourceType: "stripe_checkout_session",
75152
+ sourceType: freeCouponCheckout ? "stripe_checkout_coupon" : "stripe_checkout_session",
74893
75153
  sourceId: session.id,
74894
75154
  idempotencyKey: credit.idempotencyKey,
74895
75155
  metadata: {
74896
75156
  stripeEventId: event.id,
74897
75157
  stripePaymentIntentId: typeof session.payment_intent === "string" ? session.payment_intent : session.payment_intent?.id ?? null,
74898
75158
  stripePackageId: credit.packageId,
74899
- stripeCreditAmountUsd: credit.amountUsd
75159
+ stripeCreditAmountUsd: credit.amountUsd,
75160
+ ...session.metadata?.opengeni_credit_coupon_v1 === "1" ? {
75161
+ stripeAmountTotalCents: session.amount_total,
75162
+ stripeDiscountCents: session.total_details?.amount_discount ?? 0
75163
+ } : {}
74900
75164
  }
74901
75165
  });
74902
- recordCreditMicrosMetric(deps, "topup", credit.amountMicros);
75166
+ recordCreditMicrosMetric(deps, freeCouponCheckout ? "grant" : "topup", credit.amountMicros);
74903
75167
  }
74904
75168
  async function mirrorPaymentIntentCustomer(deps, event) {
74905
75169
  const intent = event.data.object;
@@ -74936,19 +75200,21 @@ async function applyRefundDebit(deps, stripe, refund) {
74936
75200
  if (await hasCreditLedgerEntry(deps.db, accountId, idempotencyKey)) {
74937
75201
  return;
74938
75202
  }
75203
+ const debitMicros = await creditAdjustmentMicros(deps, metadata, refund.amount);
74939
75204
  await applyCreditLedgerEntry(deps.db, {
74940
75205
  accountId,
74941
75206
  type: "credit_refund",
74942
- amountMicros: -centsToMicros(refund.amount),
75207
+ amountMicros: -debitMicros,
74943
75208
  sourceType: "stripe_refund",
74944
75209
  sourceId: refund.id,
74945
75210
  idempotencyKey,
74946
75211
  metadata: {
74947
75212
  stripeRefundId: refund.id,
74948
- stripePaymentIntentId: paymentIntentId(refund.payment_intent)
75213
+ stripePaymentIntentId: paymentIntentId(refund.payment_intent),
75214
+ stripeRefundAmountCents: refund.amount
74949
75215
  }
74950
75216
  });
74951
- recordCreditMicrosMetric(deps, "refund", centsToMicros(refund.amount));
75217
+ recordCreditMicrosMetric(deps, "refund", debitMicros);
74952
75218
  }
74953
75219
  async function holdDisputedCredits(deps, stripe, event) {
74954
75220
  const dispute = event.data.object;
@@ -74957,10 +75223,11 @@ async function holdDisputedCredits(deps, stripe, event) {
74957
75223
  if (!accountId) {
74958
75224
  return;
74959
75225
  }
75226
+ const debitMicros = await creditAdjustmentMicros(deps, metadata, dispute.amount);
74960
75227
  await applyCreditLedgerEntry(deps.db, {
74961
75228
  accountId,
74962
75229
  type: "credit_dispute_hold",
74963
- amountMicros: -centsToMicros(dispute.amount),
75230
+ amountMicros: -debitMicros,
74964
75231
  sourceType: "stripe_dispute",
74965
75232
  sourceId: dispute.id,
74966
75233
  idempotencyKey: `stripe:dispute_hold:${dispute.id}`,
@@ -74981,16 +75248,30 @@ async function releaseDisputedCredits(deps, stripe, event) {
74981
75248
  if (!await hasCreditLedgerEntry(deps.db, accountId, holdIdempotencyKey)) {
74982
75249
  return;
74983
75250
  }
75251
+ const debitMicros = await creditAdjustmentMicros(deps, metadata, dispute.amount);
74984
75252
  await applyCreditLedgerEntry(deps.db, {
74985
75253
  accountId,
74986
75254
  type: "credit_dispute_release",
74987
- amountMicros: centsToMicros(dispute.amount),
75255
+ amountMicros: debitMicros,
74988
75256
  sourceType: "stripe_dispute",
74989
75257
  sourceId: dispute.id,
74990
75258
  idempotencyKey: `stripe:dispute_release:${dispute.id}`,
74991
75259
  metadata: { stripeDisputeId: dispute.id, stripeEventType: event.type }
74992
75260
  });
74993
75261
  }
75262
+ async function creditAdjustmentMicros(deps, metadata, amountCents) {
75263
+ if (metadata?.opengeni_credit_coupon_v1 !== "1") return centsToMicros(amountCents);
75264
+ const credit = creditMetadata(metadata, "Stripe adjustment");
75265
+ const entry2 = await getCreditLedgerEntry(deps.db, credit.accountId, credit.idempotencyKey);
75266
+ const paidCents = entry2?.metadata.stripeAmountTotalCents;
75267
+ if (entry2?.sourceType !== "stripe_checkout_session" || entry2.amountMicros !== credit.amountMicros || typeof paidCents !== "number" || !Number.isSafeInteger(paidCents) || paidCents <= 0 || !Number.isSafeInteger(amountCents) || amountCents <= 0) {
75268
+ throw new Error("Stripe credit adjustment is missing a settled checkout");
75269
+ }
75270
+ const proportionalMicros = (BigInt(entry2.amountMicros) * BigInt(amountCents) + BigInt(Math.floor(paidCents / 2))) / BigInt(paidCents);
75271
+ return Number(
75272
+ proportionalMicros > BigInt(entry2.amountMicros) ? entry2.amountMicros : proportionalMicros
75273
+ );
75274
+ }
74994
75275
  async function mirrorCustomer(deps, event, customer) {
74995
75276
  const accountId = customer.metadata?.opengeni_account_id;
74996
75277
  if (!accountId || customer.deleted) {
@@ -75015,14 +75296,14 @@ function recordCreditMicrosMetric(deps, kind, amountMicros) {
75015
75296
  });
75016
75297
  }
75017
75298
  async function metadataForRefund(stripe, refund) {
75018
- if (Object.keys(refund.metadata ?? {}).length > 0) {
75299
+ if (refund.metadata?.opengeni_credit_idempotency_key) {
75019
75300
  return refund.metadata;
75020
75301
  }
75021
75302
  const paymentIntent = paymentIntentId(refund.payment_intent);
75022
- return paymentIntent ? (await stripe.paymentIntents.retrieve(paymentIntent)).metadata : null;
75303
+ return paymentIntent ? (await stripe.paymentIntents.retrieve(paymentIntent)).metadata : refund.metadata;
75023
75304
  }
75024
75305
  async function metadataForDispute(stripe, dispute) {
75025
- if (Object.keys(dispute.metadata ?? {}).length > 0) {
75306
+ if (dispute.metadata?.opengeni_credit_idempotency_key) {
75026
75307
  return dispute.metadata;
75027
75308
  }
75028
75309
  const paymentIntent = paymentIntentId(
@@ -75033,7 +75314,7 @@ async function metadataForDispute(stripe, dispute) {
75033
75314
  }
75034
75315
  const chargeId = typeof dispute.charge === "string" ? dispute.charge : dispute.charge?.id;
75035
75316
  if (!chargeId) {
75036
- return null;
75317
+ return dispute.metadata;
75037
75318
  }
75038
75319
  const charge = await stripe.charges.retrieve(chargeId);
75039
75320
  const chargePaymentIntent = paymentIntentId(charge.payment_intent);
@@ -76430,6 +76711,7 @@ function registerBrowserSessionRoutes(app, deps) {
76430
76711
  await authorizeSourceSession2(deps, grant, request.sessionId, "session.control");
76431
76712
  const origin = requestOrigin(context, deps.settings);
76432
76713
  const authority = browserAuthorityRoot(deps);
76714
+ let prepared = null;
76433
76715
  try {
76434
76716
  assertEphemeralBrowserCreateEnabled(
76435
76717
  request,
@@ -76446,7 +76728,7 @@ function registerBrowserSessionRoutes(app, deps) {
76446
76728
  );
76447
76729
  }
76448
76730
  if (existing) assertCreateReplay(request, existing.session);
76449
- let prepared = existing ? await prepareBrowserSessionCreate(
76731
+ prepared = existing ? await prepareBrowserSessionCreate(
76450
76732
  deps.db,
76451
76733
  browserCreateInput(grant, workspaceId, request, existing.session.placement)
76452
76734
  ) : null;
@@ -76661,6 +76943,16 @@ function registerBrowserSessionRoutes(app, deps) {
76661
76943
  parsed.operation.state === "completed" && !parsed.operation.replayed ? 201 : 200
76662
76944
  );
76663
76945
  } catch (error) {
76946
+ if (prepared) {
76947
+ await failBrowserSessionOperation(deps.db, {
76948
+ accountId: grant.accountId,
76949
+ workspaceId,
76950
+ operationId: request.operationId,
76951
+ browserSessionId: prepared.session.id,
76952
+ onlyIfPreparedCreate: true,
76953
+ error: interactionFailure(error)
76954
+ }).catch(() => void 0);
76955
+ }
76664
76956
  throw browserRouteError(error);
76665
76957
  }
76666
76958
  });
@@ -79662,6 +79954,7 @@ async function ensureInteractionHolder(deps, grant, sourceSession, browserSessio
79662
79954
  },
79663
79955
  os: placement.lease.os,
79664
79956
  image: sandboxRuntime.image,
79957
+ imagePolicy: "new_creates_only",
79665
79958
  rigVersionId: sourceSession.rigVersionId,
79666
79959
  leaseTtlMs: deps.settings.sandboxLeaseTtlMs,
79667
79960
  expectedEpoch: placement.lease.leaseEpoch,
@@ -80059,10 +80352,10 @@ async function resolveBrowserNetworkRouteLaunch(input) {
80059
80352
  browserSessionId: input.browserSessionId,
80060
80353
  operationId: input.operationId
80061
80354
  });
80062
- const route = record6.networkRouteAuthority;
80063
- if (!route) return null;
80064
- const kind = route.configuration.kind;
80065
- const providerRoute = kind === "managed" ? managedNetworkRouteForPlacement(route.configuration, route.consistency, input.placement) : void 0;
80355
+ const route2 = record6.networkRouteAuthority;
80356
+ if (!route2) return null;
80357
+ const kind = route2.configuration.kind;
80358
+ const providerRoute = kind === "managed" ? managedNetworkRouteForPlacement(route2.configuration, route2.consistency, input.placement) : void 0;
80066
80359
  if (kind !== "managed" && input.placement.kind === "external_provider") {
80067
80360
  throw new BrowserSessionStateError2(
80068
80361
  "External browser providers require a provider-managed NetworkRoute"
@@ -80073,7 +80366,7 @@ async function resolveBrowserNetworkRouteLaunch(input) {
80073
80366
  let proxyUrl;
80074
80367
  const dispatched = record6.operation?.state === "dispatched";
80075
80368
  if (kind === "proxy") {
80076
- const reference = route.configuration.credential;
80369
+ const reference = route2.configuration.credential;
80077
80370
  if (reference) {
80078
80371
  try {
80079
80372
  const credential = await loadExactInteractionCredential(
@@ -80085,27 +80378,27 @@ async function resolveBrowserNetworkRouteLaunch(input) {
80085
80378
  credentialVersion = credential.version;
80086
80379
  proxyCredential = proxyCredentialFromBundle(credential.credential);
80087
80380
  } catch (error) {
80088
- if (route.authorityDigest && dispatched) {
80089
- return secretlessNetworkRouteReplay(route, kind, providerRoute);
80381
+ if (route2.authorityDigest && dispatched) {
80382
+ return secretlessNetworkRouteReplay(route2, kind, providerRoute);
80090
80383
  }
80091
80384
  throw error;
80092
80385
  }
80093
- if (dispatched && route.credentialVersion !== credentialVersion) {
80094
- return secretlessNetworkRouteReplay(route, kind, providerRoute);
80386
+ if (dispatched && route2.credentialVersion !== credentialVersion) {
80387
+ return secretlessNetworkRouteReplay(route2, kind, providerRoute);
80095
80388
  }
80096
80389
  }
80097
- proxyUrl = browserProxyUrl(route.configuration, proxyCredential);
80390
+ proxyUrl = browserProxyUrl(route2.configuration, proxyCredential);
80098
80391
  }
80099
80392
  if (dispatched) {
80100
- if (!route.authorityDigest) {
80393
+ if (!route2.authorityDigest) {
80101
80394
  throw new BrowserSessionStateError2("BrowserSession route authority is not bound");
80102
80395
  }
80103
80396
  return {
80104
- routeId: route.routeId,
80105
- routeVersion: route.routeVersion,
80106
- authorityDigest: route.authorityDigest,
80397
+ routeId: route2.routeId,
80398
+ routeVersion: route2.routeVersion,
80399
+ authorityDigest: route2.authorityDigest,
80107
80400
  kind,
80108
- consistency: route.consistency,
80401
+ consistency: route2.consistency,
80109
80402
  ...proxyUrl === void 0 ? {} : { proxyUrl },
80110
80403
  ...providerRoute === void 0 ? {} : { providerRoute }
80111
80404
  };
@@ -80115,11 +80408,11 @@ async function resolveBrowserNetworkRouteLaunch(input) {
80115
80408
  accountId: input.grant.accountId,
80116
80409
  workspaceId: input.workspaceId,
80117
80410
  browserSessionId: input.browserSessionId,
80118
- routeId: route.routeId,
80119
- routeVersion: route.routeVersion,
80411
+ routeId: route2.routeId,
80412
+ routeVersion: route2.routeVersion,
80120
80413
  credentialVersion,
80121
- configuration: route.configuration,
80122
- consistency: route.consistency,
80414
+ configuration: route2.configuration,
80415
+ consistency: route2.consistency,
80123
80416
  proxyCredential
80124
80417
  });
80125
80418
  const bound = await bindBrowserSessionNetworkRouteAuthority(input.deps.db, {
@@ -80127,7 +80420,7 @@ async function resolveBrowserNetworkRouteLaunch(input) {
80127
80420
  workspaceId: input.workspaceId,
80128
80421
  browserSessionId: input.browserSessionId,
80129
80422
  operationId: input.operationId,
80130
- routeVersion: route.routeVersion,
80423
+ routeVersion: route2.routeVersion,
80131
80424
  credentialVersion,
80132
80425
  authorityDigest
80133
80426
  });
@@ -80135,23 +80428,23 @@ async function resolveBrowserNetworkRouteLaunch(input) {
80135
80428
  throw new BrowserSessionOperationConflictError("BrowserSession route launch authority changed");
80136
80429
  }
80137
80430
  return {
80138
- routeId: route.routeId,
80139
- routeVersion: route.routeVersion,
80431
+ routeId: route2.routeId,
80432
+ routeVersion: route2.routeVersion,
80140
80433
  authorityDigest,
80141
80434
  kind,
80142
- consistency: route.consistency,
80435
+ consistency: route2.consistency,
80143
80436
  ...proxyUrl === void 0 ? {} : { proxyUrl },
80144
80437
  ...providerRoute === void 0 ? {} : { providerRoute }
80145
80438
  };
80146
80439
  }
80147
80440
  async function resolveActiveBrowserNetworkRouteLaunch(input) {
80148
- const route = input.record.networkRouteAuthority;
80149
- if (!route) return null;
80150
- if (!route.authorityDigest) {
80441
+ const route2 = input.record.networkRouteAuthority;
80442
+ if (!route2) return null;
80443
+ if (!route2.authorityDigest) {
80151
80444
  throw new BrowserSessionStateError2("BrowserSession route authority is not bound");
80152
80445
  }
80153
- const kind = route.configuration.kind;
80154
- const providerRoute = kind === "managed" ? managedNetworkRouteForPlacement(route.configuration, route.consistency, input.placement) : void 0;
80446
+ const kind = route2.configuration.kind;
80447
+ const providerRoute = kind === "managed" ? managedNetworkRouteForPlacement(route2.configuration, route2.consistency, input.placement) : void 0;
80155
80448
  if (kind !== "managed" && input.placement.kind === "external_provider") {
80156
80449
  throw new BrowserSessionStateError2(
80157
80450
  "External browser providers require a provider-managed NetworkRoute"
@@ -80159,7 +80452,7 @@ async function resolveActiveBrowserNetworkRouteLaunch(input) {
80159
80452
  }
80160
80453
  let proxyUrl;
80161
80454
  if (kind === "proxy") {
80162
- const reference = route.configuration.credential;
80455
+ const reference = route2.configuration.credential;
80163
80456
  let proxyCredential = null;
80164
80457
  if (reference) {
80165
80458
  const credential = await loadExactInteractionCredential(
@@ -80168,35 +80461,35 @@ async function resolveActiveBrowserNetworkRouteLaunch(input) {
80168
80461
  input.record.session.workspaceId,
80169
80462
  reference
80170
80463
  );
80171
- if (credential.version !== route.credentialVersion) {
80464
+ if (credential.version !== route2.credentialVersion) {
80172
80465
  throw new BrowserSessionStateError2(
80173
80466
  "BrowserSession proxy credential changed; resume the browser to reconnect safely"
80174
80467
  );
80175
80468
  }
80176
80469
  proxyCredential = proxyCredentialFromBundle(credential.credential);
80177
80470
  }
80178
- proxyUrl = browserProxyUrl(route.configuration, proxyCredential);
80471
+ proxyUrl = browserProxyUrl(route2.configuration, proxyCredential);
80179
80472
  }
80180
80473
  return {
80181
- routeId: route.routeId,
80182
- routeVersion: route.routeVersion,
80183
- authorityDigest: route.authorityDigest,
80474
+ routeId: route2.routeId,
80475
+ routeVersion: route2.routeVersion,
80476
+ authorityDigest: route2.authorityDigest,
80184
80477
  kind,
80185
- consistency: route.consistency,
80478
+ consistency: route2.consistency,
80186
80479
  ...proxyUrl === void 0 ? {} : { proxyUrl },
80187
80480
  ...providerRoute === void 0 ? {} : { providerRoute }
80188
80481
  };
80189
80482
  }
80190
- function secretlessNetworkRouteReplay(route, kind, providerRoute) {
80191
- if (!route.authorityDigest) {
80483
+ function secretlessNetworkRouteReplay(route2, kind, providerRoute) {
80484
+ if (!route2.authorityDigest) {
80192
80485
  throw new BrowserSessionStateError2("BrowserSession route authority is not bound");
80193
80486
  }
80194
80487
  return {
80195
- routeId: route.routeId,
80196
- routeVersion: route.routeVersion,
80197
- authorityDigest: route.authorityDigest,
80488
+ routeId: route2.routeId,
80489
+ routeVersion: route2.routeVersion,
80490
+ authorityDigest: route2.authorityDigest,
80198
80491
  kind,
80199
- consistency: route.consistency,
80492
+ consistency: route2.consistency,
80200
80493
  ...providerRoute === void 0 ? {} : { providerRoute }
80201
80494
  };
80202
80495
  }
@@ -80633,6 +80926,7 @@ function registerComputerSessionRoutes(app, deps) {
80633
80926
  await authorizeSourceSession3(deps, grant, request.sessionId, "session.control");
80634
80927
  const origin = requestOrigin2(context, deps.settings);
80635
80928
  const authority = controllerAuthorityRoot(deps);
80929
+ let prepared = null;
80636
80930
  try {
80637
80931
  const existing = await findComputerSessionControlRecordByOperation(deps.db, {
80638
80932
  accountId: grant.accountId,
@@ -80645,7 +80939,7 @@ function registerComputerSessionRoutes(app, deps) {
80645
80939
  );
80646
80940
  }
80647
80941
  if (existing) assertCreateReplay2(request, existing.session);
80648
- let prepared = existing ? await prepareComputerSessionCreate(
80942
+ prepared = existing ? await prepareComputerSessionCreate(
80649
80943
  deps.db,
80650
80944
  computerCreateInput(grant, workspaceId, request, existing.session.placement)
80651
80945
  ) : null;
@@ -80791,6 +81085,16 @@ function registerComputerSessionRoutes(app, deps) {
80791
81085
  parsed.operation.state === "completed" && !parsed.operation.replayed ? 201 : 200
80792
81086
  );
80793
81087
  } catch (error) {
81088
+ if (prepared) {
81089
+ await failComputerSessionOperation(deps.db, {
81090
+ accountId: grant.accountId,
81091
+ workspaceId,
81092
+ operationId: request.operationId,
81093
+ computerSessionId: prepared.session.id,
81094
+ onlyIfPreparedCreate: true,
81095
+ error: interactionFailure2(error)
81096
+ }).catch(() => void 0);
81097
+ }
80794
81098
  throw computerRouteError(error);
80795
81099
  }
80796
81100
  });
@@ -81669,6 +81973,7 @@ function registerComputerSessionRoutes(app, deps) {
81669
81973
  },
81670
81974
  os: placement.lease.os,
81671
81975
  image: sandboxRuntime.image,
81976
+ imagePolicy: "new_creates_only",
81672
81977
  rigVersionId: sourceSession.rigVersionId,
81673
81978
  leaseTtlMs: deps.settings.sandboxLeaseTtlMs,
81674
81979
  expectedEpoch: placement.lease.leaseEpoch,
@@ -84707,7 +85012,7 @@ import {
84707
85012
  } from "@opengeni/contracts";
84708
85013
  import {
84709
85014
  getWorkspaceInteractionRevisionState,
84710
- listSessionEvents,
85015
+ listSessionEventPage as listSessionEventPage2,
84711
85016
  listWorkspaceControlEvents
84712
85017
  } from "@opengeni/db";
84713
85018
  import {
@@ -84719,6 +85024,7 @@ import {
84719
85024
  } from "@opengeni/events";
84720
85025
  import { MANAGED_AUTH_ACTOR_EPOCH_HEADER } from "@opengeni/core/managed-auth-session-sets";
84721
85026
  var SESSION_REPLAY_PAGE_SIZE = 100;
85027
+ var SESSION_REPLAY_PAGE_MAX_BYTES = 8 * 1024 * 1024;
84722
85028
  var WORKSPACE_CONTROL_REPLAY_PAGE_SIZE = 100;
84723
85029
  var SSE_QUEUED_FRAME_MAX_COUNT = 1;
84724
85030
  var SSE_WRITE_STALL_TIMEOUT_MS = 3e4;
@@ -84894,9 +85200,10 @@ function createLatestWinsDelivery(send, onError) {
84894
85200
  }
84895
85201
  async function sseSessionStream(db, bus, workspaceId, sessionId, after, signal, options = {}) {
84896
85202
  if (isHttp1BrowserBatch(options)) {
84897
- const events = await listSessionEvents(db, workspaceId, sessionId, {
85203
+ const { events } = await listSessionEventPage2(db, workspaceId, sessionId, {
84898
85204
  after,
84899
- limit: SESSION_REPLAY_PAGE_SIZE
85205
+ limit: SESSION_REPLAY_PAGE_SIZE,
85206
+ maxBytes: options.finiteResponseMaxBytes ?? HTTP1_BROWSER_SSE_BATCH_MAX_BYTES
84900
85207
  });
84901
85208
  await options.reauthorize?.();
84902
85209
  const compactProjection = coalesceSessionEventDeltasWithCoverage(events);
@@ -84969,10 +85276,12 @@ async function sseSessionStream(db, bus, workspaceId, sessionId, after, signal,
84969
85276
  if (targetSequence !== void 0 && lastSent >= targetSequence) return;
84970
85277
  const previousLastSent = lastSent;
84971
85278
  const limit = targetSequence === void 0 ? SESSION_REPLAY_PAGE_SIZE : Math.min(SESSION_REPLAY_PAGE_SIZE, targetSequence - lastSent);
84972
- const page2 = await listSessionEvents(db, workspaceId, sessionId, {
85279
+ const replayPage = await listSessionEventPage2(db, workspaceId, sessionId, {
84973
85280
  after: lastSent,
84974
- limit
85281
+ limit,
85282
+ maxBytes: SESSION_REPLAY_PAGE_MAX_BYTES
84975
85283
  });
85284
+ const page2 = replayPage.events;
84976
85285
  const eligible = targetSequence === void 0 ? page2 : page2.filter((event) => event.sequence <= targetSequence);
84977
85286
  if (eligible.length === 0) {
84978
85287
  if (targetSequence === void 0) return;
@@ -84990,6 +85299,7 @@ async function sseSessionStream(db, bus, workspaceId, sessionId, after, signal,
84990
85299
  throw new Error(`Session event replay made no progress after sequence ${lastSent}`);
84991
85300
  }
84992
85301
  if (targetSequence !== void 0 && lastSent >= targetSequence) return;
85302
+ if (targetSequence === void 0 && !replayPage.hasMore) return;
84993
85303
  }
84994
85304
  };
84995
85305
  let durableDeliveryTail = Promise.resolve();
@@ -86212,6 +86522,7 @@ import {
86212
86522
  defaultPrReviewProviderBaseUrl,
86213
86523
  workspaceCustomModelReference as workspaceCustomModelReference2,
86214
86524
  lockActiveCustomModelForAdmission as lockActiveCustomModelForAdmission2,
86525
+ modelUnavailableHttpException as modelUnavailableHttpException2,
86215
86526
  prReviewWebhookAuthKind,
86216
86527
  normalizePrReviewProviderBaseUrl,
86217
86528
  requireAccessGrant as requireAccessGrant26,
@@ -86470,7 +86781,7 @@ import { HTTPException as HTTPException68 } from "hono/http-exception";
86470
86781
 
86471
86782
  // src/routes/automations.ts
86472
86783
  import { environmentsEncryptionKeyBytes as environmentsEncryptionKeyBytes7 } from "@opengeni/config";
86473
- import { requireAgentConfigAdmission as requireAgentConfigAdmission2 } from "@opengeni/core";
86784
+ import { modelUnavailableHttpException, requireAgentConfigAdmission as requireAgentConfigAdmission2 } from "@opengeni/core";
86474
86785
  import {
86475
86786
  AUTOMATION_WEBHOOK_MAX_BYTES,
86476
86787
  AUTOMATION_MAX_MATCHED_TRIGGERS,
@@ -87076,9 +87387,7 @@ function workspaceCustomModelCommitGuard(input) {
87076
87387
  reference
87077
87388
  });
87078
87389
  if (!active) {
87079
- throw new HTTPException67(422, {
87080
- message: `model is not available: ${input.modelId}`
87081
- });
87390
+ throw modelUnavailableHttpException(input.modelId);
87082
87391
  }
87083
87392
  };
87084
87393
  }
@@ -87174,12 +87483,12 @@ function registerPrReviewGitHubRoutes(app, deps) {
87174
87483
  if (installationId === null || repositoryId === null) {
87175
87484
  return c.json(ignoredWebhook("unsupported_event"), 202);
87176
87485
  }
87177
- const route = await resolveManagedGitHubPrReviewRoute(deps.db, {
87486
+ const route2 = await resolveManagedGitHubPrReviewRoute(deps.db, {
87178
87487
  installationId: String(installationId),
87179
87488
  providerRepositoryId: String(repositoryId)
87180
87489
  });
87181
- if (!route) return c.json(ignoredWebhook("repository_not_connected"), 202);
87182
- const source = await getAutomationSourceSecret2(deps.db, route);
87490
+ if (!route2) return c.json(ignoredWebhook("repository_not_connected"), 202);
87491
+ const source = await getAutomationSourceSecret2(deps.db, route2);
87183
87492
  if (!source || source.status !== "active") {
87184
87493
  return c.json(ignoredWebhook("source_disabled"), 202);
87185
87494
  }
@@ -88212,9 +88521,7 @@ function prReviewCustomModelCommitGuard(input) {
88212
88521
  reference
88213
88522
  });
88214
88523
  if (!active) {
88215
- throw new HTTPException69(422, {
88216
- message: `model is not available: ${input.modelId}`
88217
- });
88524
+ throw modelUnavailableHttpException2(input.modelId);
88218
88525
  }
88219
88526
  };
88220
88527
  }
@@ -89653,8 +89960,8 @@ function registerSkillContentRoutes(app, deps) {
89653
89960
  const workspaceId = c.req.param("workspaceId");
89654
89961
  const grant = await requireAccessGrant29(c, deps, workspaceId, "workspace:read");
89655
89962
  const skillId = skillIdentifier(c.req.param("skillId"));
89656
- const revision = c.req.query("revisionId");
89657
- const revisionId2 = revision === void 0 ? void 0 : skillIdentifier(revision);
89963
+ const revision2 = c.req.query("revisionId");
89964
+ const revisionId2 = revision2 === void 0 ? void 0 : skillIdentifier(revision2);
89658
89965
  const skill = await readSkill(
89659
89966
  deps.db,
89660
89967
  { accountId: grant.accountId, workspaceId, subjectId: grant.subjectId },
@@ -90906,6 +91213,7 @@ function registerScheduledTaskRoutes(app, deps) {
90906
91213
 
90907
91214
  // src/routes/sessions.ts
90908
91215
  import { getRetainedProviderCommand } from "@opengeni/db/retained-provider-commands";
91216
+ import { UnsupportedLatencyModeError } from "@opengeni/config";
90909
91217
  import { searchSessionMessagesForSubject, SessionMessageSearchCursorError } from "@opengeni/db";
90910
91218
  import { listSessionEventSlices } from "@opengeni/db/session-event-slices";
90911
91219
  import * as sessionPreviewSchema from "@opengeni/db/schema";
@@ -91027,7 +91335,7 @@ import {
91027
91335
  getSessionQueueSnapshot as getSessionQueueSnapshot2,
91028
91336
  getStreamAcknowledgment,
91029
91337
  insertPtySession,
91030
- listSessionEventPage as listSessionEventPage2,
91338
+ listSessionEventPage as listSessionEventPage3,
91031
91339
  listSessionHumanInputRequests,
91032
91340
  listSessionGoalRevisionPage,
91033
91341
  listSessionGoalRevisions,
@@ -91036,6 +91344,7 @@ import {
91036
91344
  listSessionDiscoverySummaries as listSessionDiscoverySummaries2,
91037
91345
  listSessionDiscoveryAncestorPaths,
91038
91346
  listSessionsForSubject,
91347
+ listSessionEntriesForSubject,
91039
91348
  getLatestStartedSessionTurn,
91040
91349
  listSessionTurns,
91041
91350
  projectEffectiveControlForRelatedAccess as projectEffectiveControlForRelatedAccess2,
@@ -91905,8 +92214,7 @@ import {
91905
92214
  workflowIdForSession as workflowIdForSession3,
91906
92215
  sessionWithEffectiveToolPolicy as sessionWithEffectiveToolPolicy2,
91907
92216
  workspaceSessionEffectiveToolsContext as workspaceSessionEffectiveToolsContext2,
91908
- workspaceSessionToolPolicyDefaultServerIds as workspaceSessionToolPolicyDefaultServerIds2,
91909
- workspaceSessionToolPolicyServerIds as workspaceSessionToolPolicyServerIds2,
92217
+ workspaceSessionToolPolicyContext,
91910
92218
  relayConfigFromSettings as relayConfigFromSettings5,
91911
92219
  validateVariableSetAttachment
91912
92220
  } from "@opengeni/core";
@@ -92517,33 +92825,44 @@ function registerSessionRoutes(app, deps) {
92517
92825
  }
92518
92826
  const pageView = c.req.query("view") === "page";
92519
92827
  const query = sessionListQuery(c.req.query(), pageView);
92828
+ const projection = c.req.query("projection");
92829
+ if (projection !== void 0 && projection !== "summary")
92830
+ throw new HTTPException80(400, { message: 'projection must be "summary"' });
92831
+ if (projection && !pageView)
92832
+ throw new HTTPException80(400, { message: 'summary projection requires view="page"' });
92833
+ const summary = projection === "summary";
92520
92834
  let page2;
92521
92835
  try {
92522
- page2 = await listSessionsForSubject(db, workspaceId, {
92523
- subjectId: grant.subjectId,
92524
- limit: boundedLimit(query.limit),
92525
- materializeSnapshot: pageView,
92526
- ...query.cursor ? { cursor: query.cursor } : {},
92527
- ...query.search ? { search: query.search } : {},
92528
- ...query.pinsOnly ? { pinsOnly: true } : {},
92529
- ...query.archivedOnly ? { archivedOnly: true } : {},
92530
- ...query.sortBy ? { sortBy: query.sortBy } : {},
92531
- ...query.archiveStatus ? { archiveStatus: query.archiveStatus } : {},
92532
- ...query.parentSessionId !== void 0 ? { parentSessionId: query.parentSessionId } : {},
92533
- ...query.channelId !== void 0 ? { channelId: query.channelId } : {},
92534
- ...query.originSiteId ? { originSiteId: query.originSiteId } : {},
92535
- ...query.createdBy ? { createdBy: query.createdBy } : {},
92536
- ...query.updatedFrom ? { updatedFrom: query.updatedFrom } : {},
92537
- ...query.updatedBefore ? { updatedBefore: query.updatedBefore } : {},
92538
- ...query.createdFrom ? { createdFrom: query.createdFrom } : {},
92539
- ...query.createdBefore ? { createdBefore: query.createdBefore } : {},
92540
- ...query.scopeSubjectId ? { scopeSubjectId: query.scopeSubjectId } : {},
92541
- ...authorizationScope ? { authorizationScope } : {},
92542
- // A managed human's own personal workspace has no membership row, so
92543
- // the list's removal fence must fall back to the organization-membership
92544
- // pointer, only with verified native or external owning-user provenance.
92545
- personalWorkspaceOwnerException: hasVerifiedOwningUserAuthorization(authorization)
92546
- });
92836
+ page2 = await (summary ? listSessionEntriesForSubject : listSessionsForSubject)(
92837
+ db,
92838
+ workspaceId,
92839
+ {
92840
+ subjectId: grant.subjectId,
92841
+ limit: boundedLimit(query.limit),
92842
+ materializeSnapshot: pageView,
92843
+ ...query.cursor ? { cursor: query.cursor } : {},
92844
+ ...query.search ? { search: query.search } : {},
92845
+ ...query.pinsOnly ? { pinsOnly: true } : {},
92846
+ ...query.includePinned === false ? { includePinned: false } : {},
92847
+ ...query.archivedOnly ? { archivedOnly: true } : {},
92848
+ ...query.sortBy ? { sortBy: query.sortBy } : {},
92849
+ ...query.archiveStatus ? { archiveStatus: query.archiveStatus } : {},
92850
+ ...query.parentSessionId !== void 0 ? { parentSessionId: query.parentSessionId } : {},
92851
+ ...query.channelId !== void 0 ? { channelId: query.channelId } : {},
92852
+ ...query.originSiteId ? { originSiteId: query.originSiteId } : {},
92853
+ ...query.createdBy ? { createdBy: query.createdBy } : {},
92854
+ ...query.updatedFrom ? { updatedFrom: query.updatedFrom } : {},
92855
+ ...query.updatedBefore ? { updatedBefore: query.updatedBefore } : {},
92856
+ ...query.createdFrom ? { createdFrom: query.createdFrom } : {},
92857
+ ...query.createdBefore ? { createdBefore: query.createdBefore } : {},
92858
+ ...query.scopeSubjectId ? { scopeSubjectId: query.scopeSubjectId } : {},
92859
+ ...authorizationScope ? { authorizationScope } : {},
92860
+ // A managed human's own personal workspace has no membership row, so
92861
+ // the list's removal fence must fall back to the organization-membership
92862
+ // pointer, only with verified native or external owning-user provenance.
92863
+ personalWorkspaceOwnerException: hasVerifiedOwningUserAuthorization(authorization)
92864
+ }
92865
+ );
92547
92866
  } catch (error) {
92548
92867
  if (error instanceof SessionListAccessError) {
92549
92868
  throw new HTTPException80(403, { message: error.message });
@@ -92557,10 +92876,6 @@ function registerSessionRoutes(app, deps) {
92557
92876
  throw error;
92558
92877
  }
92559
92878
  c.header("x-opengeni-pinned-truncated", page2.pinnedTruncated === true ? "true" : "false");
92560
- const policy = await loadEffectivePolicyContext(deps, workspaceId, grant.subjectId, [
92561
- ...page2.pinned,
92562
- ...page2.sessions
92563
- ]);
92564
92879
  const commandActivity = await backgroundCommandActivityForSessions(db, {
92565
92880
  accountId: grant.accountId,
92566
92881
  workspaceId,
@@ -92571,14 +92886,33 @@ function registerSessionRoutes(app, deps) {
92571
92886
  workspaceId,
92572
92887
  [...page2.pinned, ...page2.sessions].map((session) => session.id)
92573
92888
  ) : /* @__PURE__ */ new Set();
92889
+ if ("projection" in page2) {
92890
+ const decorateEntry = (session) => ({
92891
+ ...session,
92892
+ hasSchedules: scheduleTargets.has(session.id),
92893
+ ...commandActivity.has(session.id) ? { backgroundCommandActivity: commandActivity.get(session.id) } : {}
92894
+ });
92895
+ return c.json({
92896
+ ...page2,
92897
+ ...query.hasPageFilters ? { filtersApplied: true } : {},
92898
+ ...query.originSiteId ? { originSiteId: query.originSiteId } : {},
92899
+ pinned: page2.pinned.map(decorateEntry),
92900
+ sessions: page2.sessions.map(decorateEntry)
92901
+ });
92902
+ }
92903
+ const policy = await loadEffectivePolicyContext(deps, workspaceId, grant.subjectId, [
92904
+ ...page2.pinned,
92905
+ ...page2.sessions
92906
+ ]);
92574
92907
  const decorate = (session) => {
92575
92908
  const activity = commandActivity.get(session.id);
92909
+ const decorated = {
92910
+ ...session,
92911
+ hasSchedules: scheduleTargets.has(session.id),
92912
+ ...activity ? { backgroundCommandActivity: activity } : {}
92913
+ };
92576
92914
  return sessionWithEffectiveToolPolicy2(
92577
- {
92578
- ...session,
92579
- hasSchedules: scheduleTargets.has(session.id),
92580
- ...activity ? { backgroundCommandActivity: activity } : {}
92581
- },
92915
+ decorated,
92582
92916
  policy.workspaceServerIds,
92583
92917
  policy.workspaceDefaultServerIds,
92584
92918
  policy.effectiveToolsContext
@@ -94081,20 +94415,20 @@ function registerSessionRoutes(app, deps) {
94081
94415
  const sessionId = c.req.param("sessionId");
94082
94416
  await assertSessionExists(db, workspaceId, sessionId);
94083
94417
  const payload = await parseRequestJson(c, ApplySessionGoalRevisionRequest);
94084
- const revision = await getSessionGoalRevision(
94418
+ const revision2 = await getSessionGoalRevision(
94085
94419
  db,
94086
94420
  workspaceId,
94087
94421
  sessionId,
94088
94422
  c.req.param("revisionId")
94089
94423
  );
94090
- if (!revision || revision.disposition !== "proposed") {
94424
+ if (!revision2 || revision2.disposition !== "proposed") {
94091
94425
  throw new HTTPException80(404, {
94092
94426
  message: "goal rewrite proposal not found"
94093
94427
  });
94094
94428
  }
94095
- if (!goalProposalMatchesExpectedRevision(revision, payload.expectedObjectiveRevision)) {
94429
+ if (!goalProposalMatchesExpectedRevision(revision2, payload.expectedObjectiveRevision)) {
94096
94430
  throw new HTTPException80(409, {
94097
- message: `goal proposal was based on objective revision ${revision.baseObjectiveRevision}; requested fence ${payload.expectedObjectiveRevision} cannot apply it`
94431
+ message: `goal proposal was based on objective revision ${revision2.baseObjectiveRevision}; requested fence ${payload.expectedObjectiveRevision} cannot apply it`
94098
94432
  });
94099
94433
  }
94100
94434
  try {
@@ -94102,15 +94436,15 @@ function registerSessionRoutes(app, deps) {
94102
94436
  accountId: grant.accountId,
94103
94437
  workspaceId,
94104
94438
  sessionId,
94105
- text: revision.text,
94106
- successCriteria: revision.successCriteria,
94107
- rootConstraints: revision.rootConstraints,
94108
- mutationPolicy: revision.mutationPolicy,
94439
+ text: revision2.text,
94440
+ successCriteria: revision2.successCriteria,
94441
+ rootConstraints: revision2.rootConstraints,
94442
+ mutationPolicy: revision2.mutationPolicy,
94109
94443
  expectedObjectiveRevision: payload.expectedObjectiveRevision,
94110
- expectedGoalId: revision.goalId,
94111
- changeKind: revision.changeKind,
94112
- changeRationale: payload.rationale ?? `Applied goal proposal ${revision.id}`,
94113
- sourceProposalId: revision.id,
94444
+ expectedGoalId: revision2.goalId,
94445
+ changeKind: revision2.changeKind,
94446
+ changeRationale: payload.rationale ?? `Applied goal proposal ${revision2.id}`,
94447
+ sourceProposalId: revision2.id,
94114
94448
  createdBy: "api",
94115
94449
  actor: "api"
94116
94450
  });
@@ -94171,13 +94505,13 @@ function registerSessionRoutes(app, deps) {
94171
94505
  const sessionId = c.req.param("sessionId");
94172
94506
  await assertSessionExists(db, workspaceId, sessionId);
94173
94507
  const payload = await parseRequestJson(c, RollbackSessionGoalRevisionRequest);
94174
- const revision = await getSessionGoalRevision(
94508
+ const revision2 = await getSessionGoalRevision(
94175
94509
  db,
94176
94510
  workspaceId,
94177
94511
  sessionId,
94178
94512
  c.req.param("revisionId")
94179
94513
  );
94180
- if (!revision || revision.disposition !== "applied") {
94514
+ if (!revision2 || revision2.disposition !== "applied") {
94181
94515
  throw new HTTPException80(404, { message: "applied goal revision not found" });
94182
94516
  }
94183
94517
  try {
@@ -94185,15 +94519,15 @@ function registerSessionRoutes(app, deps) {
94185
94519
  accountId: grant.accountId,
94186
94520
  workspaceId,
94187
94521
  sessionId,
94188
- text: revision.text,
94189
- successCriteria: revision.successCriteria,
94190
- rootConstraints: revision.rootConstraints,
94191
- mutationPolicy: revision.mutationPolicy,
94522
+ text: revision2.text,
94523
+ successCriteria: revision2.successCriteria,
94524
+ rootConstraints: revision2.rootConstraints,
94525
+ mutationPolicy: revision2.mutationPolicy,
94192
94526
  expectedObjectiveRevision: payload.expectedObjectiveRevision,
94193
- expectedGoalId: revision.goalId,
94527
+ expectedGoalId: revision2.goalId,
94194
94528
  changeKind: "replacement",
94195
94529
  changeRationale: payload.rationale,
94196
- rollbackOfRevisionId: revision.id,
94530
+ rollbackOfRevisionId: revision2.id,
94197
94531
  createdBy: "api",
94198
94532
  actor: "api"
94199
94533
  });
@@ -94476,7 +94810,7 @@ function registerSessionRoutes(app, deps) {
94476
94810
  mode === "monitoring" ? 40 : 500
94477
94811
  );
94478
94812
  const dbPayloadMode = resultMode === "compact" ? "full" : payloadMode;
94479
- const dbPage = await listSessionEventPage2(db, workspaceId, sessionId, {
94813
+ const dbPage = await listSessionEventPage3(db, workspaceId, sessionId, {
94480
94814
  after,
94481
94815
  ...before !== void 0 ? { before } : {},
94482
94816
  limit,
@@ -95828,13 +96162,13 @@ function registerSessionRoutes(app, deps) {
95828
96162
  const revisionParam = c.req.query("revision");
95829
96163
  let row;
95830
96164
  if (revisionParam !== void 0 && revisionParam !== "") {
95831
- const revision = Number(revisionParam);
95832
- if (!Number.isInteger(revision) || revision < 0) {
96165
+ const revision2 = Number(revisionParam);
96166
+ if (!Number.isInteger(revision2) || revision2 < 0) {
95833
96167
  throw new HTTPException80(400, {
95834
96168
  message: "revision must be a non-negative integer"
95835
96169
  });
95836
96170
  }
95837
- row = await workspaceCaptureAtRevision(db, workspaceId, sessionId, revision);
96171
+ row = await workspaceCaptureAtRevision(db, workspaceId, sessionId, revision2);
95838
96172
  } else {
95839
96173
  row = await latestWorkspaceCapture(db, workspaceId, sessionId);
95840
96174
  }
@@ -96077,8 +96411,8 @@ function registerSessionRoutes(app, deps) {
96077
96411
  return c.body(null, 204);
96078
96412
  });
96079
96413
  }
96080
- function goalProposalMatchesExpectedRevision(revision, expectedObjectiveRevision) {
96081
- return revision.baseObjectiveRevision === expectedObjectiveRevision;
96414
+ function goalProposalMatchesExpectedRevision(revision2, expectedObjectiveRevision) {
96415
+ return revision2.baseObjectiveRevision === expectedObjectiveRevision;
96082
96416
  }
96083
96417
  function eventListLimit(raw, max = 2e3, fallback = 500) {
96084
96418
  const limit = Number(raw ?? fallback);
@@ -96367,6 +96701,13 @@ function sessionListQuery(query, allowCursor = true) {
96367
96701
  });
96368
96702
  }
96369
96703
  const pinsOnly = query.pinsOnly === "true";
96704
+ if (query.includePinned !== void 0 && !["true", "false"].includes(query.includePinned)) {
96705
+ throw new HTTPException80(400, { message: 'includePinned must be "true" or "false"' });
96706
+ }
96707
+ const includePinned = query.includePinned !== "false";
96708
+ if (pinsOnly && !includePinned) {
96709
+ throw new HTTPException80(400, { message: "pinsOnly requires includePinned" });
96710
+ }
96370
96711
  if (query.archivedOnly !== void 0 && query.archivedOnly !== "true") {
96371
96712
  throw new HTTPException80(400, { message: 'archivedOnly must be the literal "true"' });
96372
96713
  }
@@ -96461,6 +96802,7 @@ function sessionListQuery(query, allowCursor = true) {
96461
96802
  cursor,
96462
96803
  search: search || void 0,
96463
96804
  pinsOnly,
96805
+ includePinned,
96464
96806
  archivedOnly,
96465
96807
  sortBy: sortBy.data,
96466
96808
  archiveStatus: archiveStatus.data,
@@ -96724,6 +97066,21 @@ function optionalEventSequence(raw) {
96724
97066
  return Math.floor(sequence);
96725
97067
  }
96726
97068
  function sessionCreateErrorResponse(c, error) {
97069
+ if (error instanceof UnsupportedLatencyModeError) {
97070
+ return c.json(
97071
+ {
97072
+ code: "SESSION_CREATE_REJECTED",
97073
+ message: error.message,
97074
+ details: {
97075
+ code: error.code,
97076
+ modelId: error.modelId,
97077
+ latencyMode: error.latencyMode,
97078
+ allowedLatencyModes: [...error.allowedLatencyModes]
97079
+ }
97080
+ },
97081
+ 422
97082
+ );
97083
+ }
96727
97084
  if (error instanceof SessionCreateConnectionSelectionUnavailableError2) {
96728
97085
  return c.json({ code: error.code, message: error.message, retryable: false }, 409);
96729
97086
  }
@@ -96856,12 +97213,11 @@ function commandConflictResponse(c, error) {
96856
97213
  throw error;
96857
97214
  }
96858
97215
  async function loadEffectivePolicyContext(deps, workspaceId, subjectId, sessions) {
96859
- const [workspaceServerIds, workspaceDefaultServerIds, effectiveToolsContext] = await Promise.all([
96860
- workspaceSessionToolPolicyServerIds2(deps.db, workspaceId, deps.settings, subjectId),
96861
- workspaceSessionToolPolicyDefaultServerIds2(deps.db, workspaceId, deps.settings, subjectId),
97216
+ const [policy, effectiveToolsContext] = await Promise.all([
97217
+ workspaceSessionToolPolicyContext(deps.db, workspaceId, deps.settings, subjectId),
96862
97218
  workspaceSessionEffectiveToolsContext2(deps, workspaceId, subjectId, sessions)
96863
97219
  ]);
96864
- return { workspaceServerIds, workspaceDefaultServerIds, effectiveToolsContext };
97220
+ return { ...policy, effectiveToolsContext };
96865
97221
  }
96866
97222
  async function withEffectivePolicy(deps, workspaceId, subjectId, session) {
96867
97223
  const policy = await loadEffectivePolicyContext(deps, workspaceId, subjectId, [session]);
@@ -97330,9 +97686,12 @@ function socialHttpException(error) {
97330
97686
  }
97331
97687
 
97332
97688
  // src/routes/workspaces.ts
97689
+ import { withDirectModelProviders } from "@opengeni/config";
97690
+ import { listConnectionsMetadata as listConnectionsMetadata7 } from "@opengeni/db";
97333
97691
  import {
97334
- getWorkspaceProviderApiKeyConnectionMetadata as getWorkspaceProviderApiKeyConnectionMetadata3,
97335
- listWorkspaceProviderCustomModels as listWorkspaceProviderCustomModels2
97692
+ workspaceProviderApiKeyConnectionMetadataFromConnections,
97693
+ listWorkspaceProviderCustomModelsByKind,
97694
+ getOrganizationModelProviderCatalogForWorkspace
97336
97695
  } from "@opengeni/db";
97337
97696
  import { CLAUDE_CONNECTION_KINDS } from "@opengeni/config";
97338
97697
  import { SessionControlConflictError as SessionControlConflictError2, WorkspacePauseTimerInputError } from "@opengeni/db";
@@ -97407,9 +97766,6 @@ import {
97407
97766
  workspaceControlRequestLockTimeoutMs as workspaceControlRequestLockTimeoutMs2,
97408
97767
  workspaceXaiSubscriptionActive,
97409
97768
  workspaceVercelAiGatewayConnectionActive,
97410
- workspaceOpenRouterConnectionActive,
97411
- organizationModelProviderConnectionActiveForWorkspace,
97412
- listOrganizationModelProviderCustomModelsForWorkspace,
97413
97769
  WorkspaceGatewayCustomModelHistoryLimitError,
97414
97770
  WorkspaceOpenRouterCustomModelHistoryLimitError,
97415
97771
  WorkspaceExternalIdentityConflictError,
@@ -97542,6 +97898,11 @@ async function deleteWorkspaceForRequest(deps, input) {
97542
97898
  if (state === "P0002") {
97543
97899
  throw new HTTPException83(404, { message: "workspace not found" });
97544
97900
  }
97901
+ if (state === "23503") {
97902
+ throw new HTTPException83(409, {
97903
+ message: "workspace deletion is blocked by retained or linked records; keep the workspace, revoke access, and disable scheduled work instead"
97904
+ });
97905
+ }
97545
97906
  throw error;
97546
97907
  }
97547
97908
  if (deleted.status === "not_found") {
@@ -97882,6 +98243,13 @@ function registerWorkspaceRoutes(app, deps) {
97882
98243
  const workspaceId = c.req.param("workspaceId");
97883
98244
  const grant = await requireAccessGrant36(c, deps, workspaceId, "workspace:read");
97884
98245
  const catalogSettings = deps.resolveCatalogSettings();
98246
+ const providerKinds = [
98247
+ "vercel_gateway",
98248
+ "openrouter",
98249
+ ...CLAUDE_CONNECTION_KINDS.filter(
98250
+ (kind) => kind !== "claude_subscription" || deps.settings.claudeSubscriptionEnabled
98251
+ )
98252
+ ];
97885
98253
  const [
97886
98254
  connectionModelRestrictions,
97887
98255
  resolvedCatalog,
@@ -97889,14 +98257,9 @@ function registerWorkspaceRoutes(app, deps) {
97889
98257
  codexSubscriptionActive,
97890
98258
  codexModelAvailability,
97891
98259
  xaiSubscriptionActive,
97892
- workspaceGatewayConnectionActive,
97893
- workspaceGatewayCustomModels,
97894
- openRouterConnectionActive,
97895
- workspaceOpenRouterCustomModels,
97896
- organizationGatewayConnectionActive,
97897
- organizationOpenRouterConnectionActive,
97898
- organizationGatewayCustomModels,
97899
- organizationOpenRouterCustomModels,
98260
+ workspaceConnections,
98261
+ workspaceCustomModels,
98262
+ organizationProviders,
97900
98263
  workspace
97901
98264
  ] = await Promise.all([
97902
98265
  getWorkspaceConnectionModelRestrictions(deps.db, workspaceId, grant.subjectId),
@@ -97907,88 +98270,55 @@ function registerWorkspaceRoutes(app, deps) {
97907
98270
  ({ settings }) => loadWorkspaceCodexModelAvailability(deps.db, settings, workspaceId)
97908
98271
  ),
97909
98272
  workspaceXaiSubscriptionActive(deps.db, deps.settings, workspaceId, grant.subjectId),
97910
- workspaceVercelAiGatewayConnectionActive(deps.db, workspaceId),
97911
- listWorkspaceGatewayCustomModels(deps.db, {
97912
- accountId: grant.accountId,
97913
- workspaceId
97914
- }),
97915
- workspaceOpenRouterConnectionActive(deps.db, workspaceId),
97916
- listWorkspaceOpenRouterCustomModels(deps.db, {
97917
- accountId: grant.accountId,
97918
- workspaceId
97919
- }),
97920
- organizationModelProviderConnectionActiveForWorkspace(deps.db, {
98273
+ listConnectionsMetadata7(deps.db, workspaceId, null),
98274
+ listWorkspaceProviderCustomModelsByKind(deps.db, {
97921
98275
  accountId: grant.accountId,
97922
98276
  workspaceId,
97923
- providerKind: "vercel_gateway"
98277
+ providerKinds
97924
98278
  }),
97925
- organizationModelProviderConnectionActiveForWorkspace(deps.db, {
98279
+ getOrganizationModelProviderCatalogForWorkspace(deps.db, {
97926
98280
  accountId: grant.accountId,
97927
98281
  workspaceId,
97928
- providerKind: "openrouter"
97929
- }),
97930
- listOrganizationModelProviderCustomModelsForWorkspace(deps.db, {
97931
- accountId: grant.accountId,
97932
- workspaceId,
97933
- providerKind: "vercel_gateway"
97934
- }),
97935
- listOrganizationModelProviderCustomModelsForWorkspace(deps.db, {
97936
- accountId: grant.accountId,
97937
- workspaceId,
97938
- providerKind: "openrouter"
98282
+ providerKinds
97939
98283
  }),
97940
98284
  getWorkspace4(deps.db, workspaceId)
97941
98285
  ]);
97942
98286
  const claudeConnections = {};
97943
98287
  const workspaceClaudeConnections = {};
97944
- await Promise.all(
97945
- CLAUDE_CONNECTION_KINDS.map(async (kind) => {
97946
- if (kind === "claude_subscription" && !deps.settings.claudeSubscriptionEnabled) return;
97947
- const [active, models] = await Promise.all([
97948
- organizationModelProviderConnectionActiveForWorkspace(deps.db, {
97949
- accountId: grant.accountId,
97950
- workspaceId,
97951
- providerKind: kind
97952
- }),
97953
- listOrganizationModelProviderCustomModelsForWorkspace(deps.db, {
97954
- accountId: grant.accountId,
97955
- workspaceId,
97956
- providerKind: kind
97957
- })
97958
- ]);
97959
- claudeConnections[kind] = { active, models };
97960
- const [metadata, workspaceModels] = await Promise.all([
97961
- getWorkspaceProviderApiKeyConnectionMetadata3(deps.db, workspaceId, kind),
97962
- listWorkspaceProviderCustomModels2(deps.db, {
97963
- accountId: grant.accountId,
97964
- workspaceId,
97965
- providerKind: kind
97966
- })
97967
- ]);
97968
- workspaceClaudeConnections[kind] = { active: metadata !== null, models: workspaceModels };
97969
- })
98288
+ const workspaceConnectionActive = (kind) => workspaceProviderApiKeyConnectionMetadataFromConnections(workspaceConnections, kind) !== null;
98289
+ for (const kind of CLAUDE_CONNECTION_KINDS) {
98290
+ if (kind === "claude_subscription" && !deps.settings.claudeSubscriptionEnabled) continue;
98291
+ claudeConnections[kind] = organizationProviders[kind];
98292
+ workspaceClaudeConnections[kind] = {
98293
+ active: workspaceConnectionActive(kind),
98294
+ models: workspaceCustomModels[kind]
98295
+ };
98296
+ }
98297
+ const workspaceCatalogSettings = withDirectModelProviders(
98298
+ resolvedCatalog.settings,
98299
+ workspaceConnections
97970
98300
  );
97971
98301
  const selections = resolveWorkspaceModelSelection2({
97972
98302
  claudeConnections,
97973
98303
  workspaceClaudeConnections,
97974
98304
  connectionModelRestrictions,
97975
- settings: resolvedCatalog.settings,
98305
+ settings: workspaceCatalogSettings,
97976
98306
  policy,
97977
98307
  observations: codexModelAvailability,
97978
98308
  codexSubscriptionActive,
97979
98309
  xaiSubscriptionActive,
97980
- workspaceGatewayConnectionActive,
97981
- workspaceGatewayCustomModels,
97982
- workspaceOpenRouterConnectionActive: openRouterConnectionActive,
97983
- workspaceOpenRouterCustomModels,
97984
- organizationGatewayConnectionActive,
97985
- organizationOpenRouterConnectionActive,
97986
- organizationGatewayCustomModels,
97987
- organizationOpenRouterCustomModels
98310
+ workspaceGatewayConnectionActive: workspaceConnectionActive("vercel_gateway"),
98311
+ workspaceGatewayCustomModels: workspaceCustomModels.vercel_gateway,
98312
+ workspaceOpenRouterConnectionActive: workspaceConnectionActive("openrouter"),
98313
+ workspaceOpenRouterCustomModels: workspaceCustomModels.openrouter,
98314
+ organizationGatewayConnectionActive: organizationProviders.vercel_gateway.active,
98315
+ organizationOpenRouterConnectionActive: organizationProviders.openrouter.active,
98316
+ organizationGatewayCustomModels: organizationProviders.vercel_gateway.models,
98317
+ organizationOpenRouterCustomModels: organizationProviders.openrouter.models
97988
98318
  });
97989
98319
  const workspaceSettings = workspace?.settings ?? {};
97990
98320
  const defaultSelection = await resolveDefaultSessionModelForSelections(deps.db, {
97991
- settings: resolvedCatalog.settings,
98321
+ settings: workspaceCatalogSettings,
97992
98322
  accountId: grant.accountId,
97993
98323
  workspaceSettings,
97994
98324
  selections
@@ -97999,7 +98329,7 @@ function registerWorkspaceRoutes(app, deps) {
97999
98329
  projectWorkspaceModelCatalog(selections, {
98000
98330
  defaultSelection,
98001
98331
  creditsSelection: creditsDefaultSessionModel({
98002
- settings: resolvedCatalog.settings,
98332
+ settings: workspaceCatalogSettings,
98003
98333
  selections,
98004
98334
  workspaceSettings
98005
98335
  })
@@ -98982,7 +99312,7 @@ import {
98982
99312
  WorkspaceInstructionPolicyOnboardingProposalStaleResponse,
98983
99313
  WorkspaceInstructionPolicyOperationReuseResponse,
98984
99314
  WorkspaceInstructionPolicyRevision,
98985
- WORKSPACE_INSTRUCTION_POLICY_CONTENT_MAX_CHARS
99315
+ WORKSPACE_INSTRUCTION_POLICY_CONTENT_MAX_CHARS as WORKSPACE_INSTRUCTION_POLICY_CONTENT_MAX_CHARS2
98986
99316
  } from "@opengeni/contracts";
98987
99317
  import { requireAccessGrant as requireAccessGrant37 } from "@opengeni/core";
98988
99318
  import {
@@ -99039,7 +99369,7 @@ function policyErrorResponse(context, error) {
99039
99369
  WorkspaceInstructionPolicyOnboardingProposalContentErrorResponse.parse({
99040
99370
  code: error.code,
99041
99371
  message: error.message,
99042
- maxChars: WORKSPACE_INSTRUCTION_POLICY_CONTENT_MAX_CHARS
99372
+ maxChars: WORKSPACE_INSTRUCTION_POLICY_CONTENT_MAX_CHARS2
99043
99373
  }),
99044
99374
  422
99045
99375
  );
@@ -99788,67 +100118,67 @@ function canonicalJson(value) {
99788
100118
  }
99789
100119
  function companyProfileEntries(profile) {
99790
100120
  const activeRevisionId = profile.current?.revisionId ?? null;
99791
- const byId = new Map(profile.revisions.map((revision) => [revision.id, revision]));
100121
+ const byId = new Map(profile.revisions.map((revision2) => [revision2.id, revision2]));
99792
100122
  if (profile.activeRevision) byId.set(profile.activeRevision.id, profile.activeRevision);
99793
100123
  return {
99794
- entries: [...byId.values()].map((revision) => {
99795
- const active = revision.id === activeRevisionId;
100124
+ entries: [...byId.values()].map((revision2) => {
100125
+ const active = revision2.id === activeRevisionId;
99796
100126
  return {
99797
100127
  id: "company-profile",
99798
- revisionId: revision.id,
100128
+ revisionId: revision2.id,
99799
100129
  path: "company/profile",
99800
100130
  scope: "organization",
99801
100131
  classification: "company_profile",
99802
100132
  title: "Company profile",
99803
100133
  description: "Company identity, mission, products, customers, goals, and constraints.",
99804
100134
  // Company-profile hashes cover this exact canonical field order.
99805
- content: JSON.stringify(revision.profile),
99806
- contentHash: revision.contentHash,
99807
- revision: revision.revision,
100135
+ content: JSON.stringify(revision2.profile),
100136
+ contentHash: revision2.contentHash,
100137
+ revision: revision2.revision,
99808
100138
  active,
99809
- lifecycle: active ? "active" : revision.intent === "proposal" ? "proposal" : "historical",
100139
+ lifecycle: active ? "active" : revision2.intent === "proposal" ? "proposal" : "historical",
99810
100140
  provenance: {
99811
- source: revision.provenance.source,
99812
- sourceId: revision.provenance.sourceId,
99813
- trust: revision.intent === "proposal" ? "untrusted_proposal" : "organization_managed"
100141
+ source: revision2.provenance.source,
100142
+ sourceId: revision2.provenance.sourceId,
100143
+ trust: revision2.intent === "proposal" ? "untrusted_proposal" : "organization_managed"
99814
100144
  },
99815
- relationships: revision.supersedesRevisionId && byId.has(revision.supersedesRevisionId) ? [{ type: "supersedes", targetId: revision.supersedesRevisionId }] : [],
99816
- createdAt: revision.createdAt
100145
+ relationships: revision2.supersedesRevisionId && byId.has(revision2.supersedesRevisionId) ? [{ type: "supersedes", targetId: revision2.supersedesRevisionId }] : [],
100146
+ createdAt: revision2.createdAt
99817
100147
  };
99818
100148
  }),
99819
100149
  truncated: profile.nextAfterRevision !== null
99820
100150
  };
99821
100151
  }
99822
100152
  function instructionPolicyEntries(policies, activeRevisions, activatedRevisionIds) {
99823
- const byId = new Map(policies.revisions.map((revision) => [revision.id, revision]));
99824
- for (const revision of activeRevisions) byId.set(revision.id, revision);
100153
+ const byId = new Map(policies.revisions.map((revision2) => [revision2.id, revision2]));
100154
+ for (const revision2 of activeRevisions) byId.set(revision2.id, revision2);
99825
100155
  const activeIds = new Set(policies.activeHeads.map((head) => head.revisionId));
99826
100156
  const activatedIds = new Set(activatedRevisionIds);
99827
100157
  return {
99828
- entries: [...byId.values()].map((revision) => {
99829
- const roleSegment = revision.roleKey ?? "all";
99830
- const active = activeIds.has(revision.id);
99831
- const lifecycle3 = active ? "active" : activatedIds.has(revision.id) ? "historical" : revision.provenance.source === "onboarding" || revision.provenance.source === "knowledge_proposal" ? "proposal" : "inactive";
100158
+ entries: [...byId.values()].map((revision2) => {
100159
+ const roleSegment = revision2.roleKey ?? "all";
100160
+ const active = activeIds.has(revision2.id);
100161
+ const lifecycle3 = active ? "active" : activatedIds.has(revision2.id) ? "historical" : revision2.provenance.source === "onboarding" || revision2.provenance.source === "knowledge_proposal" ? "proposal" : "inactive";
99832
100162
  return {
99833
- id: `instruction-policy:${revision.kind}:${revision.scope}:${roleSegment}`,
99834
- revisionId: revision.id,
99835
- path: `ways-of-working/rules/${revision.kind}/${revision.scope}/${roleSegment}`,
100163
+ id: `instruction-policy:${revision2.kind}:${revision2.scope}:${roleSegment}`,
100164
+ revisionId: revision2.id,
100165
+ path: `ways-of-working/rules/${revision2.kind}/${revision2.scope}/${roleSegment}`,
99836
100166
  scope: "workspace",
99837
100167
  classification: "mandatory_rule",
99838
- title: revision.kind === "charter" ? "Workspace charter" : revision.scope === "role" ? `Role policy: ${roleSegment}` : "Workspace policy",
100168
+ title: revision2.kind === "charter" ? "Workspace charter" : revision2.scope === "role" ? `Role policy: ${roleSegment}` : "Workspace policy",
99839
100169
  description: active ? "Mandatory context included for applicable agents." : null,
99840
- content: revision.content,
99841
- contentHash: revision.contentHash,
99842
- revision: revision.revision,
100170
+ content: revision2.content,
100171
+ contentHash: revision2.contentHash,
100172
+ revision: revision2.revision,
99843
100173
  active,
99844
100174
  lifecycle: lifecycle3,
99845
100175
  provenance: {
99846
- source: revision.provenance.source,
99847
- sourceId: revision.provenance.sourceId,
99848
- trust: revision.provenance.source === "human" ? "workspace_managed" : revision.provenance.source === "legacy_import" ? "migrated" : "untrusted_proposal"
100176
+ source: revision2.provenance.source,
100177
+ sourceId: revision2.provenance.sourceId,
100178
+ trust: revision2.provenance.source === "human" ? "workspace_managed" : revision2.provenance.source === "legacy_import" ? "migrated" : "untrusted_proposal"
99849
100179
  },
99850
- relationships: revision.supersedesRevisionId && byId.has(revision.supersedesRevisionId) ? [{ type: "supersedes", targetId: revision.supersedesRevisionId }] : [],
99851
- createdAt: revision.createdAt
100180
+ relationships: revision2.supersedesRevisionId && byId.has(revision2.supersedesRevisionId) ? [{ type: "supersedes", targetId: revision2.supersedesRevisionId }] : [],
100181
+ createdAt: revision2.createdAt
99852
100182
  };
99853
100183
  }),
99854
100184
  truncated: policies.nextAfterRevision !== null
@@ -100308,11 +100638,11 @@ async function readCompanyBrainPackage(deps, input) {
100308
100638
  ) : Promise.resolve(null)
100309
100639
  ]);
100310
100640
  if (!workspace) throw new HTTPException89(404, { message: "workspace not found" });
100311
- const listedRevisionIds = new Set(instructionPolicies.revisions.map((revision) => revision.id));
100641
+ const listedRevisionIds = new Set(instructionPolicies.revisions.map((revision2) => revision2.id));
100312
100642
  const missingActiveIds = instructionPolicies.activeHeads.map((head) => head.revisionId).filter((revisionId2) => !listedRevisionIds.has(revisionId2));
100313
100643
  const activeInstructionPolicyRevisions = [
100314
100644
  ...instructionPolicies.revisions.filter(
100315
- (revision) => instructionPolicies.activeHeads.some((head) => head.revisionId === revision.id)
100645
+ (revision2) => instructionPolicies.activeHeads.some((head) => head.revisionId === revision2.id)
100316
100646
  ),
100317
100647
  ...await Promise.all(
100318
100648
  missingActiveIds.map(
@@ -100326,7 +100656,7 @@ async function readCompanyBrainPackage(deps, input) {
100326
100656
  workspaceId: input.workspaceId,
100327
100657
  subjectId: input.subjectId,
100328
100658
  revisionIds: [...instructionPolicies.revisions, ...activeInstructionPolicyRevisions].map(
100329
- (revision) => revision.id
100659
+ (revision2) => revision2.id
100330
100660
  )
100331
100661
  }
100332
100662
  );
@@ -101443,6 +101773,7 @@ import {
101443
101773
  measureInsightsPhase,
101444
101774
  workspaceInsightsPhaseMetricObserver,
101445
101775
  normalizeWorkspaceInsightsFilter,
101776
+ normalizeWorkspaceInsightsSessionScope,
101446
101777
  requireAccessGrant as requireAccessGrant43,
101447
101778
  WorkspaceInsightsFilterValidationError
101448
101779
  } from "@opengeni/core";
@@ -101451,7 +101782,7 @@ import { workspaceInsightsMetricObserver } from "@opengeni/observability";
101451
101782
  import { HTTPException as HTTPException95 } from "hono/http-exception";
101452
101783
  function normalizeWorkspaceInsightsQueryFilter(value, field) {
101453
101784
  try {
101454
- return normalizeWorkspaceInsightsFilter(value, field);
101785
+ return field === "rootSessionId" || field === "sessionId" ? normalizeWorkspaceInsightsSessionScope(value, field) : normalizeWorkspaceInsightsFilter(value, field);
101455
101786
  } catch (error) {
101456
101787
  if (error instanceof WorkspaceInsightsFilterValidationError) {
101457
101788
  throw new HTTPException95(400, { message: error.message });
@@ -101482,6 +101813,8 @@ function workspaceInsightsCoalesceKey(input) {
101482
101813
  input.range,
101483
101814
  input.provider,
101484
101815
  input.model,
101816
+ input.rootSessionId ?? null,
101817
+ input.sessionId ?? null,
101485
101818
  input.rlsActor
101486
101819
  ]);
101487
101820
  }
@@ -101494,6 +101827,8 @@ function registerInsightsRoutes(app, deps) {
101494
101827
  const rangeRaw = c.req.query("range") ?? "week";
101495
101828
  const providerRaw = c.req.query("provider");
101496
101829
  const modelRaw = c.req.query("model");
101830
+ const rootSessionIdRaw = c.req.query("rootSessionId");
101831
+ const sessionIdRaw = c.req.query("sessionId");
101497
101832
  let provider = null;
101498
101833
  let model = null;
101499
101834
  let outcome = "failed";
@@ -101512,12 +101847,19 @@ function registerInsightsRoutes(app, deps) {
101512
101847
  }
101513
101848
  provider = normalizeWorkspaceInsightsQueryFilter(providerRaw, "provider");
101514
101849
  model = normalizeWorkspaceInsightsQueryFilter(modelRaw, "model");
101850
+ const rootSessionId = normalizeWorkspaceInsightsQueryFilter(
101851
+ rootSessionIdRaw,
101852
+ "rootSessionId"
101853
+ );
101854
+ const sessionId = normalizeWorkspaceInsightsQueryFilter(sessionIdRaw, "sessionId");
101515
101855
  const response = await coalesce.run(
101516
101856
  workspaceInsightsCoalesceKey({
101517
101857
  workspaceId,
101518
101858
  range: rangeParsed.data,
101519
101859
  provider,
101520
101860
  model,
101861
+ rootSessionId,
101862
+ sessionId,
101521
101863
  rlsActor: currentSessionRlsActorIdentityKey()
101522
101864
  }),
101523
101865
  () => getWorkspaceInsights(
@@ -101527,7 +101869,9 @@ function registerInsightsRoutes(app, deps) {
101527
101869
  workspaceId,
101528
101870
  range: rangeParsed.data,
101529
101871
  provider,
101530
- model
101872
+ model,
101873
+ rootSessionId,
101874
+ sessionId
101531
101875
  },
101532
101876
  observePhase
101533
101877
  )
@@ -106809,7 +107153,7 @@ import {
106809
107153
  namedSubjectPersonalWorkspaceId as namedSubjectPersonalWorkspaceId2,
106810
107154
  resolveSlackTargetAuthority as resolveSlackTargetAuthority2,
106811
107155
  listSlackInteractionProgressDeliveryEvidence,
106812
- listSessionEventPage as listSessionEventPage3,
107156
+ listSessionEventPage as listSessionEventPage4,
106813
107157
  listSessionHumanInputRequests as listSessionHumanInputRequests2,
106814
107158
  listPendingSlackInteractionMessageActionHandles,
106815
107159
  listPendingSlackUserLinkAccessRequests,
@@ -107734,17 +108078,17 @@ function slackReactionInboxEntry(payload, bot, settings) {
107734
108078
  };
107735
108079
  }
107736
108080
  async function resolveSlackLinkTargetInstallation(deps, link, workspaceId) {
107737
- const route = await resolveSlackInstallationRoute2(deps.db, link.slackTeamId);
107738
- if (!route || route.connectionId !== link.connectionId || route.accountId.length === 0) {
108081
+ const route2 = await resolveSlackInstallationRoute2(deps.db, link.slackTeamId);
108082
+ if (!route2 || route2.connectionId !== link.connectionId || route2.accountId.length === 0) {
107739
108083
  return null;
107740
108084
  }
107741
108085
  const workspace = await getWorkspace10(deps.db, workspaceId);
107742
- if (!workspace || workspace.accountId !== route.accountId) return null;
108086
+ if (!workspace || workspace.accountId !== route2.accountId) return null;
107743
108087
  return {
107744
- accountId: route.accountId,
107745
- connectionId: route.connectionId,
108088
+ accountId: route2.accountId,
108089
+ connectionId: route2.connectionId,
107746
108090
  workspaceName: workspace.name,
107747
- identityHome: { accountId: route.accountId, workspaceId: route.workspaceId }
108091
+ identityHome: { accountId: route2.accountId, workspaceId: route2.workspaceId }
107748
108092
  };
107749
108093
  }
107750
108094
  function registerSlackInteractionRoutes(app, deps) {
@@ -108169,22 +108513,22 @@ function registerSlackInteractionRoutes(app, deps) {
108169
108513
  { connectionId }
108170
108514
  );
108171
108515
  const names = /* @__PURE__ */ new Map();
108172
- for (const route of routes) {
108173
- if (names.has(route.targetWorkspaceId)) continue;
108516
+ for (const route2 of routes) {
108517
+ if (names.has(route2.targetWorkspaceId)) continue;
108174
108518
  names.set(
108175
- route.targetWorkspaceId,
108176
- (await getWorkspace10(deps.db, route.targetWorkspaceId))?.name ?? null
108519
+ route2.targetWorkspaceId,
108520
+ (await getWorkspace10(deps.db, route2.targetWorkspaceId))?.name ?? null
108177
108521
  );
108178
108522
  }
108179
108523
  return c.json(
108180
108524
  SlackChannelRouteListResponse.parse({
108181
108525
  routingEnabled: deps.settings.slackWorkspaceRoutingEnabled,
108182
- routes: routes.map((route) => ({
108183
- slackChannelId: route.slackChannelId,
108184
- targetWorkspaceId: route.targetWorkspaceId,
108185
- targetWorkspaceName: names.get(route.targetWorkspaceId) ?? null,
108186
- source: route.source,
108187
- updatedAt: route.updatedAt.toISOString()
108526
+ routes: routes.map((route2) => ({
108527
+ slackChannelId: route2.slackChannelId,
108528
+ targetWorkspaceId: route2.targetWorkspaceId,
108529
+ targetWorkspaceName: names.get(route2.targetWorkspaceId) ?? null,
108530
+ source: route2.source,
108531
+ updatedAt: route2.updatedAt.toISOString()
108188
108532
  }))
108189
108533
  })
108190
108534
  );
@@ -108205,38 +108549,38 @@ function registerSlackInteractionRoutes(app, deps) {
108205
108549
  accountId: grant.accountId,
108206
108550
  subjectId: grant.subjectId
108207
108551
  });
108208
- for (const route of payload.routes) {
108209
- if (route.targetWorkspaceId === null) continue;
108552
+ for (const route2 of payload.routes) {
108553
+ if (route2.targetWorkspaceId === null) continue;
108210
108554
  const authorized = await resolveSlackTargetAuthority2(deps.db, {
108211
108555
  subjectId: grant.subjectId,
108212
108556
  targetAccountId: grant.accountId,
108213
- targetWorkspaceId: route.targetWorkspaceId
108557
+ targetWorkspaceId: route2.targetWorkspaceId
108214
108558
  });
108215
108559
  if (!authorized || !hasPermission29(authorized.permissions, "sessions:create")) {
108216
108560
  throw new HTTPException106(403, {
108217
- message: `you cannot start work in the workspace chosen for ${route.slackChannelId}`
108561
+ message: `you cannot start work in the workspace chosen for ${route2.slackChannelId}`
108218
108562
  });
108219
108563
  }
108220
- if (callerPersonalWorkspaceId !== null && route.targetWorkspaceId === callerPersonalWorkspaceId) {
108564
+ if (callerPersonalWorkspaceId !== null && route2.targetWorkspaceId === callerPersonalWorkspaceId) {
108221
108565
  throw new HTTPException106(422, {
108222
- message: `a channel cannot be routed to a personal workspace: work started in ${route.slackChannelId} would be invisible to everyone else in it`
108566
+ message: `a channel cannot be routed to a personal workspace: work started in ${route2.slackChannelId} would be invisible to everyone else in it`
108223
108567
  });
108224
108568
  }
108225
108569
  }
108226
- for (const route of payload.routes) {
108227
- if (route.targetWorkspaceId === null) {
108570
+ for (const route2 of payload.routes) {
108571
+ if (route2.targetWorkspaceId === null) {
108228
108572
  await deleteSlackChannelRoute(deps.db, home, {
108229
108573
  connectionId: payload.connectionId,
108230
- slackChannelId: route.slackChannelId
108574
+ slackChannelId: route2.slackChannelId
108231
108575
  });
108232
108576
  continue;
108233
108577
  }
108234
108578
  await upsertSlackChannelRoute(deps.db, home, {
108235
108579
  connectionId: payload.connectionId,
108236
108580
  slackTeamId: binding.slackTeamId,
108237
- slackChannelId: route.slackChannelId,
108581
+ slackChannelId: route2.slackChannelId,
108238
108582
  targetAccountId: grant.accountId,
108239
- targetWorkspaceId: route.targetWorkspaceId,
108583
+ targetWorkspaceId: route2.targetWorkspaceId,
108240
108584
  decidedBySubjectId: grant.subjectId,
108241
108585
  decidedBySlackUserId: "admin",
108242
108586
  source: "admin"
@@ -110200,8 +110544,8 @@ async function processSlackBlockAction(deps, entry2) {
110200
110544
  const actionId = separator > 0 ? entry2.text.slice(0, separator) : "";
110201
110545
  const handleId = separator > 0 ? entry2.text.slice(separator + 1) : "";
110202
110546
  const home = { accountId: entry2.accountId, workspaceId: entry2.workspaceId };
110203
- const route = await resolveSlackInstallationRoute2(deps.db, entry2.slackTeamId);
110204
- if (!route || route.accountId !== entry2.accountId || route.workspaceId !== entry2.workspaceId || route.connectionId !== entry2.connectionId) {
110547
+ const route2 = await resolveSlackInstallationRoute2(deps.db, entry2.slackTeamId);
110548
+ if (!route2 || route2.accountId !== entry2.accountId || route2.workspaceId !== entry2.workspaceId || route2.connectionId !== entry2.connectionId) {
110205
110549
  throw new SlackInteractionPermanentError("slack_action_installation_changed");
110206
110550
  }
110207
110551
  if (actionId === "opengeni.route.select" || actionId === "opengeni.route.cancel") {
@@ -111051,7 +111395,7 @@ async function resolveSlackDeliveryHome(deps, interaction) {
111051
111395
  }
111052
111396
  async function deliverSlackSessionEvents(deps, interaction, claimHolderId) {
111053
111397
  if (!interaction.sessionId) return;
111054
- const page2 = await listSessionEventPage3(deps.db, interaction.workspaceId, interaction.sessionId, {
111398
+ const page2 = await listSessionEventPage4(deps.db, interaction.workspaceId, interaction.sessionId, {
111055
111399
  after: interaction.lastDeliveredSessionEventSequence,
111056
111400
  limit: 100,
111057
111401
  includeTypes: [...SLACK_DELIVERY_EVENT_TYPES],
@@ -111412,11 +111756,11 @@ async function deliverSlackModelText(deliver) {
111412
111756
  await deliver((text) => text);
111413
111757
  }
111414
111758
  }
111415
- async function enqueueNormalizedSlackInteraction(deps, route, entry2) {
111759
+ async function enqueueNormalizedSlackInteraction(deps, route2, entry2) {
111416
111760
  await enqueueSlackInteractionInbox(deps.db, {
111417
- accountId: route.accountId,
111418
- workspaceId: route.workspaceId,
111419
- connectionId: route.connectionId,
111761
+ accountId: route2.accountId,
111762
+ workspaceId: route2.workspaceId,
111763
+ connectionId: route2.connectionId,
111420
111764
  ...entry2
111421
111765
  });
111422
111766
  }
@@ -112153,14 +112497,14 @@ function createAppComposition(deps) {
112153
112497
  app.use("/v1/*", bearerApiContractHeaderCompatibility());
112154
112498
  app.use("*", async (c, next) => {
112155
112499
  const url = new URL(c.req.url);
112156
- const route = routeLabel(url.pathname, registeredHandlerRoutePath(c));
112500
+ const route2 = routeLabel(url.pathname, registeredHandlerRoutePath(c));
112157
112501
  const correlationId2 = correlationIds.get(c.req.raw) ?? crypto.randomUUID();
112158
112502
  const start = performance.now();
112159
112503
  const span = observability.startSpan(
112160
- `HTTP ${c.req.method} ${route}`,
112504
+ `HTTP ${c.req.method} ${route2}`,
112161
112505
  {
112162
112506
  "http.request.method": c.req.method,
112163
- "opengeni.route": route
112507
+ "opengeni.route": route2
112164
112508
  },
112165
112509
  { parent: null }
112166
112510
  );
@@ -112174,7 +112518,7 @@ function createAppComposition(deps) {
112174
112518
  const durationSeconds = (performance.now() - start) / 1e3;
112175
112519
  observability.recordHttpRequest({
112176
112520
  method: c.req.method,
112177
- route,
112521
+ route: route2,
112178
112522
  status,
112179
112523
  durationSeconds
112180
112524
  });
@@ -112186,7 +112530,7 @@ function createAppComposition(deps) {
112186
112530
  });
112187
112531
  observability.info("HTTP request completed", {
112188
112532
  method: c.req.method,
112189
- route,
112533
+ route: route2,
112190
112534
  status,
112191
112535
  durationMs: Math.round(durationSeconds * 1e3),
112192
112536
  traceId: span.traceId,
@@ -112199,14 +112543,14 @@ function createAppComposition(deps) {
112199
112543
  const durationSeconds = (performance.now() - start) / 1e3;
112200
112544
  observability.recordHttpRequest({
112201
112545
  method: c.req.method,
112202
- route,
112546
+ route: route2,
112203
112547
  status,
112204
112548
  durationSeconds
112205
112549
  });
112206
112550
  observability.incrementCounter({
112207
112551
  name: "opengeni_http_errors_total",
112208
112552
  help: "Total OpenGeni HTTP request failures by bounded route, status, and stable code.",
112209
- labels: { route, status: String(status), code: errorCode }
112553
+ labels: { route: route2, status: String(status), code: errorCode }
112210
112554
  });
112211
112555
  span.end({
112212
112556
  attributes: {
@@ -112217,7 +112561,7 @@ function createAppComposition(deps) {
112217
112561
  });
112218
112562
  observability.error("HTTP request failed", {
112219
112563
  method: c.req.method,
112220
- route,
112564
+ route: route2,
112221
112565
  status,
112222
112566
  durationMs: Math.round(durationSeconds * 1e3),
112223
112567
  traceId: span.traceId,
@@ -112639,6 +112983,8 @@ function createAppComposition(deps) {
112639
112983
  auth: clientAuthConfig(deps.settings),
112640
112984
  documentationUrl: deps.settings.documentationUrl,
112641
112985
  analytics: clientAnalyticsConfig(deps.settings),
112986
+ legal: clientLegalConfig(deps.settings),
112987
+ ...deps.settings.supportEmail ? { supportEmail: deps.settings.supportEmail } : {},
112642
112988
  // Channel-A structured services (P4.4) ride exec/readFile/createEditor,
112643
112989
  // available on every real backend; `none` has no box so they are all off.
112644
112990
  // Per-session availability is still negotiated on /stream-capabilities.
@@ -113029,7 +113375,18 @@ function createAppComposition(deps) {
113029
113375
  }
113030
113376
  const importErrorResponse = archivedSessionImportErrorResponse(c, rawError);
113031
113377
  if (importErrorResponse) return importErrorResponse;
113032
- const error = rawError instanceof OrganizationIntegrationDeniedError4 ? new HTTPException107(403, { message: rawError.message }) : allowanceExhaustedHttpError(rawError) ?? workspaceControlBusyHttpError(rawError) ?? agentConfigHttpError(rawError) ?? requestBodyValidationHttpError(rawError) ?? invalidPathIdentifierHttpError(rawError, new URL(c.req.url).pathname) ?? rawError;
113378
+ const error = rawError instanceof OrganizationIntegrationDeniedError4 ? new HTTPException107(403, { message: rawError.message }) : allowanceExhaustedHttpError(rawError) ?? workspaceControlBusyHttpError(rawError) ?? agentConfigHttpError(rawError) ?? modelUnavailableHttpError(rawError) ?? (rawError instanceof UnsupportedLatencyModeError2 ? new ApiHttpError(422, {
113379
+ code: "validation_failed",
113380
+ message: rawError.message,
113381
+ retryable: false,
113382
+ outcomeUnknown: false,
113383
+ details: {
113384
+ code: rawError.code,
113385
+ modelId: rawError.modelId,
113386
+ latencyMode: rawError.latencyMode,
113387
+ allowedLatencyModes: [...rawError.allowedLatencyModes]
113388
+ }
113389
+ }) : null) ?? requestBodyValidationHttpError(rawError) ?? invalidPathIdentifierHttpError(rawError, new URL(c.req.url).pathname) ?? rawError;
113033
113390
  const compactionLock = codexCompactionV2ProviderLockedError(error);
113034
113391
  const apiError = error instanceof ApiHttpError ? error : null;
113035
113392
  const status = compactionLock ? 422 : httpStatusForError(error);
@@ -113080,7 +113437,7 @@ function workspaceActorContextExempt(method, pathname) {
113080
113437
  ))
113081
113438
  return true;
113082
113439
  if (/^\/v1\/workspaces\/[^/]+\/mcp(?:\/(?:docs|files))?$/.test(pathname)) return true;
113083
- if (method === "GET" && publicWorkspaceBrowserRoutePatterns.some((route) => route.test(pathname))) {
113440
+ if (method === "GET" && publicWorkspaceBrowserRoutePatterns.some((route2) => route2.test(pathname))) {
113084
113441
  return true;
113085
113442
  }
113086
113443
  return method === "POST" && /^\/v1\/workspaces\/[^/]+\/github\/installations$/.test(pathname);
@@ -113331,6 +113688,12 @@ function clientAnalyticsConfig(settings) {
113331
113688
  }
113332
113689
  };
113333
113690
  }
113691
+ function clientLegalConfig(settings) {
113692
+ return {
113693
+ ...settings.legalPrivacyPolicyUrl ? { privacyPolicyUrl: settings.legalPrivacyPolicyUrl } : {},
113694
+ ...settings.legalTermsOfServiceUrl ? { termsOfServiceUrl: settings.legalTermsOfServiceUrl } : {}
113695
+ };
113696
+ }
113334
113697
  function structuredServicesHint(backend) {
113335
113698
  const hasBox = backend !== "none";
113336
113699
  return { fileSystem: hasBox, git: hasBox, terminalEvents: hasBox };
@@ -114462,4 +114825,4 @@ export {
114462
114825
  withDefaultEnabledCapabilityMcpTools,
114463
114826
  workflowIdForSession4 as workflowIdForSession
114464
114827
  };
114465
- //# sourceMappingURL=chunk-2QCRFD6U.js.map
114828
+ //# sourceMappingURL=chunk-ZQ7PIFGF.js.map