@opengeni/api-router 1.4.5-canary.38533733713001 → 2.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -62,7 +62,7 @@ export function requireLegacyOAuthActor(access: AccessGrantAuthorization): void
62
62
  * The allow-list that actually decides is `principalKind === "human_session"`.
63
63
  * This list is defence-in-depth against exactly one residual threat: a holder
64
64
  * of the first-party delegation secret signing a human claim over an
65
- * OpenGeni-minted machine subject.
65
+ * Opengeni-minted machine subject.
66
66
  *
67
67
  * `connection-ownership.test.ts` asserts that every machine subject named in
68
68
  * its list is rejected. Note what that does and does not buy: it catches a
@@ -108,7 +108,7 @@ export function isPersonalConnectionOwnerPrincipal(access: AccessGrantAuthorizat
108
108
  );
109
109
  }
110
110
 
111
- /** Rejects an OpenGeni-minted machine subject; see the namespace list's rationale. */
111
+ /** Rejects an Opengeni-minted machine subject; see the namespace list's rationale. */
112
112
  export function isPersonalConnectionOwnerSubject(subjectId: string): boolean {
113
113
  const separator = subjectId.indexOf(":");
114
114
  if (separator < 0) {
@@ -672,28 +672,27 @@ async function startMcpOAuthWithinDeadline(
672
672
  ...(existing ? { connectionId: existing.id, connectionVersion: existing.version } : {}),
673
673
  });
674
674
  const expiresAt = new Date(Date.now() + oauthStateTtlMs);
675
- let state = fullState;
676
- if (settings.integrationsOauthShortStateEnabled) {
677
- const stateId = randomUUID();
678
- state = createSignedState(requireIntegrationsStateSecret(settings), {
679
- kind: "mcp_oauth_reference",
675
+ // Providers cap `state` length (Stripe at 1024 characters), so only a short
676
+ // signed reference leaves; the full context waits encrypted at rest.
677
+ const stateId = randomUUID();
678
+ const state = createSignedState(requireIntegrationsStateSecret(settings), {
679
+ kind: "mcp_oauth_reference",
680
+ id: stateId,
681
+ accountId: context.accountId,
682
+ workspaceId: context.workspaceId,
683
+ });
684
+ if (state.length > 512) {
685
+ throw new Error("MCP OAuth reference state exceeded its provider-safe length");
686
+ }
687
+ await deadline.run("state_persist", async () =>
688
+ storeIntegrationOAuthPendingState(db, {
680
689
  id: stateId,
681
690
  accountId: context.accountId,
682
691
  workspaceId: context.workspaceId,
683
- });
684
- if (state.length > 512) {
685
- throw new Error("MCP OAuth reference state exceeded its provider-safe length");
686
- }
687
- await deadline.run("state_persist", async () =>
688
- storeIntegrationOAuthPendingState(db, {
689
- id: stateId,
690
- accountId: context.accountId,
691
- workspaceId: context.workspaceId,
692
- stateEncrypted: encryptEnvironmentValue(key, fullState),
693
- expiresAt,
694
- }),
695
- );
696
- }
692
+ stateEncrypted: encryptEnvironmentValue(key, fullState),
693
+ expiresAt,
694
+ }),
695
+ );
697
696
  const authorizationUrl = buildAuthorizationUrl({
698
697
  endpoint: discovery.as.authorizationEndpoint,
699
698
  settings,
@@ -51,7 +51,7 @@ const RULES: Rule[] = [
51
51
  method: "PATCH",
52
52
  pattern: /\/(codex|claude|supergrok)\/accounts\/:accountId\/allocator$/,
53
53
  text: ({ provider }) =>
54
- `Turn "Use for new work" on or off for a ${provider} account (pause or resume it). Paused accounts aren't picked for new chats or schedules; running work continues. Send the current allocator version.`,
54
+ `Turn "Enable account" on or off for a ${provider} account (enable or disable it, sometimes called pause or resume). Disabled accounts aren't picked for new chats or schedules; running work continues. Send the current allocator version.`,
55
55
  },
56
56
  {
57
57
  method: "PATCH",
package/src/mcp/server.ts CHANGED
@@ -4262,7 +4262,7 @@ function registerFleetTools(
4262
4262
  "sandbox_provision",
4263
4263
  {
4264
4264
  description:
4265
- "Provision a new sandbox for the fleet. kind=selfhosted posts the Connected Machine card in this chat: the person copies a one-line connect command, runs it on their own computer, and picks Use in this chat; the card also offers the OpenGeni Browser Chrome extension once the machine is connected. When you can already run commands on the target machine, install it yourself with connected_machine_enroll_token instead. kind=modal creates a named Modal sandbox record, but it is NOT yet attachable as a swap target: routing a session onto a second Modal box is not supported yet, so sandbox_swap to its id is rejected. Use the session's own box (the default) or attach a Connected Machine instead.",
4265
+ "Provision a new sandbox for the fleet. kind=selfhosted posts the Connected Machine card in this chat: the person copies a one-line connect command, runs it on their own computer, and picks Use in this chat; the card also offers the Opengeni Browser Chrome extension once the machine is connected. When you can already run commands on the target machine, install it yourself with connected_machine_enroll_token instead. kind=modal creates a named Modal sandbox record, but it is NOT yet attachable as a swap target: routing a session onto a second Modal box is not supported yet, so sandbox_swap to its id is rejected. Use the session's own box (the default) or attach a Connected Machine instead.",
4266
4266
  inputSchema: {
4267
4267
  kind: z4.enum(["selfhosted", "modal"]),
4268
4268
  name: z4.string().min(1).max(120).optional(),
@@ -4303,7 +4303,7 @@ function registerFleetTools(
4303
4303
  setup.status === "ready"
4304
4304
  ? `${setup.detail} The Connected Machine card lists them with Use in this chat and can connect another.`
4305
4305
  : "The Connected Machine card was posted. The person copies its one-line connect command, runs it on their machine, and picks Use in this chat; nothing is connected yet."
4306
- } Check sandboxes_list after they confirm. Chrome needs the OpenGeni Browser extension (${OPENGENI_BROWSER_EXTENSION_URL}) on the connected machine.`,
4306
+ } Check sandboxes_list after they confirm. Chrome needs the Opengeni Browser extension (${OPENGENI_BROWSER_EXTENSION_URL}) on the connected machine.`,
4307
4307
  });
4308
4308
  }
4309
4309
  return json(
@@ -4332,7 +4332,7 @@ function registerConnectedMachineTools(
4332
4332
  "connected_machine_enroll_token",
4333
4333
  {
4334
4334
  description:
4335
- "Create a single-use Connected Machine enrollment token for this workspace (expires in one hour) using existing enrollments:manage authority. Returns the token, expiry and Unix/PowerShell install commands. Use it when you can run commands on the target machine yourself (run_on, SSH, a VM you control): run the matching command there, then verify the machine with sandboxes_list. One token connects one machine; mint another for each additional machine (the same machine may rerun its command). When the person must run it on their own computer, post the Connected Machine card instead (capability_authorization_request with api:connected-machine), which mints the command in their browser; do not paste a token into the chat. Screen control is optional and defaults off; to turn it on for a machine that is already connected, use connected_machine_enable_screen_control instead of reconnecting. The token grants whole-machine access on enrollment; do not put it in public code or unrelated logs. Existing machine connections are preserved. For Chrome, the person installs the OpenGeni Browser extension (" +
4335
+ "Create a single-use Connected Machine enrollment token for this workspace (expires in one hour) using existing enrollments:manage authority. Returns the token, expiry and Unix/PowerShell install commands. Use it when you can run commands on the target machine yourself (run_on, SSH, a VM you control): run the matching command there, then verify the machine with sandboxes_list. One token connects one machine; mint another for each additional machine (the same machine may rerun its command). When the person must run it on their own computer, post the Connected Machine card instead (capability_authorization_request with api:connected-machine), which mints the command in their browser; do not paste a token into the chat. Screen control is optional and defaults off; to turn it on for a machine that is already connected, use connected_machine_enable_screen_control instead of reconnecting. The token grants whole-machine access on enrollment; do not put it in public code or unrelated logs. Existing machine connections are preserved. For Chrome, the person installs the Opengeni Browser extension (" +
4336
4336
  OPENGENI_BROWSER_EXTENSION_URL +
4337
4337
  ") after the machine is connected.",
4338
4338
  inputSchema: { allowScreenControl: z4.boolean().optional() },
@@ -4363,7 +4363,7 @@ function registerConnectedMachineTools(
4363
4363
  "connected_machine_enable_screen_control",
4364
4364
  {
4365
4365
  description:
4366
- "Turn screen control on for a machine that is already connected, in place, so agents can see and use its screen (computer and attached-Chrome tools). `target` is a sandboxes_list id or an enrollment id. Needs no human click, token, terminal or reconnect: OpenGeni records the consent on that machine and its agent refreshes its own credentials over the live connection. status=active means it is on now. status=pending means it is allowed and turns on by itself: reason=offline when the machine next connects; reason=agent_update_required after its agent is updated (update it with the machine update action, POST /v1/workspaces/{workspaceId}/machines/{enrollmentId}/update); reason=renewal_failed: call again shortly; reason=reconnect_required: the machine's connection predates in-place changes, so rerun the connect command on it (connected_machine_enroll_token with allowScreenControl=true). On a Mac the OS must also allow Screen Recording, Accessibility and Input Monitoring for OpenGeni: open each pane on the Mac with run_on exec `open 'x-apple.systempreferences:com.apple.preference.security?Privacy_ScreenCapture'` (Privacy_Accessibility, Privacy_ListenEvent; also the privacy-settings machine action) and ask the person to switch OpenGeni on there, choosing Quit & Reopen if macOS asks; the machine list reports runtime.macPermissions. Chrome additionally needs the OpenGeni Browser extension (" +
4366
+ "Turn screen control on for a machine that is already connected, in place, so agents can see and use its screen (computer and attached-Chrome tools). `target` is a sandboxes_list id or an enrollment id. Needs no human click, token, terminal or reconnect: Opengeni records the consent on that machine and its agent refreshes its own credentials over the live connection. status=active means it is on now. status=pending means it is allowed and turns on by itself: reason=offline when the machine next connects; reason=agent_update_required after its agent is updated (update it with the machine update action, POST /v1/workspaces/{workspaceId}/machines/{enrollmentId}/update); reason=renewal_failed: call again shortly; reason=reconnect_required: the machine's connection predates in-place changes, so rerun the connect command on it (connected_machine_enroll_token with allowScreenControl=true). On a Mac the OS must also allow Screen Recording, Accessibility and Input Monitoring for Opengeni: open each pane on the Mac with run_on exec `open 'x-apple.systempreferences:com.apple.preference.security?Privacy_ScreenCapture'` (Privacy_Accessibility, Privacy_ListenEvent; also the privacy-settings machine action) and ask the person to switch Opengeni on there, choosing Quit & Reopen if macOS asks; the machine list reports runtime.macPermissions. Chrome additionally needs the Opengeni Browser extension (" +
4367
4367
  OPENGENI_BROWSER_EXTENSION_URL +
4368
4368
  ") on that machine.",
4369
4369
  inputSchema: { target: z4.string().min(1) },
@@ -6555,7 +6555,7 @@ function registerCapabilityDiscoveryTools(
6555
6555
  "capability_authorization_request",
6556
6556
  {
6557
6557
  description:
6558
- "Show a Connect card in this chat for a suitable capability returned by capability_catalog_search with setup.nextAction. Supply its capability ID and a brief rationale explaining how it helps the task; no separate confirmation is needed before showing the card. Requesting setup needs no integration-management permission and grants no access. The authenticated human completes setup through the card; never ask them to paste credentials into chat. Do not request another card for the same pending setup, or for a candidate reported ready or unavailable. GitHub App (api:github-app) and Connected Machine (api:connected-machine) are exceptions: when already connected their card is still shown, listing repositories or machines the person can use in this chat. Use api:connected-machine when the person should connect their own computer: the card gives them a one-line connect command and the OpenGeni Browser Chrome extension link; never paste an enroll token into the chat yourself." +
6558
+ "Show a Connect card in this chat for a suitable capability returned by capability_catalog_search with setup.nextAction. Supply its capability ID and a brief rationale explaining how it helps the task; no separate confirmation is needed before showing the card. Requesting setup needs no integration-management permission and grants no access. The authenticated human completes setup through the card; never ask them to paste credentials into chat. Do not request another card for the same pending setup, or for a candidate reported ready or unavailable. GitHub App (api:github-app) and Connected Machine (api:connected-machine) are exceptions: when already connected their card is still shown, listing repositories or machines the person can use in this chat. Use api:connected-machine when the person should connect their own computer: the card gives them a one-line connect command and the Opengeni Browser Chrome extension link; never paste an enroll token into the chat yourself." +
6559
6559
  (secretsSelected
6560
6560
  ? ' For a single password, API key or code for a service with no connector here, ask with a kind: "secret" question in request_human_input instead; you get a saved reference back.'
6561
6561
  : ""),
@@ -6610,7 +6610,7 @@ function registerCapabilityDiscoveryTools(
6610
6610
  capabilityId: item.id,
6611
6611
  status: "ready",
6612
6612
  eventId,
6613
- message: `${setup.detail} The Connected Machine card is in this chat: the person can pick a machine with "Use in this chat" or connect another. Chrome needs the OpenGeni Browser extension (${OPENGENI_BROWSER_EXTENSION_URL}) on a connected machine.`,
6613
+ message: `${setup.detail} The Connected Machine card is in this chat: the person can pick a machine with "Use in this chat" or connect another. Chrome needs the Opengeni Browser extension (${OPENGENI_BROWSER_EXTENSION_URL}) on a connected machine.`,
6614
6614
  });
6615
6615
  }
6616
6616
  if (githubCardWhenReady) {
@@ -1,3 +1,4 @@
1
+ import { attachedBrowserViewer } from "../attached-browser-viewer";
1
2
  import {
2
3
  assertEphemeralBrowserCreateEnabled,
3
4
  ephemeralBrowserPartition,
@@ -271,8 +272,7 @@ export function registerBrowserSessionRoutes(
271
272
  return context.json(
272
273
  AttachedBrowserDeviceListResponse.parse(
273
274
  await listAttachedBrowserDevices(deps.db, {
274
- accountId: grant.accountId,
275
- workspaceId,
275
+ ...(await attachedBrowserViewer(deps, grant, workspaceId)),
276
276
  includeDisconnected,
277
277
  }),
278
278
  ),
@@ -287,8 +287,7 @@ export function registerBrowserSessionRoutes(
287
287
  return context.json(
288
288
  AttachedBrowserDevice.parse(
289
289
  await getAttachedBrowserDevice(deps.db, {
290
- accountId: grant.accountId,
291
- workspaceId,
290
+ ...(await attachedBrowserViewer(deps, grant, workspaceId)),
292
291
  deviceId,
293
292
  }),
294
293
  ),
@@ -2692,20 +2691,17 @@ export function registerBrowserSessionRoutes(
2692
2691
  ): Promise<T> {
2693
2692
  if (expectedPlacement?.kind === "attached_device") {
2694
2693
  waitSignal.throwIfAborted();
2694
+ // Personal machines are controlled only for their owner, as everywhere else.
2695
+ const viewer = await attachedBrowserViewer(deps, grant, sourceSession.workspaceId);
2695
2696
  const device = await getAttachedBrowserDevice(deps.db, {
2696
- accountId: grant.accountId,
2697
- workspaceId: sourceSession.workspaceId,
2697
+ ...viewer,
2698
2698
  deviceId: expectedPlacement.deviceId,
2699
2699
  });
2700
2700
  if (device.state !== "connected") {
2701
2701
  recordAttachedBrowserUnavailable(deps.observability, "disconnected");
2702
2702
  throw new BrowserSessionStateError("Attached browser is disconnected");
2703
2703
  }
2704
- const enrollment = await getLiveEnrollmentConnection(
2705
- deps.db,
2706
- sourceSession.workspaceId,
2707
- device.enrollmentId,
2708
- );
2704
+ const enrollment = await getLiveEnrollmentConnection(deps.db, viewer, device.enrollmentId);
2709
2705
  if (!enrollment || enrollment.status !== "active" || !enrollment.connectionInstanceId) {
2710
2706
  recordAttachedBrowserUnavailable(deps.observability, "machine_unavailable");
2711
2707
  throw new BrowserSessionStateError("Attached browser machine is unavailable");
@@ -125,7 +125,9 @@ export async function coreCodexConnected(
125
125
  default:
126
126
  throw new HTTPException(403, {
127
127
  message:
128
- "only an organization administrator can connect a new shared Codex account; workspace administrators can reconnect the accounts their workspace manages",
128
+ input.workspaceId === null
129
+ ? "only an organization administrator can connect a Codex account for the whole organization"
130
+ : "only an administrator of this workspace or of the organization can connect a Codex account here",
129
131
  });
130
132
  }
131
133
  }
@@ -155,7 +157,8 @@ export async function coreCodexConnected(
155
157
  function disconnectRefused(outcome: SubscriptionCoreCodexDisconnectOutcome): void {
156
158
  if (outcome === "forbidden") {
157
159
  throw new HTTPException(403, {
158
- message: "only an organization administrator can disconnect a shared Codex account",
160
+ message:
161
+ "only an organization administrator can disconnect this Codex account; a workspace administrator can disconnect only an account that serves just their workspace",
159
162
  });
160
163
  }
161
164
  if (outcome === "unresolved_redemption") {
@@ -1,3 +1,4 @@
1
+ import { attachedBrowserViewer } from "../attached-browser-viewer";
1
2
  import { randomUUID } from "node:crypto";
2
3
  import {
3
4
  resolveFirstPartyDelegationSecret,
@@ -1114,19 +1115,16 @@ export function registerComputerSessionRoutes(
1114
1115
  }
1115
1116
  if (expectedPlacement?.kind === "attached_device") {
1116
1117
  waitSignal.throwIfAborted();
1118
+ // Personal machines are controlled only for their owner, as everywhere else.
1119
+ const viewer = await attachedBrowserViewer(deps, grant, sourceSession.workspaceId);
1117
1120
  const device = await getAttachedBrowserDevice(deps.db, {
1118
- accountId: grant.accountId,
1119
- workspaceId: sourceSession.workspaceId,
1121
+ ...viewer,
1120
1122
  deviceId: expectedPlacement.deviceId,
1121
1123
  });
1122
1124
  if (device.state !== "connected") {
1123
1125
  throw new ComputerSessionStateError("Attached browser machine is disconnected");
1124
1126
  }
1125
- const enrollment = await getLiveEnrollmentConnection(
1126
- deps.db,
1127
- sourceSession.workspaceId,
1128
- device.enrollmentId,
1129
- );
1127
+ const enrollment = await getLiveEnrollmentConnection(deps.db, viewer, device.enrollmentId);
1130
1128
  if (!enrollment || enrollment.status !== "active" || !enrollment.connectionInstanceId) {
1131
1129
  throw new ComputerSessionStateError("Attached browser machine is unavailable");
1132
1130
  }
@@ -315,7 +315,7 @@ export function registerMachineRoutes(app: Hono, deps: ApiRouteDeps): void {
315
315
 
316
316
  // ── POST /workspaces/:ws/machines/:enrollmentId/privacy-settings ───────────
317
317
  // Open one macOS Privacy & Security pane on the connected Mac, so the person
318
- // only flips the switch for OpenGeni. Opening a pane grants nothing.
318
+ // only flips the switch for Opengeni. Opening a pane grants nothing.
319
319
  app.post("/v1/workspaces/:workspaceId/machines/:enrollmentId/privacy-settings", async (c) => {
320
320
  const workspaceId = c.req.param("workspaceId");
321
321
  const authorization = await requireAccessGrantAuthorization(
@@ -17,6 +17,7 @@ import type { Hono } from "hono";
17
17
  import { HTTPException } from "hono/http-exception";
18
18
  import { z } from "zod";
19
19
  import { runManagedAuthProvider } from "../auth/managed-auth-attempt-context";
20
+ import { managedAuthSessionCookieHeaders } from "../auth/managed-auth-session-adapter";
20
21
  import { admitManagedSignInMutation } from "./managed-sign-in-methods";
21
22
 
22
23
  // Native app sign-in (RFC 8252 shape: authorization code + PKCE over a custom
@@ -32,13 +33,30 @@ const CODE_IDENTIFIER_PREFIX = "opengeni-native-app-code:";
32
33
  const PKCE_VERIFIER = /^[A-Za-z0-9._~-]{43,128}$/u;
33
34
  const PKCE_CHALLENGE = /^[A-Za-z0-9_-]{43}$/u;
34
35
  const MANAGED_PROVIDERS = ["credential", "google", "github"] as const;
36
+ export const NATIVE_APP_PLATFORMS = ["ios", "android", "macos", "windows", "linux"] as const;
37
+ type NativeAppPlatform = (typeof NATIVE_APP_PLATFORMS)[number];
38
+ const PLATFORM_LABELS: Record<NativeAppPlatform, string> = {
39
+ ios: "iOS",
40
+ android: "Android",
41
+ macos: "macOS",
42
+ windows: "Windows",
43
+ linux: "Linux",
44
+ };
45
+
46
+ /** The device label shown in the person's signed-in sessions. */
47
+ export function nativeAppSessionUserAgent(
48
+ platform: NativeAppPlatform,
49
+ deviceName: string | null,
50
+ ): string {
51
+ return `Opengeni app (${PLATFORM_LABELS[platform]}${deviceName ? `; ${deviceName}` : ""})`;
52
+ }
35
53
 
36
54
  const AuthorizeBody = z.object({
37
55
  redirectUri: z.string().min(1).max(512),
38
56
  codeChallenge: z.string().regex(PKCE_CHALLENGE),
39
57
  codeChallengeMethod: z.literal("S256"),
40
58
  state: z.string().min(8).max(256).optional(),
41
- platform: z.enum(["ios", "android"]),
59
+ platform: z.enum(NATIVE_APP_PLATFORMS),
42
60
  deviceName: z.string().trim().min(1).max(80).optional(),
43
61
  });
44
62
 
@@ -61,7 +79,7 @@ const StoredGrant = z.object({
61
79
  providerId: z.enum(MANAGED_PROVIDERS),
62
80
  redirectUri: z.string(),
63
81
  codeChallenge: z.string().regex(PKCE_CHALLENGE),
64
- platform: z.enum(["ios", "android"]),
82
+ platform: z.enum(NATIVE_APP_PLATFORMS),
65
83
  deviceName: z.string().nullable(),
66
84
  });
67
85
 
@@ -177,10 +195,9 @@ export function registerNativeAppAuthRoutes(app: Hono, deps: ApiRouteDeps): void
177
195
  ) {
178
196
  return context.json({ error: "invalid_grant" }, 400);
179
197
  }
180
- const platform = grant.platform === "ios" ? "iOS" : "Android";
181
198
  const session = await runManagedAuthProvider(grant.providerId, () =>
182
199
  authContext.internalAdapter.createSession(grant.userId, false, {
183
- userAgent: `Opengeni app (${platform}${grant.deviceName ? `; ${grant.deviceName}` : ""})`,
200
+ userAgent: nativeAppSessionUserAgent(grant.platform, grant.deviceName),
184
201
  }),
185
202
  );
186
203
  if (!session?.token || new Date(session.expiresAt).getTime() <= Date.now()) {
@@ -209,6 +226,36 @@ export function registerNativeAppAuthRoutes(app: Hono, deps: ApiRouteDeps): void
209
226
  return context.json({ signedOut: true });
210
227
  });
211
228
 
229
+ // A desktop app shows the deployment's own web app. It signs that window in
230
+ // with the same device session, so the window is an ordinary first-party web
231
+ // session and signing the device out ends both. The bearer is required: an
232
+ // ambient browser request can never mint this cookie.
233
+ app.post(`${base}/web-session`, async (context) => {
234
+ const auth = deps.managedAuth;
235
+ if (!auth) throw new HTTPException(404);
236
+ if (deps.settings.managedAuthSessionSetMode === "broker") {
237
+ throw new HTTPException(409, {
238
+ message: "This deployment signs browser windows in through login session sets",
239
+ });
240
+ }
241
+ const credential = bearer(context.req.header("authorization"));
242
+ if (!credential?.startsWith(NATIVE_APP_CREDENTIAL_PREFIX)) {
243
+ throw new HTTPException(401, { message: "Native app credential required" });
244
+ }
245
+ const session = await getNativeAppManagedSession(auth, credential, deps.db).catch(() => null);
246
+ if (typeof session?.session?.id !== "string") {
247
+ throw new HTTPException(401, { message: "Sign in again" });
248
+ }
249
+ for (const cookie of await managedAuthSessionCookieHeaders(
250
+ auth,
251
+ credential.slice(NATIVE_APP_CREDENTIAL_PREFIX.length),
252
+ context.req.header("cookie") ?? null,
253
+ )) {
254
+ context.header("set-cookie", cookie, { append: true });
255
+ }
256
+ return context.json({ signedIn: true });
257
+ });
258
+
212
259
  // Push registration belongs to the app session that registered it: signing
213
260
  // the app out, or revoking its session anywhere, removes it.
214
261
  const appSessionId = async (authorization: string | undefined): Promise<string> => {
@@ -22,7 +22,7 @@ import {
22
22
  RealtimeVoiceUnavailableError,
23
23
  TranscriptionBillingRefusedError,
24
24
  } from "@opengeni/core";
25
- import type { CreditDebitAttribution } from "@opengeni/db";
25
+ import type { CreditDebitAttribution, SessionForkPointRefusal } from "@opengeni/db";
26
26
  import { creditDebitAttributionForGrant } from "../access-grant-rls";
27
27
  import { transcriptionBillingRefusal } from "../transcription/billing-refusal";
28
28
  import { resolveSiteSessionOrigin, withOptionalSiteCommandOrigin } from "../site-session-origin";
@@ -606,7 +606,7 @@ export function registerSessionRoutes(app: Hono, deps: SessionRouteDeps): void {
606
606
  });
607
607
 
608
608
  // Every deployment has one fail-closed authorization seam for every HTTP
609
- // session surface. The core boundary always enforces durable OpenGeni-owned
609
+ // session surface. The core boundary always enforces durable Opengeni-owned
610
610
  // private-session rules; an embedding host port can add narrower policy.
611
611
  // Register it before the routes so a newly added path cannot accidentally
612
612
  // inherit workspace access without an explicit operation classification. The
@@ -5603,6 +5603,17 @@ function sessionAuthorizationHttpError(error: unknown): HTTPException {
5603
5603
  throw error;
5604
5604
  }
5605
5605
 
5606
+ const SESSION_FORK_POINT_REFUSAL_MESSAGES: Record<SessionForkPointRefusal, string> = {
5607
+ message_unavailable:
5608
+ "Only your messages and the agent's replies can be fork points. Choose one of those, or fork the whole session.",
5609
+ boundary_not_retained:
5610
+ "This message is from before the conversation was compacted, so it can't be a fork point. Choose a more recent message, or fork the whole session.",
5611
+ history_compacted:
5612
+ "The conversation up to this message was compacted, so it can't be copied as it was then. Choose a more recent message, or fork the whole session.",
5613
+ unfinished_tool_exchange:
5614
+ "The agent was still waiting for a tool result at this message. Choose a later message, or fork the whole session.",
5615
+ };
5616
+
5606
5617
  export function sessionTenancyHttpError(error: unknown): Error {
5607
5618
  if (error instanceof SessionTenancyManagedHumanRequiredError) {
5608
5619
  return new ApiHttpError(403, {
@@ -5665,6 +5676,14 @@ export function sessionTenancyHttpError(error: unknown): Error {
5665
5676
  });
5666
5677
  }
5667
5678
  if (error instanceof SessionTenancyInvalidRequestError) {
5679
+ if (error.forkPointRefusal) {
5680
+ return new ApiHttpError(422, {
5681
+ code: "validation_failed",
5682
+ message: SESSION_FORK_POINT_REFUSAL_MESSAGES[error.forkPointRefusal],
5683
+ retryable: false,
5684
+ details: { reason: `fork_point_${error.forkPointRefusal}` },
5685
+ });
5686
+ }
5668
5687
  return new ApiHttpError(422, {
5669
5688
  code: "validation_failed",
5670
5689
  message: "Invalid session tenancy request.",
@@ -184,7 +184,7 @@ export function coreConnectRefused(
184
184
  return new HTTPException(personal ? 409 : 403, {
185
185
  message: personal
186
186
  ? `a private ${displayName} account is connected from your Personal workspace`
187
- : `only an organization administrator can connect a new shared ${displayName} account; workspace administrators can reconnect the accounts their workspace manages`,
187
+ : `only an administrator of this workspace or of the organization can connect a ${displayName} account here`,
188
188
  });
189
189
  }
190
190
  }
@@ -194,7 +194,7 @@ function disconnectRefused(outcome: SubscriptionCoreDisconnectOutcome, displayNa
194
194
  throw new HTTPException(404, { message: `${displayName} account not found` });
195
195
  if (outcome === "forbidden")
196
196
  throw new HTTPException(403, {
197
- message: `only an organization administrator can disconnect a shared ${displayName} account`,
197
+ message: `only an organization administrator can disconnect this ${displayName} account; a workspace administrator can disconnect only an account that serves just their workspace`,
198
198
  });
199
199
  if (outcome === "unresolved_redemption")
200
200
  throw new HTTPException(409, {
@@ -385,7 +385,7 @@ export function registerVaultProviderRoutes(
385
385
  );
386
386
  });
387
387
 
388
- // The vaults a stored 1Password connection reaches, with the OpenGeni vault
388
+ // The vaults a stored 1Password connection reaches, with the Opengeni vault
389
389
  // each one is linked to (if any). One `vaults.list` request.
390
390
  app.get("/v1/workspaces/:workspaceId/connections/:connectionId/provider-vaults", async (c) => {
391
391
  const workspaceId = c.req.param("workspaceId");
@@ -427,7 +427,7 @@ export function registerVaultProviderRoutes(
427
427
  });
428
428
 
429
429
  /**
430
- * Link a provider vault as an OpenGeni vault, then mirror its metadata.
430
+ * Link a provider vault as an Opengeni vault, then mirror its metadata.
431
431
  * The link stands even if the first sync fails; its status says why.
432
432
  */
433
433
  app.post("/v1/workspaces/:workspaceId/vaults/link", async (c) => {
@@ -555,7 +555,7 @@ export function registerVaultProviderRoutes(
555
555
  }
556
556
  const created = await provider.createVault(routeProviderContext(connectionId), {
557
557
  name: payload.name,
558
- description: "Managed by OpenGeni",
558
+ description: "Managed by Opengeni",
559
559
  allowAdminsAccess: true,
560
560
  ...(payload.groupId ? { groupId: payload.groupId } : {}),
561
561
  });
@@ -251,7 +251,7 @@ export async function reconcileScreenControlOnHello(
251
251
  }
252
252
 
253
253
  /** Open one macOS Privacy & Security pane on a connected Mac. Opening a pane
254
- * grants nothing; the person flips the switch for OpenGeni. */
254
+ * grants nothing; the person flips the switch for Opengeni. */
255
255
  export async function openMachinePrivacySettings(
256
256
  services: Services,
257
257
  input: {