@opengeni/api-router 3.3.0 → 3.4.0

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.
@@ -40,8 +40,12 @@ import {
40
40
  BrowserSessionLifecycleRequest,
41
41
  BrowserSessionListResponse,
42
42
  BrowserSessionMutationResponse,
43
+ type BrowserTarget,
43
44
  BrowserTargetListResponse,
44
45
  BrowserTargetState,
46
+ BrowserWorkingCopyAdoptionRequest,
47
+ BrowserWorkingCopyAdoptionResponse,
48
+ BrowserWorkingCopyInspection,
45
49
  CreateBrowserSessionRequest,
46
50
  ExternalAuthInteractiveRequest,
47
51
  ExternalAuthInteractiveResponse,
@@ -57,6 +61,8 @@ import {
57
61
  type BrowserActionRequest as BrowserActionRequestValue,
58
62
  type BrowserDownload as BrowserDownloadValue,
59
63
  type BrowserSession as BrowserSessionValue,
64
+ type BrowserWorkingCopyAdoption as BrowserWorkingCopyAdoptionValue,
65
+ type BrowserWorkingCopyRefusal as BrowserWorkingCopyRefusalValue,
60
66
  type FileAsset,
61
67
  type CreateBrowserSessionRequest as CreateBrowserSessionRequestValue,
62
68
  type InteractionPlacement,
@@ -143,6 +149,7 @@ import {
143
149
  verifyAuthRun,
144
150
  settleBrowserDownloadSaveFailure,
145
151
  terminalizeStaleConnectedInteractionPlacement,
152
+ recordAuditEvent,
146
153
  type BrowserSessionControlRecord,
147
154
  type BrowserPrivateCheckpointAuthority,
148
155
  type BrowserRevisionArtifactAuthority,
@@ -182,6 +189,9 @@ import {
182
189
  type BrowserControlScreenshotOptions,
183
190
  type BrowserControlFrame,
184
191
  type PlacementBrowserStateCaptureReceipt,
192
+ type PlacementBrowserWorkingCopyAdoption,
193
+ type PlacementBrowserWorkingCopyAuthority,
194
+ type PlacementBrowserWorkingCopyInspection,
185
195
  type PlacementBrowserNetworkRoute,
186
196
  type PlacementBrowserTransport,
187
197
  type RestorePlacementBrowserStateInput,
@@ -352,22 +362,36 @@ export function registerBrowserSessionRoutes(
352
362
  const request = await parseJsonBody(context, CreateBrowserSessionRequest);
353
363
  const startedAtMs = performance.now();
354
364
  const stages = new InteractionRouteStages("create");
355
- await authorizeSourceSession(deps, grant, request.sessionId, "session.control");
365
+ // The replay lookup and the source session are read alongside the
366
+ // authorization check. A denial still wins, and neither read is used
367
+ // until the request is authorized; each failure surfaces where it did.
368
+ const [authorized, existingRead, sourceRead] = await Promise.allSettled([
369
+ authorizeSourceSession(deps, grant, request.sessionId, "session.control"),
370
+ findBrowserSessionControlRecordByOperation(deps.db, {
371
+ accountId: grant.accountId,
372
+ workspaceId,
373
+ operationId: request.operationId,
374
+ }),
375
+ requireSourceSession(deps, workspaceId, request.sessionId),
376
+ ]);
377
+ if (authorized.status === "rejected") throw authorized.reason;
356
378
  stages.mark("authorize");
357
379
  const origin = requestOrigin(context, deps.settings);
358
380
  const authority = browserAuthorityRoot(deps);
359
381
  let prepared: Awaited<ReturnType<typeof prepareBrowserSessionCreate>> | null = null;
382
+ // What the controller showed when it launched this browser, returned so
383
+ // the caller can present the opened tab without two more reads.
384
+ const launch: { result: Awaited<ReturnType<BrowserControlClient["createSession"]>> | null } = {
385
+ result: null,
386
+ };
360
387
 
361
388
  try {
362
389
  assertEphemeralBrowserCreateEnabled(
363
390
  request,
364
391
  deps.settings.experimentalBrowserContextPoolEnabled,
365
392
  );
366
- const existing = await findBrowserSessionControlRecordByOperation(deps.db, {
367
- accountId: grant.accountId,
368
- workspaceId,
369
- operationId: request.operationId,
370
- });
393
+ if (existingRead.status === "rejected") throw existingRead.reason;
394
+ const existing = existingRead.value;
371
395
  if (existing && existing.sourceSessionId !== request.sessionId) {
372
396
  throw new BrowserSessionOperationConflictError(
373
397
  "BrowserSession create operation belongs to another source session",
@@ -401,7 +425,8 @@ export function registerBrowserSessionRoutes(
401
425
  }
402
426
  }
403
427
 
404
- const sourceSession = await requireSourceSession(deps, workspaceId, request.sessionId);
428
+ if (sourceRead.status === "rejected") throw sourceRead.reason;
429
+ const sourceSession = sourceRead.value;
405
430
  stages.mark("source_session");
406
431
  const response = await withBrowserPlacement(
407
432
  sourceSession,
@@ -536,7 +561,7 @@ export function registerBrowserSessionRoutes(
536
561
  linkedComputer !== null,
537
562
  );
538
563
  stages.mark("display");
539
- return await client.createSession({
564
+ launch.result = await client.createSession({
540
565
  browserSessionId: preparedSession.id,
541
566
  controllerGeneration,
542
567
  tokenGeneration: record.tokenGeneration,
@@ -553,6 +578,7 @@ export function registerBrowserSessionRoutes(
553
578
  ...(request.initialUrl ? { initialUrl: request.initialUrl } : {}),
554
579
  ...(restore ? { restore } : {}),
555
580
  });
581
+ return launch.result;
556
582
  },
557
583
  );
558
584
  },
@@ -614,7 +640,7 @@ export function registerBrowserSessionRoutes(
614
640
  }
615
641
  },
616
642
  );
617
- const parsed = BrowserSessionMutationResponse.parse(response);
643
+ const parsed = BrowserSessionMutationResponse.parse(withInitialView(response, launch.result));
618
644
  observeLifecycleResult(deps.observability, startedAtMs, parsed);
619
645
  reportInteractionRouteStages(context, deps.observability, stages);
620
646
  return context.json(
@@ -2612,6 +2638,217 @@ export function registerBrowserSessionRoutes(
2612
2638
  },
2613
2639
  );
2614
2640
 
2641
+ /** Run one working-copy request against an active browser's own controller
2642
+ * generation, with the same session.control and placement authority as any
2643
+ * browser control. The controller binds every request to the exact
2644
+ * controller authority the copy was launched under. Never replayed. */
2645
+ async function withPreservedWorkingCopy<T>(
2646
+ context: Context,
2647
+ callback: (input: {
2648
+ grant: AccessGrant;
2649
+ workspaceId: string;
2650
+ browserSessionId: string;
2651
+ client: BrowserControlClient;
2652
+ authority: PlacementBrowserWorkingCopyAuthority;
2653
+ }) => Promise<T>,
2654
+ ): Promise<T> {
2655
+ const { workspaceId, grant, browserSessionId } = await browserRoutePreamble(
2656
+ context,
2657
+ "sessions:control",
2658
+ );
2659
+ return await withActiveBrowserController(
2660
+ context,
2661
+ grant,
2662
+ workspaceId,
2663
+ browserSessionId,
2664
+ "session.control",
2665
+ "browser.control",
2666
+ async ({ client, record, binding, placement }) => {
2667
+ const transport = browserRuntimeTransport(record.session, placement.transport);
2668
+ if (
2669
+ transport.kind !== "managed" ||
2670
+ transport.engine !== "chromium" ||
2671
+ transport.ephemeralPartition ||
2672
+ browserSessionStorageMode(record.session) !== "private_profile"
2673
+ ) {
2674
+ throw new BrowserSessionStateError(WORKING_COPY_REFUSAL_EXPLANATIONS.unsupported_browser);
2675
+ }
2676
+ const tokens = deriveBrowserSessionControllerTokens({
2677
+ rootSecret: browserAuthorityRoot(deps),
2678
+ accountId: grant.accountId,
2679
+ workspaceId,
2680
+ placement: record.session.placement,
2681
+ placementInstanceId: placement.placementInstanceId,
2682
+ browserSessionId,
2683
+ controllerGeneration: binding.controllerGeneration,
2684
+ tokenGeneration: record.tokenGeneration,
2685
+ });
2686
+ return await callback({
2687
+ grant,
2688
+ workspaceId,
2689
+ browserSessionId,
2690
+ client,
2691
+ authority: {
2692
+ browserSessionId,
2693
+ controllerGeneration: binding.controllerGeneration,
2694
+ tokenGeneration: record.tokenGeneration,
2695
+ ...tokens,
2696
+ },
2697
+ });
2698
+ },
2699
+ // A missing controller session is the state being inspected; never
2700
+ // launch anything to "recover" it here.
2701
+ false,
2702
+ );
2703
+ }
2704
+
2705
+ app.post(
2706
+ "/v1/workspaces/:workspaceId/browser-sessions/:browserSessionId/working-copy/inspection",
2707
+ async (context) => {
2708
+ await parseEmptyJsonBody(context);
2709
+ const result = await withPreservedWorkingCopy(
2710
+ context,
2711
+ async ({ grant, workspaceId, browserSessionId, client, authority }) => {
2712
+ const inspection = await client.inspectWorkingCopy(authority);
2713
+ const checkpoint = await getBrowserPrivateCheckpointAuthority(deps.db, {
2714
+ accountId: grant.accountId,
2715
+ workspaceId,
2716
+ browserSessionId,
2717
+ }).catch(() => null);
2718
+ return BrowserWorkingCopyInspection.parse({
2719
+ browserSessionId,
2720
+ controllerGeneration: authority.controllerGeneration,
2721
+ adoptable: inspection.refusal === null && inspection.manifest !== null,
2722
+ refusal: inspection.refusal,
2723
+ explanation: workingCopyInspectionExplanation(
2724
+ inspection,
2725
+ checkpoint?.createdAt ?? null,
2726
+ ),
2727
+ manifest: inspection.manifest,
2728
+ platformSupported: inspection.platformSupported,
2729
+ profileIdentityMatches: inspection.profileIdentityMatches,
2730
+ profileInUse: inspection.profileInUse,
2731
+ receipt: inspection.receipt,
2732
+ freshness: { status: "unknown", latestCheckpointAt: checkpoint?.createdAt ?? null },
2733
+ activeAdoption: inspection.activeAdoption,
2734
+ });
2735
+ },
2736
+ );
2737
+ return context.json(result);
2738
+ },
2739
+ );
2740
+
2741
+ app.post(
2742
+ "/v1/workspaces/:workspaceId/browser-sessions/:browserSessionId/working-copy/adoptions",
2743
+ async (context) => {
2744
+ const request = await parseJsonBody(context, BrowserWorkingCopyAdoptionRequest);
2745
+ const result = await withPreservedWorkingCopy(
2746
+ context,
2747
+ async ({ grant, workspaceId, browserSessionId, client, authority }) => {
2748
+ // Durable attribution before any effect: who selected which exact
2749
+ // bytes as this browser's state. Freshness stays unproven.
2750
+ await recordAuditEvent(deps.db, {
2751
+ accountId: grant.accountId,
2752
+ workspaceId,
2753
+ subjectId: grant.subjectId,
2754
+ action: "browserSession.working_copy.adoption_requested",
2755
+ targetType: "browser_session",
2756
+ targetId: browserSessionId,
2757
+ metadata: {
2758
+ operationId: request.operationId,
2759
+ manifestDigest: request.manifestDigest,
2760
+ controllerGeneration: authority.controllerGeneration,
2761
+ },
2762
+ });
2763
+ const adoption = await client.adoptWorkingCopy({
2764
+ ...authority,
2765
+ operationId: request.operationId,
2766
+ manifestDigest: request.manifestDigest,
2767
+ });
2768
+ await recordAuditEvent(deps.db, {
2769
+ accountId: grant.accountId,
2770
+ workspaceId,
2771
+ subjectId: grant.subjectId,
2772
+ action: "browserSession.working_copy.adoption_settled",
2773
+ targetType: "browser_session",
2774
+ targetId: browserSessionId,
2775
+ metadata: {
2776
+ operationId: adoption.operationId,
2777
+ manifestDigest: adoption.manifestDigest,
2778
+ controllerGeneration: adoption.controllerGeneration,
2779
+ phase: adoption.phase,
2780
+ failureCode: adoption.failureCode,
2781
+ },
2782
+ }).catch(() => undefined);
2783
+ return BrowserWorkingCopyAdoptionResponse.parse({
2784
+ inFlight: false,
2785
+ adoption: publicWorkingCopyAdoption(adoption),
2786
+ explanation: workingCopyAdoptionExplanation(false, adoption),
2787
+ });
2788
+ },
2789
+ );
2790
+ return context.json(result);
2791
+ },
2792
+ );
2793
+
2794
+ app.get(
2795
+ "/v1/workspaces/:workspaceId/browser-sessions/:browserSessionId/working-copy/adoptions/:operationId",
2796
+ async (context) => {
2797
+ const operationId = requireUuidParam(context, "operationId");
2798
+ const result = await withPreservedWorkingCopy(context, async ({ client, authority }) => {
2799
+ const status = await client.workingCopyAdoption(authority, operationId);
2800
+ return BrowserWorkingCopyAdoptionResponse.parse({
2801
+ inFlight: status.inFlight,
2802
+ adoption: status.adoption ? publicWorkingCopyAdoption(status.adoption) : null,
2803
+ explanation: workingCopyAdoptionExplanation(status.inFlight, status.adoption),
2804
+ });
2805
+ });
2806
+ return context.json(result);
2807
+ },
2808
+ );
2809
+
2810
+ app.post(
2811
+ "/v1/workspaces/:workspaceId/browser-sessions/:browserSessionId/working-copy/adoptions/:operationId/revert",
2812
+ async (context) => {
2813
+ const operationId = requireUuidParam(context, "operationId");
2814
+ const result = await withPreservedWorkingCopy(
2815
+ context,
2816
+ async ({ grant, workspaceId, browserSessionId, client, authority }) => {
2817
+ await recordAuditEvent(deps.db, {
2818
+ accountId: grant.accountId,
2819
+ workspaceId,
2820
+ subjectId: grant.subjectId,
2821
+ action: "browserSession.working_copy.revert_requested",
2822
+ targetType: "browser_session",
2823
+ targetId: browserSessionId,
2824
+ metadata: { operationId, controllerGeneration: authority.controllerGeneration },
2825
+ });
2826
+ const status = await client.revertWorkingCopyAdoption({ ...authority, operationId });
2827
+ await recordAuditEvent(deps.db, {
2828
+ accountId: grant.accountId,
2829
+ workspaceId,
2830
+ subjectId: grant.subjectId,
2831
+ action: "browserSession.working_copy.revert_settled",
2832
+ targetType: "browser_session",
2833
+ targetId: browserSessionId,
2834
+ metadata: {
2835
+ operationId,
2836
+ controllerGeneration: authority.controllerGeneration,
2837
+ phase: status.adoption?.phase ?? null,
2838
+ failureCode: status.adoption?.failureCode ?? null,
2839
+ },
2840
+ }).catch(() => undefined);
2841
+ return BrowserWorkingCopyAdoptionResponse.parse({
2842
+ inFlight: status.inFlight,
2843
+ adoption: status.adoption ? publicWorkingCopyAdoption(status.adoption) : null,
2844
+ explanation: workingCopyAdoptionExplanation(status.inFlight, status.adoption),
2845
+ });
2846
+ },
2847
+ );
2848
+ return context.json(result);
2849
+ },
2850
+ );
2851
+
2615
2852
  app.post(
2616
2853
  "/v1/workspaces/:workspaceId/browser-sessions/:browserSessionId/end",
2617
2854
  async (context) => {
@@ -4457,6 +4694,34 @@ function requireOperationGeneration(record: BrowserSessionControlRecord): string
4457
4694
  return generation;
4458
4695
  }
4459
4696
 
4697
+ /** Attach the controller's launch view to a create that activated exactly that
4698
+ * launch in this request. Replays, failures and other bindings get nothing, so
4699
+ * a caller then reads the targets and observes as before. */
4700
+ export function withInitialView<
4701
+ T extends {
4702
+ session: { id: string; lifecycle: string; controller: { controllerGeneration: string } | null };
4703
+ operation: { state: string; replayed: boolean };
4704
+ },
4705
+ >(
4706
+ response: T,
4707
+ launched: Awaited<ReturnType<BrowserControlClient["createSession"]>> | null,
4708
+ ): T | (T & { initialView: { targets: BrowserTarget[]; observation: BrowserObservation } }) {
4709
+ if (
4710
+ !launched?.targets ||
4711
+ response.operation.state !== "completed" ||
4712
+ response.operation.replayed ||
4713
+ response.session.lifecycle !== "active" ||
4714
+ response.session.id !== launched.browserSessionId ||
4715
+ response.session.controller?.controllerGeneration !== launched.controllerGeneration
4716
+ ) {
4717
+ return response;
4718
+ }
4719
+ return {
4720
+ ...response,
4721
+ initialView: { targets: launched.targets, observation: launched.observation },
4722
+ };
4723
+ }
4724
+
4460
4725
  async function provisionController(
4461
4726
  deps: ApiRouteDeps,
4462
4727
  grant: AccessGrant,
@@ -5376,6 +5641,91 @@ function recordAttachedBrowserUnavailable(
5376
5641
  }
5377
5642
  }
5378
5643
 
5644
+ const WORKING_COPY_REFUSAL_EXPLANATIONS: Record<BrowserWorkingCopyRefusalValue, string> = {
5645
+ unsupported_platform:
5646
+ "Adopting a working copy is supported only for browsers on Linux machines, where the controller can prove no process uses the copy. Nothing was changed.",
5647
+ unsupported_browser:
5648
+ "Only a managed Chromium browser with a private profile keeps a working copy that can be adopted. Nothing was changed.",
5649
+ browser_running:
5650
+ "The browser is running, starting or being ended on its controller, so there is no stopped working copy to adopt now. If it is running, use it normally; otherwise inspect again once it has stopped.",
5651
+ working_copy_invalid:
5652
+ "The browser's working directory on its machine is missing, incomplete or not a plain directory, so it cannot be adopted. Nothing was changed.",
5653
+ receipt_not_adoptable:
5654
+ "The working copy has no completed record of a browser launch, so it cannot be adopted. Its record and recovery state are preserved.",
5655
+ authority_changed:
5656
+ "The working copy was launched under a different controller authority than this browser now has, so it cannot be adopted for it. Nothing was changed.",
5657
+ process_state_unknown:
5658
+ "It cannot be proven that no process uses the copy's browser profile. Close any browser using it, then inspect again. Nothing was changed.",
5659
+ adoption_not_needed:
5660
+ "The working copy is the same directory the browser was launched in, so it needs no adoption; the browser continues from it when it is next used.",
5661
+ adoption_unresolved:
5662
+ "An earlier adoption for this browser has not finished. Check it with getBrowserSessionWorkingCopyAdoption and its operation id (see activeAdoption). Inspecting and checking never change it; retrying that same adoption with the same operationId and manifestDigest settles it. Do not start another.",
5663
+ };
5664
+
5665
+ function workingCopyInspectionExplanation(
5666
+ inspection: PlacementBrowserWorkingCopyInspection,
5667
+ latestCheckpointAt: string | null,
5668
+ ): string {
5669
+ if (inspection.refusal) return WORKING_COPY_REFUSAL_EXPLANATIONS[inspection.refusal];
5670
+ const manifest = inspection.manifest!;
5671
+ return [
5672
+ `This working copy can be adopted: ${manifest.entries} entries, ${manifest.bytes} bytes, manifest digest ${manifest.digest}.`,
5673
+ manifest.excluded.length > 0
5674
+ ? `Left out because the controller rebuilds them: ${manifest.excluded.join(", ")}. Everything else is in the digest.`
5675
+ : "Every entry is in the digest.",
5676
+ "It is not the directory the browser was launched in (it was moved, copied or restored), so the browser refuses to continue from it until it is adopted.",
5677
+ latestCheckpointAt
5678
+ ? `How recent its contents are is not proven; the browser's latest saved checkpoint is from ${latestCheckpointAt}.`
5679
+ : "How recent its contents are is not proven.",
5680
+ "Adopting selects exactly these bytes as the browser's state and is never replaced by an older checkpoint afterwards; the directory it replaces is kept.",
5681
+ "To adopt, call adoptBrowserSessionWorkingCopy with this manifestDigest and a new operationId. If the bytes change first, the adoption is refused.",
5682
+ ].join(" ");
5683
+ }
5684
+
5685
+ function publicWorkingCopyAdoption(
5686
+ adoption: PlacementBrowserWorkingCopyAdoption,
5687
+ ): BrowserWorkingCopyAdoptionValue {
5688
+ return {
5689
+ operationId: adoption.operationId,
5690
+ browserSessionId: adoption.browserSessionId,
5691
+ controllerGeneration: adoption.controllerGeneration,
5692
+ manifestDigest: adoption.manifestDigest,
5693
+ phase: adoption.phase,
5694
+ failureCode: adoption.failureCode,
5695
+ retainedDirectories: adoption.retainedDirectories,
5696
+ createdAt: adoption.createdAt,
5697
+ updatedAt: adoption.updatedAt,
5698
+ };
5699
+ }
5700
+
5701
+ function workingCopyAdoptionExplanation(
5702
+ inFlight: boolean,
5703
+ adoption: PlacementBrowserWorkingCopyAdoption | null,
5704
+ ): string {
5705
+ const kept = adoption?.retainedDirectories.length
5706
+ ? ` Kept on the machine for separate cleanup: ${adoption.retainedDirectories.join(", ")}.`
5707
+ : "";
5708
+ if (inFlight)
5709
+ return "The adoption is still running on the controller. Check again with getBrowserSessionWorkingCopyAdoption; do not start another.";
5710
+ if (!adoption)
5711
+ return "The controller has no record of this adoption, which proves it changed nothing there.";
5712
+ switch (adoption.phase) {
5713
+ case "adopted":
5714
+ return `Adopted. The browser continues from the copy with manifest digest ${adoption.manifestDigest} the next time it is used, and is never replaced by an older checkpoint.${kept}`;
5715
+ case "interrupted":
5716
+ case "failed":
5717
+ return `The adoption stopped before the copy took the browser's place${adoption.failureCode ? ` (${adoption.failureCode})` : ""}; the browser's own directory is unchanged.${kept}`;
5718
+ case "collision":
5719
+ return `The adoption found the browser's directories in an unexpected state (${adoption.failureCode ?? "unknown"}) and stopped without moving or removing anything more. The browser stays unavailable until this adoption is reverted with revertBrowserSessionWorkingCopyAdoption, which puts the browser's own directory back and keeps the staged copy. If the revert refuses, the directories need a person on that machine.${kept}`;
5720
+ case "reverting":
5721
+ return "A revert of this adoption stopped part way and is not running. Retry revertBrowserSessionWorkingCopyAdoption with the same operationId to finish it; a retry repeats nothing already done.";
5722
+ case "reverted":
5723
+ return `Reverted. The browser's own directory is back in place and the staged copy was not used.${kept}`;
5724
+ default:
5725
+ return "The adoption stopped part way and is not running. Checking it never changes it. To finish it, retry the same adoption with the same operationId and manifestDigest under the authority that approved it; a retry repeats nothing already done. To undo it instead, revert it with revertBrowserSessionWorkingCopyAdoption. Do not start another.";
5726
+ }
5727
+ }
5728
+
5379
5729
  function browserRouteError(error: unknown): HTTPException {
5380
5730
  const tabDiagnosis = browserTabDiagnosisError(error);
5381
5731
  if (tabDiagnosis instanceof ApiHttpError) return tabDiagnosis;
@@ -247,6 +247,18 @@ function parseId(schema: z.ZodString, value: string, label: string): string {
247
247
 
248
248
  function rethrowMembershipError(error: unknown, resourceLimitMessage?: string): never {
249
249
  const sqlState = nestedPostgresSqlState(error);
250
+ if (sqlState === "P0S01") {
251
+ throw new HTTPException(402, {
252
+ res: Response.json(
253
+ {
254
+ error: "seat_limit_reached",
255
+ message: "Organization seat limit reached. Upgrade your plan to add people.",
256
+ action: { type: "upgrade", href: "/billing" },
257
+ },
258
+ { status: 402 },
259
+ ),
260
+ });
261
+ }
250
262
  if (sqlState === "54000" && resourceLimitMessage) {
251
263
  throw new HTTPException(409, {
252
264
  message: resourceLimitMessage,
@@ -1004,6 +1004,7 @@ export function registerSessionRoutes(app: Hono, deps: SessionRouteDeps): void {
1004
1004
  ...(query.includeTotals ? { includeTotals: true } : {}),
1005
1005
  ...(query.needsYouOnly ? { needsYouOnly: true } : {}),
1006
1006
  ...(query.contentArchivedOnly ? { contentArchivedOnly: true } : {}),
1007
+ ...(query.mineOnly ? { mineOnly: true } : {}),
1007
1008
  ...(query.includePinned === false ? { includePinned: false } : {}),
1008
1009
  ...(query.archivedOnly ? { archivedOnly: true } : {}),
1009
1010
  ...(query.sortBy ? { sortBy: query.sortBy } : {}),
@@ -5835,6 +5836,7 @@ export function sessionListQuery(
5835
5836
  includeTotals: boolean;
5836
5837
  needsYouOnly: boolean;
5837
5838
  contentArchivedOnly: boolean;
5839
+ mineOnly: boolean;
5838
5840
  archivedOnly: boolean;
5839
5841
  sortBy: "updatedAt" | "createdAt" | "name" | undefined;
5840
5842
  archiveStatus: "active" | "archived" | "all" | undefined;
@@ -5884,17 +5886,20 @@ export function sessionListQuery(
5884
5886
  throw new HTTPException(400, { message: 'includePinned must be "true" or "false"' });
5885
5887
  }
5886
5888
  const includePinned = query.includePinned !== "false";
5887
- for (const key of ["includeTotals", "needsYouOnly", "contentArchivedOnly"]) {
5889
+ for (const key of ["includeTotals", "needsYouOnly", "contentArchivedOnly", "mineOnly"]) {
5888
5890
  if (query[key] !== undefined && !["true", "false"].includes(query[key]!))
5889
5891
  throw new HTTPException(400, { message: `${key} must be "true" or "false"` });
5890
5892
  }
5891
5893
  const includeTotals = query.includeTotals === "true";
5892
5894
  const needsYouOnly = query.needsYouOnly === "true";
5893
5895
  const contentArchivedOnly = query.contentArchivedOnly === "true";
5896
+ const mineOnly = query.mineOnly === "true";
5894
5897
  if (includeTotals && (!allowCursor || (parentSessionId !== "null" && !pinsOnly)))
5895
5898
  throw new HTTPException(400, { message: "includeTotals requires a root page" });
5896
5899
  if (needsYouOnly && !allowCursor)
5897
5900
  throw new HTTPException(400, { message: 'needsYouOnly requires view="page"' });
5901
+ if (mineOnly && !allowCursor)
5902
+ throw new HTTPException(400, { message: 'mineOnly requires view="page"' });
5898
5903
  if (pinsOnly && !includePinned) {
5899
5904
  throw new HTTPException(400, { message: "pinsOnly requires includePinned" });
5900
5905
  }
@@ -5990,6 +5995,7 @@ export function sessionListQuery(
5990
5995
  const hasPageFilters =
5991
5996
  needsYouOnly ||
5992
5997
  contentArchivedOnly ||
5998
+ mineOnly ||
5993
5999
  originSiteId !== undefined ||
5994
6000
  channelId !== undefined ||
5995
6001
  createdByKind !== undefined ||
@@ -6025,6 +6031,7 @@ export function sessionListQuery(
6025
6031
  includeTotals,
6026
6032
  needsYouOnly,
6027
6033
  contentArchivedOnly,
6034
+ mineOnly,
6028
6035
  archivedOnly,
6029
6036
  sortBy: sortBy.data,
6030
6037
  archiveStatus: archiveStatus.data,