@dev.fast/whiteboard 0.4.1-preview.20261027.141 → 0.4.1-preview.20261028.142

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.
Files changed (70) hide show
  1. package/dist/{account-alias-AJ6miBT5.js → account-alias-D1iIc7j5.js} +4 -5
  2. package/dist/{agent-cli-DIC2tBpL.js → agent-cli-B_gSzrhA.js} +8 -9
  3. package/dist/agent-cli-DtjnkDSL.js +2 -0
  4. package/dist/{agent-client-DyNmSHfO.js → agent-client-DudPjH3I.js} +3 -3
  5. package/dist/{authoring-tools-B9MhiKw4.js → authoring-tools-B-OyXmtR.js} +5 -5
  6. package/dist/{background-server-CZaB8zFH.js → background-server-CORnMQmu.js} +1 -1
  7. package/dist/{background-server-DlgbnDVt.js → background-server-CesuMV6N.js} +7 -6
  8. package/dist/build-info.json +1 -1
  9. package/dist/{cli-D7Lpc4ws.js → cli-CIP_-Fvm.js} +1 -1
  10. package/dist/{cli-runner-JPkbHD7I.js → cli-runner-dHnKnyNw.js} +22 -23
  11. package/dist/cli.js +2 -2
  12. package/dist/{fuzzy-match-rWIeloqo.js → fuzzy-match-CTvayutE.js} +3 -3
  13. package/dist/{headless-host-Cn6WDmaL.js → headless-host-CWuFw-CO.js} +118 -218
  14. package/dist/{headless-host-DmuAjcgt.js → headless-host-Cnu-o0xk.js} +1 -1
  15. package/dist/{import-CUNdl3NP.js → import-FOpJL22V.js} +198 -505
  16. package/dist/{lens-selection-BQxqiuWY.js → lens-selection-B_LTVyTB.js} +1 -1
  17. package/dist/{local-data-DxkQXCNG.js → local-data-BhNZ1Yep.js} +72 -91
  18. package/dist/{mcp-B154Vpj0.js → mcp-2bWqlJNW.js} +3 -3
  19. package/dist/{package-paths-LcO6HNp_.js → package-paths-Cg6fnaGp.js} +1 -1
  20. package/dist/profile-BmtFpq8d.js +2 -0
  21. package/dist/{profile-hC1iAoRB.js → profile-DnwIJvlh.js} +2 -2
  22. package/dist/{remote-attach-DhvNHMe1.js → remote-attach-C79X5M2Z.js} +4 -4
  23. package/dist/{remote-extensions-VLpt_4Tn.js → remote-extensions-Bw029iIS.js} +1 -1
  24. package/dist/{remote-extensions-DMKyDjAs.js → remote-extensions-MUUgbLJn.js} +2 -2
  25. package/dist/{remote-language-server-zgvAe8wb.js → remote-language-server-Dw1o27aF.js} +4 -4
  26. package/dist/{remote-uninstall-BR-rIuvK.js → remote-uninstall-Aza0u1_3.js} +5 -5
  27. package/dist/{request-origin-BnA7Nnca.js → request-origin-DEQd09Db.js} +3 -3
  28. package/dist/{review-app-launcher-B0Z4B8By.js → review-app-launcher-L7xKjTLQ.js} +2 -2
  29. package/dist/{src-KTjWplV9.js → review-home-paths-LdGS4SpR.js} +48 -8
  30. package/dist/{review-telemetry-yuCLNpDu.js → review-telemetry-lmK8HPLI.js} +4 -5
  31. package/dist/{runtime-5WG8rRgg.js → runtime-Bhk5L_SN.js} +1 -1
  32. package/dist/runtime.js +1 -1
  33. package/dist/server/desktop-host.js +34 -48
  34. package/dist/{server-discovery-Dj_xSDSa.js → server-discovery-D-2vz5gG.js} +2 -3
  35. package/dist/sharing/index.d.ts +51 -144
  36. package/dist/sharing/index.js +2 -2
  37. package/dist/{src-BJ_MeEmN.js → src-Bew3mW9I.js} +1 -1
  38. package/dist/{src-BTCRHmRM.js → src-Y30gweiK.js} +7 -1
  39. package/dist/{stored-document-migration-fQWLDqDx.js → stored-document-migration-CZe65mkx.js} +20 -6
  40. package/dist/{thread-state-BburMvgn.js → thread-state-XgVKaQ3-.js} +1 -1
  41. package/dist/{tool-failure-BuvwfVeF.js → tool-failure-Bi_-Zck-.js} +2 -2
  42. package/instructions/animation.md +18 -68
  43. package/instructions/authoring.md +1 -1
  44. package/package.json +5 -5
  45. package/src/review-api/README.md +5 -5
  46. package/src/review-api/ask-history.ts +3 -13
  47. package/src/review-api/authoring-tools.ts +2 -2
  48. package/src/review-api/blocks/animation.ts +50 -0
  49. package/src/review-api/http.ts +213 -383
  50. package/src/review-api/local-data.ts +24 -53
  51. package/src/review-api/review-progress.ts +1 -5
  52. package/src/review-api/store.ts +95 -16
  53. package/src/review-api/workspaces.ts +17 -48
  54. package/src/review-checkout-paths.ts +2 -5
  55. package/src/review-head-checkout.ts +3 -14
  56. package/src/server/background-server.ts +4 -2
  57. package/src/server/desktop-host.ts +17 -9
  58. package/src/server/desktop-server.ts +4 -6
  59. package/src/server/json-review-reporting.ts +4 -23
  60. package/src/server/review-gateway-pushes.ts +1 -10
  61. package/src/server/review-server-core.ts +8 -13
  62. package/src/sharing/client.ts +86 -2
  63. package/src/sharing/host.ts +111 -88
  64. package/src/sharing/import.ts +173 -710
  65. package/src/sharing/index.ts +1 -7
  66. package/src/sharing/repository.ts +18 -49
  67. package/dist/agent-cli-BDcF54b3.js +0 -2
  68. package/dist/profile-BLToeYh6.js +0 -2
  69. package/dist/review-home-paths-CjHB5c8m.js +0 -39
  70. package/src/sharing/routes.ts +0 -35
@@ -1,23 +1,22 @@
1
- import { b as resolveRepoContext, d as git, r as defaultBranch, w as runVcs } from "./src-BTCRHmRM.js";
1
+ import { b as resolveRepoContext, d as git, r as defaultBranch, w as runVcs } from "./src-Y30gweiK.js";
2
2
  import { c as jsonString, m as isStringValue, n as isJsonObject, p as isObjectValue, u as parseJsonText } from "./src-X9phtB2j.js";
3
- import { _ as readBoundedStream, a as verifyShareRepository, c as exportShare, d as legacyModeSchema, f as lensReport, g as StreamLimitError, h as uncategorizedReport, i as readShareRepository, l as referenceCommits, m as reviewProgress, n as sharedReviewId, o as ShareAuthError, p as progressUpdateSchema, s as ShareClient, u as validateShareSources, v as parseShareLink, y as shareIdSchema } from "./import-CUNdl3NP.js";
4
- import { d as invalidateStructuralComparisons, f as diffrExecutable, i as inspectSnapshot, m as readGitHubPullRequests, p as diffrMissingError, r as commandSchema } from "./local-data-DxkQXCNG.js";
5
- import { I as decodeStructuralDiffEvent, N as reviewDiffrLoginsSchema, O as ReviewFeedbackContactSchema, P as reviewDiffrSummarizerInputSchema, a as parseReviewVerbRequest, c as boardIconKind, i as parseReviewDesktopVerbResult, j as ReviewOnboardingRunRequestSchema, k as ReviewFeedbackSchema, l as boardTitle, m as REVIEW_CLIENT_REMOTE, p as REVIEW_CLIENT_HEADER, y as REVIEW_HOST_HEADER } from "./src-BJ_MeEmN.js";
6
- import { $ as runStoreLogin, M as selectTraceStorage, N as TraceStorageDeniedError, Q as readStoreAuth, X as DEFAULT_STORE_ORIGIN, Z as clearStoreAuth, b as listReviewTraceSessions, j as resolveTraceStorage, k as isTraceStorageConfigured, q as shellQuote, v as traceMachineEnabled, x as loadReviewAgentTrace } from "./src-KTjWplV9.js";
7
- import { _ as scopedCoverage, s as formatAnchor, u as selectionKey } from "./lens-selection-BQxqiuWY.js";
8
- import { N as elements, O as boardLinks, Q as stepSources, f as sourceBranchName, k as boardRepositoryId, l as pullRequestKey, p as pullRequestProvider, q as selectionReferences, w as anchorPins } from "./stored-document-migration-fQWLDqDx.js";
3
+ import { _ as parseShareLink, a as readShareRepository, c as exportShare, d as lensReport, f as progressUpdateSchema, g as readBoundedStream, h as StreamLimitError, i as forgetSharedCommits, l as validateShareSources, m as uncategorizedReport, n as shareKey, o as ShareAuthError, p as reviewProgress, s as ShareClient, t as importShare, u as legacyModeSchema, v as shareIdSchema } from "./import-FOpJL22V.js";
4
+ import { d as invalidateStructuralComparisons, f as diffrExecutable, i as inspectSnapshot, m as readGitHubPullRequests, p as diffrMissingError, r as commandSchema } from "./local-data-BhNZ1Yep.js";
5
+ import { I as decodeStructuralDiffEvent, N as reviewDiffrLoginsSchema, O as ReviewFeedbackContactSchema, P as reviewDiffrSummarizerInputSchema, a as parseReviewVerbRequest, c as boardIconKind, i as parseReviewDesktopVerbResult, j as ReviewOnboardingRunRequestSchema, k as ReviewFeedbackSchema, l as boardTitle, m as REVIEW_CLIENT_REMOTE, p as REVIEW_CLIENT_HEADER, y as REVIEW_HOST_HEADER } from "./src-Bew3mW9I.js";
6
+ import { B as TraceStorageDeniedError, D as listReviewTraceSessions, I as isTraceStorageConfigured, O as loadReviewAgentTrace, R as resolveTraceStorage, T as traceMachineEnabled, at as readStoreAuth, et as shellQuote, it as clearStoreAuth, ot as runStoreLogin, rt as DEFAULT_STORE_ORIGIN, z as selectTraceStorage } from "./review-home-paths-LdGS4SpR.js";
7
+ import { _ as scopedCoverage, s as formatAnchor, u as selectionKey } from "./lens-selection-B_LTVyTB.js";
8
+ import { K as selectionReferences, M as elements, O as boardRepositoryId, Z as stepSources, f as sourceBranchName, l as pullRequestKey, p as pullRequestProvider, w as anchorPins } from "./stored-document-migration-CZe65mkx.js";
9
9
  import { t as ReviewInputError } from "./input-error-OLgB_h31.js";
10
10
  import { A as processStartIdentity, L as writePrivateJsonAtomic, N as devReviewHome, h as TraceConfigurationError, i as isS3MockMode, j as withFileLock } from "./s3-config-CjcLyFV5.js";
11
11
  import { t as errorMessage } from "./error-message-OtiDonty.js";
12
- import { i as readReviewPackageVersion, n as readBuildCommit, t as findReviewPackageRoot } from "./package-paths-LcO6HNp_.js";
13
- import "./review-home-paths-CjHB5c8m.js";
14
- import { _ as reviewInstanceIdentity, n as desktopOwnsHome, o as reviewServerDiscoveryPath, p as desktopUsesHome, r as headlessServerLockPath, t as DESKTOP_TAKEOVER_SIGNAL } from "./server-discovery-Dj_xSDSa.js";
15
- import { c as askPicksSchema, d as selectionMarkdown, l as askQuestionSchema, n as askAgentIds, r as askChoiceKinds, s as askPermissionsSchema, t as applyAskChange, u as AgentSelectionSchema } from "./thread-state-BburMvgn.js";
16
- import { t as openReviewProfile } from "./profile-hC1iAoRB.js";
17
- import { c as REVIEW_APP_SESSION_ID_HEADER, d as sanitizeUiTelemetryEvent, f as cleanTelemetryText, o as reviewRequestOrigin, r as REVIEW_PERSON_HEADER, s as BUNDLE_FRAME_PATTERN, t as ASK_THREAD_ENV, u as isReportableCleanedMessage } from "./request-origin-BnA7Nnca.js";
18
- import { a as defaultAskPermissions, i as askAgents, l as launchAskAgent, n as askAgentSettings, o as detectAskAgents, r as askAgentTakesMcp, t as fuzzyRank } from "./fuzzy-match-rWIeloqo.js";
19
- import { a as inspectQuerySchema, i as renderInstructions, o as queryAnchor, r as instructionsQuerySchema, s as readQuerySchemas, t as authoringTools } from "./authoring-tools-B9MhiKw4.js";
20
- import { a as headlessServerOwner, c as stopBackgroundServer, o as recordedBackgroundServer } from "./background-server-DlgbnDVt.js";
12
+ import { i as readReviewPackageVersion, n as readBuildCommit, t as findReviewPackageRoot } from "./package-paths-Cg6fnaGp.js";
13
+ import { _ as reviewInstanceIdentity, n as desktopOwnsHome, o as reviewServerDiscoveryPath, p as desktopUsesHome, r as headlessServerLockPath, t as DESKTOP_TAKEOVER_SIGNAL } from "./server-discovery-D-2vz5gG.js";
14
+ import { c as askPicksSchema, d as selectionMarkdown, l as askQuestionSchema, n as askAgentIds, r as askChoiceKinds, s as askPermissionsSchema, t as applyAskChange, u as AgentSelectionSchema } from "./thread-state-XgVKaQ3-.js";
15
+ import { t as openReviewProfile } from "./profile-DnwIJvlh.js";
16
+ import { c as REVIEW_APP_SESSION_ID_HEADER, d as sanitizeUiTelemetryEvent, f as cleanTelemetryText, o as reviewRequestOrigin, r as REVIEW_PERSON_HEADER, s as BUNDLE_FRAME_PATTERN, t as ASK_THREAD_ENV, u as isReportableCleanedMessage } from "./request-origin-DEQd09Db.js";
17
+ import { a as defaultAskPermissions, i as askAgents, l as launchAskAgent, n as askAgentSettings, o as detectAskAgents, r as askAgentTakesMcp, t as fuzzyRank } from "./fuzzy-match-CTvayutE.js";
18
+ import { a as inspectQuerySchema, i as renderInstructions, o as queryAnchor, r as instructionsQuerySchema, s as readQuerySchemas, t as authoringTools } from "./authoring-tools-B-OyXmtR.js";
19
+ import { a as headlessServerOwner, c as stopBackgroundServer, o as recordedBackgroundServer } from "./background-server-CesuMV6N.js";
21
20
  import crypto$1, { createHash, randomBytes, randomUUID } from "node:crypto";
22
21
  import { z } from "zod";
23
22
  import { existsSync, mkdirSync, writeFileSync } from "node:fs";
@@ -2610,18 +2609,30 @@ const publishSchema = z.strictObject({
2610
2609
  requestId: z.uuid().optional()
2611
2610
  });
2612
2611
  /** Mounted behind local host authentication. Account credentials never enter the renderer. */
2613
- function mountSharingHost(app, store, data, shared, options = {}) {
2612
+ function mountSharingHost(app, store, data, options = {}) {
2614
2613
  const startLogin = options.login ?? runStoreLogin;
2615
2614
  let login = { pending: false };
2616
- app.get("/sharing/account", async (context) => {
2615
+ let warmedAt = 0;
2616
+ const readAccount = async () => {
2617
2617
  const account = await readStoreAuth();
2618
- return context.json({
2618
+ if (account && Date.now() - warmedAt > 6e4) {
2619
+ warmedAt = Date.now();
2620
+ new ShareClient(account.origin, account.token, options.fetch).warm().catch(() => {});
2621
+ }
2622
+ return {
2619
2623
  account: account ? {
2620
2624
  login: account.login,
2621
- origin: account.origin
2625
+ origin: account.origin,
2626
+ email: account.email,
2627
+ githubLogin: account.githubLogin
2622
2628
  } : null,
2623
2629
  ...login
2624
- });
2630
+ };
2631
+ };
2632
+ app.get("/sharing/account", async (context) => context.json(await readAccount()));
2633
+ app.post("/sharing/logout", async (context) => {
2634
+ await clearStoreAuth();
2635
+ return context.json(await readAccount());
2625
2636
  });
2626
2637
  app.post("/sharing/login", async (context) => {
2627
2638
  if (!login.pending) {
@@ -2654,52 +2665,61 @@ function mountSharingHost(app, store, data, shared, options = {}) {
2654
2665
  });
2655
2666
  mountSharingPublisher(app, store, data, options);
2656
2667
  const imports = /* @__PURE__ */ new Map();
2668
+ let importing = Promise.resolve();
2657
2669
  app.get("/sharing/import/:id", (context) => {
2658
2670
  const id = context.req.param("id");
2659
- const status = shared.status(id);
2660
- const result = {
2661
- reviewId: id,
2662
- ...status
2671
+ const { reviewId = id, ...status } = imports.get(id) ?? {
2672
+ stage: "error",
2673
+ error: "Shared review is not available."
2663
2674
  };
2664
- if (status.stage === "ready") return context.json({
2665
- ...result,
2666
- title: shared.get(id).snapshot.title
2675
+ return context.json({
2676
+ reviewId,
2677
+ ...status,
2678
+ ...status.stage === "ready" && { title: store.read(reviewId).title }
2667
2679
  });
2668
- return context.json(result);
2669
2680
  });
2670
2681
  app.post("/sharing/import", async (context) => {
2671
2682
  const { url } = z.strictObject({ url: z.string().max(2048) }).parse(await readBoundedRequestJson(context.req.raw));
2672
2683
  const parsed = parseShareLink(url);
2673
2684
  const account = await readStoreAuth();
2674
2685
  if (parsed.origin !== DEFAULT_STORE_ORIGIN && parsed.origin !== account?.origin) throw new ReviewInputError("This share uses an untrusted service. Sign in to that service before opening its links.", 400);
2675
- const id = sharedReviewId(parsed.origin, parsed.capability);
2676
- if (!imports.has(id)) {
2677
- shared.setStatus(id, "downloading");
2678
- const job = (async () => {
2679
- if (shared.has(id)) {
2680
- await shared.prepare(id);
2681
- return;
2682
- }
2686
+ const id = shareKey(parsed.origin, parsed.capability);
2687
+ const known = imports.get(id)?.stage;
2688
+ if (!known || known === "ready" || known === "error") {
2689
+ const copy = store.copyOf(id);
2690
+ const status = copy ? {
2691
+ stage: "ready",
2692
+ reviewId: copy
2693
+ } : { stage: "downloading" };
2694
+ imports.set(id, status);
2695
+ if (!copy) importing = importing.then(async () => {
2683
2696
  const { bundle, attribution } = await new ShareClient(parsed.origin).download(parsed.capability);
2684
- await shared.import(parsed.origin, parsed.capability, bundle, attribution);
2685
- })().catch((error) => {
2686
- shared.setStatus(id, "error", error instanceof ReviewInputError ? error.message : "This share is unavailable, revoked, or needs a newer Whiteboard version.");
2687
- }).finally(() => imports.delete(id));
2688
- imports.set(id, job);
2689
- shared.trackImport(job);
2697
+ status.reviewId = await importShare({
2698
+ store,
2699
+ data,
2700
+ fetchRepository: options.fetchRepository,
2701
+ cloneRepository: options.cloneRepository,
2702
+ onStage: (stage) => {
2703
+ status.stage = stage;
2704
+ }
2705
+ }, id, bundle, attribution);
2706
+ status.stage = "ready";
2707
+ }).catch((error) => {
2708
+ status.stage = "error";
2709
+ status.error = error instanceof ReviewInputError ? error.message : "This share is unavailable, revoked, or needs a newer Whiteboard version.";
2710
+ });
2690
2711
  }
2712
+ const { reviewId: _reviewId, ...status } = imports.get(id);
2691
2713
  return context.json({
2692
2714
  reviewId: id,
2693
- ...shared.status(id)
2715
+ ...status
2694
2716
  }, 202);
2695
2717
  });
2696
2718
  }
2697
2719
  /** Publishing needs only an authored store, never Desktop or recipient workspaces. */
2698
2720
  function mountSharingPublisher(app, store, data, options = {}) {
2699
- const verifyRepository = options.verifyRepository ?? verifyShareRepository;
2700
2721
  app.post("/sharing/publish", async (context) => {
2701
2722
  const input = publishSchema.parse(await readBoundedRequestJson(context.req.raw));
2702
- if (input.reviewId.startsWith("shared-")) throw new ReviewInputError("Only the authoring review can be shared.", 409);
2703
2723
  const snapshot = store.read(input.reviewId, input.version);
2704
2724
  if (snapshot.target?.kind === "worktree") throw new ReviewInputError("Pin this review to commits before sharing it.");
2705
2725
  validateShareSources(snapshot);
@@ -2707,18 +2727,7 @@ function mountSharingPublisher(app, store, data, options = {}) {
2707
2727
  if (!account) throw new ReviewInputError("Set DEV_REVIEW_SHARE_TOKEN for CI, or run whiteboard login before sharing.", 409);
2708
2728
  const repositoryId = snapshot.pins?.repositoryId ?? snapshot.target?.repositoryId;
2709
2729
  const root = repositoryId ? store.repositoryPath(repositoryId) : void 0;
2710
- const readRepository = options.readRepository ?? readShareRepository;
2711
- const commits = referenceCommits(snapshot);
2712
- const repository = (async () => {
2713
- if (!root) return void 0;
2714
- try {
2715
- const repository = await readRepository(root);
2716
- if (snapshot.pins || commits.length) await verifyRepository(root, snapshot.pins, commits);
2717
- return repository;
2718
- } catch {
2719
- return;
2720
- }
2721
- })();
2730
+ const repository = root ? (options.readRepository ?? readShareRepository)(root).catch(() => void 0) : void 0;
2722
2731
  const bundle = await exportShare({
2723
2732
  store,
2724
2733
  data,
@@ -2762,32 +2771,6 @@ function mountSharingPublisher(app, store, data, options = {}) {
2762
2771
  });
2763
2772
  }
2764
2773
  //#endregion
2765
- //#region src/sharing/routes.ts
2766
- /** Snapshot-backed reads used by the same HTTP routes as local reviews. */
2767
- var SharedReviewData = class {
2768
- store;
2769
- constructor(store) {
2770
- this.store = store;
2771
- }
2772
- map(id, resourceId) {
2773
- const { maps } = this.store.get(id);
2774
- if (!Object.hasOwn(maps, resourceId)) throw new ReviewInputError("Map is not included in this share.", 404);
2775
- return maps[resourceId];
2776
- }
2777
- async resource(id, resourceId) {
2778
- const { bundle } = this.store.get(id);
2779
- if (Object.hasOwn(bundle.images, resourceId)) return {
2780
- data: Buffer.from(bundle.images[resourceId].png, "base64"),
2781
- mimeType: "image/png"
2782
- };
2783
- if (Object.hasOwn(bundle.traces, resourceId)) return {
2784
- data: Buffer.from(JSON.stringify(bundle.traces[resourceId])),
2785
- mimeType: "application/json"
2786
- };
2787
- throw new ReviewInputError("Resource is not included in this share.", 404);
2788
- }
2789
- };
2790
- //#endregion
2791
2774
  //#region src/review-api/anchor-quotes.ts
2792
2775
  const QUOTE_LIMIT = 20;
2793
2776
  const clip = (line) => line.trim() === "" ? "(blank line)" : line.length > 120 ? `${line.slice(0, 119)}…` : line;
@@ -3494,7 +3477,7 @@ const PERSON_EDIT_MAX_BYTES = 16777216;
3494
3477
  /** A request the gateway forwarded from another machine. */
3495
3478
  const remoteCaller = (context) => context.req.header(REVIEW_CLIENT_HEADER) === REVIEW_CLIENT_REMOTE;
3496
3479
  /** Both hosts mount this behind their token authentication. */
3497
- function createReviewApi(store, data, open, shared, capabilities = () => ({
3480
+ function createReviewApi(store, data, open, sharing = false, capabilities = () => ({
3498
3481
  desktopAvailable: Boolean(open),
3499
3482
  softwareMapEnabled: false
3500
3483
  }), traceEnabled = async () => false, status = () => ({}), hooks = {}, ask, animationsEnabled = () => false) {
@@ -3513,38 +3496,12 @@ function createReviewApi(store, data, open, shared, capabilities = () => ({
3513
3496
  if (context.req.method === "GET" && !context.req.query("version")) await store.refreshWorktrees();
3514
3497
  await next();
3515
3498
  });
3516
- const sharedData = shared ? new SharedReviewData(shared) : void 0;
3517
- const isShared = (id) => id.startsWith("shared-");
3518
- const assertWritable = (id) => {
3519
- if (isShared(id)) shared?.assertWritable(id);
3520
- else store.assertWritable(id);
3521
- };
3522
- const reviewAccess = (access) => async (context, next) => {
3523
- const id = context.req.param("id");
3524
- if (access !== "read") assertWritable(id);
3525
- if (access === "edit" && isShared(id)) throw new ReviewInputError("Shared reviews are read-only.", 409);
3499
+ const writable = async (context, next) => {
3500
+ store.assertWritable(context.req.param("id"));
3526
3501
  await next();
3527
3502
  };
3528
- const sharedCommandSchema = z.object({ operation: z.object({ reviewId: z.string().optional() }) });
3529
- const sharedGuard = async (context, next) => {
3530
- const id = context.req.param("id");
3531
- if (!id || !isShared(id)) return next();
3532
- const query = readQuerySchemas.get.parse({ version: context.req.query("version") });
3533
- readReview(id, query.version);
3534
- return next();
3535
- };
3536
- app.use("/:id", sharedGuard);
3537
- app.use("/:id/*", sharedGuard);
3538
- if (shared && data) {
3539
- shared.connect(store, data);
3540
- mountSharingHost(app, store, data, shared, hooks.sharing);
3541
- }
3542
- const readReview = (id, version) => {
3543
- if (!id.startsWith("shared-")) return store.read(id, version);
3544
- const snapshot = shared?.get(id).snapshot;
3545
- if (!snapshot || version !== void 0 && version !== snapshot.version) throw new ReviewInputError("Shared review version is unavailable.", 404);
3546
- return snapshot;
3547
- };
3503
+ if (sharing && data) mountSharingHost(app, store, data, hooks.sharing);
3504
+ const readReview = (id, version) => store.read(id, version);
3548
3505
  /** Adds a PR board's state, as this host last read it from GitHub. */
3549
3506
  const withPullRequestState = (board) => {
3550
3507
  const state = data?.pullRequestState(board.origin?.pullRequestUrl ?? board.pullRequestUrl);
@@ -3553,11 +3510,11 @@ function createReviewApi(store, data, open, shared, capabilities = () => ({
3553
3510
  pullRequestState: state
3554
3511
  } : board;
3555
3512
  };
3556
- const catalog = () => [...store.list().map(withPullRequestState), ...shared?.list() ?? []];
3513
+ const catalog = () => store.list().map(withPullRequestState);
3557
3514
  /** Where a board sits among its project's, and its PR's facts, for the
3558
3515
  * board's header. */
3559
3516
  const placeOf = (id) => {
3560
- if (isShared(id) || !store.has(id)) return { siblings: [] };
3517
+ if (!store.has(id)) return { siblings: [] };
3561
3518
  const { board, parent, siblings, children } = store.place(id);
3562
3519
  const url = board.origin?.pullRequestUrl;
3563
3520
  const state = data?.pullRequestState(url);
@@ -3614,7 +3571,7 @@ function createReviewApi(store, data, open, shared, capabilities = () => ({
3614
3571
  }
3615
3572
  return context.json(await reviewProgress(store, data, snapshot, context.req.raw.signal));
3616
3573
  });
3617
- app.post("/:id/progress", reviewAccess("edit"), async (context) => {
3574
+ app.post("/:id/progress", writable, async (context) => {
3618
3575
  if (!data) throw new ReviewInputError("Source data is unavailable.", 409);
3619
3576
  const input = progressUpdateSchema.parse(await readBoundedRequestJson(context.req.raw));
3620
3577
  const id = context.req.param("id");
@@ -3655,16 +3612,13 @@ function createReviewApi(store, data, open, shared, capabilities = () => ({
3655
3612
  app.get("/:id/activity", (context) => {
3656
3613
  const id = context.req.param("id");
3657
3614
  readReview(id);
3658
- return context.json(isShared(id) ? {
3659
- workingCount: 0,
3660
- expiresAt: null
3661
- } : store.activity.read(id));
3615
+ return context.json(store.activity.read(id));
3662
3616
  });
3663
3617
  for (const action of [
3664
3618
  "begin",
3665
3619
  "update",
3666
3620
  "end"
3667
- ]) app.post(`/:id/activity/${action}`, reviewAccess("edit"), async (context) => {
3621
+ ]) app.post(`/:id/activity/${action}`, writable, async (context) => {
3668
3622
  const input = await readBoundedRequestJson(context.req.raw);
3669
3623
  const id = context.req.param("id");
3670
3624
  store.assertExists(id);
@@ -3694,13 +3648,13 @@ function createReviewApi(store, data, open, shared, capabilities = () => ({
3694
3648
  let sentList;
3695
3649
  const linksRevision = /* @__PURE__ */ new Map();
3696
3650
  const linksKey = (id) => {
3697
- if (!data || isShared(id) || !store.has(id)) return "";
3651
+ if (!data || !store.has(id)) return "";
3698
3652
  const { outgoing, backlinks } = store.links(id);
3699
3653
  return JSON.stringify([...outgoing, ...backlinks].map((link) => link.pullRequestUrl && data.pullRequestState(link.pullRequestUrl, { seen: false })));
3700
3654
  };
3701
3655
  const linksKeys = new Map([...reviewIds].map((reviewId) => [reviewId, linksKey(reviewId)]));
3702
3656
  const placeKey = (id) => {
3703
- const project = !isShared(id) && store.summary(id)?.project;
3657
+ const project = store.summary(id)?.project;
3704
3658
  const branches = project ? store.listProject(project.id).filter(({ origin }) => origin?.branch).map(({ reviewId, origin, trashedAt }) => [
3705
3659
  reviewId,
3706
3660
  origin,
@@ -3800,9 +3754,6 @@ function createReviewApi(store, data, open, shared, capabilities = () => ({
3800
3754
  }
3801
3755
  if (marked) notify();
3802
3756
  }) ?? (() => {}),
3803
- shared?.subscribe(() => {
3804
- if (markLists()) notify();
3805
- }) ?? (() => {}),
3806
3757
  store.subscribeCatalog(() => {
3807
3758
  const marked = refreshPlaces();
3808
3759
  if (markLists() || marked) notify();
@@ -3820,7 +3771,7 @@ function createReviewApi(store, data, open, shared, capabilities = () => ({
3820
3771
  });
3821
3772
  let environmentIssues;
3822
3773
  try {
3823
- if (review.target?.kind === "commits" && review.pins) data?.workspaces.open(review.reviewId, review.pins).catch(() => {});
3774
+ if (review.target?.kind === "commits" && review.pins) data?.workspaces.source(review.reviewId, review.pins).catch(() => {});
3824
3775
  environmentIssues = data?.currentEnvironmentIssues(review);
3825
3776
  } catch (error) {
3826
3777
  environmentIssues = [{ message: `Could not check language checkouts: ${errorMessage(error)}. Recheck with review_environment.` }];
@@ -3848,12 +3799,10 @@ function createReviewApi(store, data, open, shared, capabilities = () => ({
3848
3799
  };
3849
3800
  }
3850
3801
  };
3851
- app.post("/:id/open", reviewAccess("active"), async (context) => {
3852
- const id = context.req.param("id");
3853
- if (isShared(id)) await shared?.assertReady(id);
3802
+ app.post("/:id/open", writable, async (context) => {
3854
3803
  return context.json({
3855
3804
  ok: true,
3856
- ...await openReview(readReview(id))
3805
+ ...await openReview(readReview(context.req.param("id")))
3857
3806
  });
3858
3807
  });
3859
3808
  if (data) {
@@ -3880,9 +3829,8 @@ function createReviewApi(store, data, open, shared, capabilities = () => ({
3880
3829
  }, result.status);
3881
3830
  return context.json(result);
3882
3831
  });
3883
- app.post("/:id/navigator", reviewAccess("edit"), async (context) => {
3884
- const input = readQuerySchemas.file.extend({
3885
- side: z.enum(["base", "head"]).default("head"),
3832
+ app.post("/:id/navigator", writable, async (context) => {
3833
+ const input = readQuerySchemas.file.omit({ side: true }).extend({
3886
3834
  file: z.string().min(1).optional(),
3887
3835
  empty: z.literal("true").optional()
3888
3836
  }).parse(context.req.query());
@@ -3908,7 +3856,6 @@ function createReviewApi(store, data, open, shared, capabilities = () => ({
3908
3856
  app.get("/:id/maps/:resourceId", async (context) => {
3909
3857
  const query = readQuerySchemas.maps.parse(context.req.query());
3910
3858
  const id = context.req.param("id");
3911
- if (id && isShared(id)) return context.json(sharedData.map(id, z.string().parse(context.req.param("resourceId"))));
3912
3859
  return context.json(await data.map(await data.sourcePins(readReview(id, query.version)), z.string().parse(context.req.param("resourceId"))));
3913
3860
  });
3914
3861
  app.post("/repositories", async (context) => {
@@ -3928,10 +3875,7 @@ function createReviewApi(store, data, open, shared, capabilities = () => ({
3928
3875
  app.get("/:id/resources/:resourceId", async (context) => {
3929
3876
  const id = context.req.param("id");
3930
3877
  const snapshot = readReview(id);
3931
- const resource = id && isShared(id) ? {
3932
- ...await sharedData.resource(id, z.string().parse(context.req.param("resourceId"))),
3933
- repositoryId: snapshot.pins?.repositoryId ?? ""
3934
- } : store.resource(z.string().parse(context.req.param("resourceId")));
3878
+ const resource = store.resource(z.string().parse(context.req.param("resourceId")));
3935
3879
  if (snapshot.pins && resource.repositoryId !== snapshot.pins.repositoryId) throw new ReviewInputError("Resource is outside this repository.", 404);
3936
3880
  return new Response(Buffer.from(resource.data), { headers: {
3937
3881
  "content-type": resource.mimeType,
@@ -3940,14 +3884,13 @@ function createReviewApi(store, data, open, shared, capabilities = () => ({
3940
3884
  });
3941
3885
  app.get("/:id/language-context", async (context) => {
3942
3886
  const input = readQuerySchemas.maps.extend({
3943
- side: z.enum(["base", "head"]).default("head"),
3944
3887
  commit: z.string().optional(),
3945
3888
  repositoryId: z.string().optional(),
3946
3889
  head: z.string().optional(),
3947
3890
  base: z.string().optional()
3948
3891
  }).parse(context.req.query());
3949
3892
  const snapshot = readReview(context.req.param("id"), input.version);
3950
- const environment = await data.languageEnvironment(snapshot, input.side, input.commit, false, queryAnchor(input));
3893
+ const environment = await data.languageEnvironment(snapshot, input.commit, false, queryAnchor(input));
3951
3894
  if (context.req.header("x-review-client") !== "remote") return context.json(environment);
3952
3895
  const { rootPath, ...rest } = environment;
3953
3896
  return context.json({
@@ -3956,7 +3899,7 @@ function createReviewApi(store, data, open, shared, capabilities = () => ({
3956
3899
  serverId: store.serverId()
3957
3900
  });
3958
3901
  });
3959
- app.post("/:id/environment", reviewAccess("active"), async (context) => {
3902
+ app.post("/:id/environment", writable, async (context) => {
3960
3903
  const input = z.strictObject({ retry: z.boolean().optional() }).parse(await readBoundedRequestJson(context.req.raw));
3961
3904
  return context.json({ issues: await data.environmentIssues(readReview(context.req.param("id")), input.retry) });
3962
3905
  });
@@ -3969,7 +3912,7 @@ function createReviewApi(store, data, open, shared, capabilities = () => ({
3969
3912
  readReview(context.req.param("id"));
3970
3913
  return context.json(data.workspaces.list(context.req.param("id")));
3971
3914
  });
3972
- app.post("/:id/workspaces/:workspaceId/retry", reviewAccess("active"), async (context) => {
3915
+ app.post("/:id/workspaces/:workspaceId/retry", writable, async (context) => {
3973
3916
  return context.json(await data.workspaces.retry(context.req.param("id"), context.req.param("workspaceId")));
3974
3917
  });
3975
3918
  app.get("/:id/file", async (context) => {
@@ -4041,7 +3984,7 @@ function createReviewApi(store, data, open, shared, capabilities = () => ({
4041
3984
  return context.json(await data.commits((await data.resolveSource(readReview(context.req.param("id"), input.version))).pins));
4042
3985
  });
4043
3986
  }
4044
- app.post("/:id/copy-context", reviewAccess("read"), async (context) => {
3987
+ app.post("/:id/copy-context", async (context) => {
4045
3988
  const query = readQuerySchemas.get.pick({ version: true }).extend({ mode: legacyModeSchema }).parse(context.req.query());
4046
3989
  const selection = AgentSelectionSchema.parse(await readBoundedRequestJson(context.req.raw));
4047
3990
  return context.json({ text: await selectionContext(context.req.param("id"), selection, query.version) });
@@ -4083,7 +4026,7 @@ function createReviewApi(store, data, open, shared, capabilities = () => ({
4083
4026
  ].join("\n");
4084
4027
  }
4085
4028
  if (ask && data) {
4086
- const projectOf = (reviewId) => isShared(reviewId) || !store.has(reviewId) ? void 0 : store.projectOf(reviewId);
4029
+ const projectOf = (reviewId) => store.has(reviewId) ? store.projectOf(reviewId) : void 0;
4087
4030
  const readThread = (reviewId, threadId) => {
4088
4031
  const thread = ask.threads.get(threadId);
4089
4032
  if (!thread) throw new ReviewInputError("This conversation has ended.", 404);
@@ -4179,7 +4122,7 @@ function createReviewApi(store, data, open, shared, capabilities = () => ({
4179
4122
  const files = fuzzyRank(query, await mentionableFiles(cwd), (file) => [file, file.slice(file.lastIndexOf("/") + 1)]);
4180
4123
  return context.json({ paths: files.slice(0, MENTION_LIMIT) });
4181
4124
  });
4182
- app.post("/:id/ask", reviewAccess("edit"), async (context) => {
4125
+ app.post("/:id/ask", writable, async (context) => {
4183
4126
  const reviewId = context.req.param("id");
4184
4127
  const { version } = readQuerySchemas.get.pick({ version: true }).parse(context.req.query());
4185
4128
  const input = askStartSchema.parse(await readBoundedRequestJson(context.req.raw, ASK_REQUEST_MAX_BYTES));
@@ -4190,7 +4133,7 @@ function createReviewApi(store, data, open, shared, capabilities = () => ({
4190
4133
  const createdAt = (/* @__PURE__ */ new Date()).toISOString();
4191
4134
  const permissions = input.permissions ?? defaultAskPermissions(input.agent);
4192
4135
  const initialContext = await askContext(id, input.agent, snapshot, checkout, input.selection, version);
4193
- assertWritable(reviewId);
4136
+ store.assertWritable(reviewId);
4194
4137
  store.askHistory.markViewed(id, createdAt);
4195
4138
  const thread = ask.threads.open({
4196
4139
  id,
@@ -4308,7 +4251,7 @@ function createReviewApi(store, data, open, shared, capabilities = () => ({
4308
4251
  board: reviewId
4309
4252
  });
4310
4253
  });
4311
- app.post("/:id/ask/watch", reviewAccess("read"), async (context) => {
4254
+ app.post("/:id/ask/watch", async (context) => {
4312
4255
  const reviewId = context.req.param("id");
4313
4256
  readReview(reviewId);
4314
4257
  const { threads } = askWatchSchema.parse(await readBoundedRequestJson(context.req.raw));
@@ -4343,7 +4286,7 @@ function createReviewApi(store, data, open, shared, capabilities = () => ({
4343
4286
  entries: record.entries ?? []
4344
4287
  });
4345
4288
  });
4346
- app.post("/:id/ask/:threadId/open", reviewAccess("edit"), async (context) => {
4289
+ app.post("/:id/ask/:threadId/open", writable, async (context) => {
4347
4290
  const reviewId = context.req.param("id");
4348
4291
  const threadId = context.req.param("threadId");
4349
4292
  if (ask.threads.get(threadId)) return context.json({ threadId });
@@ -4355,7 +4298,7 @@ function createReviewApi(store, data, open, shared, capabilities = () => ({
4355
4298
  const checkout = await data.agentCheckout(snapshot);
4356
4299
  const target = record.selection.target;
4357
4300
  const initialContext = await askContext(record.id, record.agent, snapshot, checkout, record.selection, record.version);
4358
- assertWritable(reviewId);
4301
+ store.assertWritable(reviewId);
4359
4302
  ask.threads.open({
4360
4303
  id: record.id,
4361
4304
  reviewId: anchor,
@@ -4377,7 +4320,7 @@ function createReviewApi(store, data, open, shared, capabilities = () => ({
4377
4320
  });
4378
4321
  return context.json({ threadId: record.id });
4379
4322
  });
4380
- app.post("/:id/ask/:threadId/prompt", reviewAccess("edit"), async (context) => {
4323
+ app.post("/:id/ask/:threadId/prompt", writable, async (context) => {
4381
4324
  const thread = readThread(context.req.param("id"), context.req.param("threadId"));
4382
4325
  const { question } = askFollowUpSchema.parse(await readBoundedRequestJson(context.req.raw, ASK_REQUEST_MAX_BYTES));
4383
4326
  const refusal = thread.askRefusal();
@@ -4385,13 +4328,13 @@ function createReviewApi(store, data, open, shared, capabilities = () => ({
4385
4328
  thread.ask(question);
4386
4329
  return context.json({ ok: true });
4387
4330
  });
4388
- app.post("/:id/ask/:threadId/permission", reviewAccess("edit"), async (context) => {
4331
+ app.post("/:id/ask/:threadId/permission", writable, async (context) => {
4389
4332
  const thread = readThread(context.req.param("id"), context.req.param("threadId"));
4390
4333
  const decision = askDecisionSchema.parse(await readBoundedRequestJson(context.req.raw));
4391
4334
  if (!thread.decide(decision.permissionId, decision.optionId)) throw new ReviewInputError("This request was already answered.", 409);
4392
4335
  return context.json({ ok: true });
4393
4336
  });
4394
- app.post("/:id/ask/:threadId/files", reviewAccess("edit"), async (context) => {
4337
+ app.post("/:id/ask/:threadId/files", writable, async (context) => {
4395
4338
  const thread = readThread(context.req.param("id"), context.req.param("threadId"));
4396
4339
  const { paths } = askFilesSchema.parse(await readBoundedRequestJson(context.req.raw));
4397
4340
  const { cwd, entries } = thread.read();
@@ -4402,7 +4345,7 @@ function createReviewApi(store, data, open, shared, capabilities = () => ({
4402
4345
  file
4403
4346
  })) });
4404
4347
  });
4405
- app.post("/:id/ask/:threadId/choice", reviewAccess("edit"), async (context) => {
4348
+ app.post("/:id/ask/:threadId/choice", writable, async (context) => {
4406
4349
  const thread = readThread(context.req.param("id"), context.req.param("threadId"));
4407
4350
  const { kind, value } = askChoiceSchema.parse(await readBoundedRequestJson(context.req.raw));
4408
4351
  if (thread.read().status !== "idle") throw new ReviewInputError("Settings can change once the agent finishes answering.", 409);
@@ -4413,7 +4356,7 @@ function createReviewApi(store, data, open, shared, capabilities = () => ({
4413
4356
  }
4414
4357
  return context.json({ ok: true });
4415
4358
  });
4416
- app.post("/:id/ask/:threadId/permissions", reviewAccess("edit"), async (context) => {
4359
+ app.post("/:id/ask/:threadId/permissions", writable, async (context) => {
4417
4360
  const thread = readThread(context.req.param("id"), context.req.param("threadId"));
4418
4361
  const { permissions } = askPermitSchema.parse(await readBoundedRequestJson(context.req.raw));
4419
4362
  if (thread.read().status !== "idle") throw new ReviewInputError("Settings can change once the agent finishes answering.", 409);
@@ -4424,13 +4367,13 @@ function createReviewApi(store, data, open, shared, capabilities = () => ({
4424
4367
  }
4425
4368
  return context.json({ ok: true });
4426
4369
  });
4427
- app.post("/:id/ask/:threadId/retry", reviewAccess("edit"), (context) => {
4370
+ app.post("/:id/ask/:threadId/retry", writable, (context) => {
4428
4371
  const thread = readThread(context.req.param("id"), context.req.param("threadId"));
4429
4372
  if (thread.read().status !== "failed") throw new ReviewInputError("Only a conversation that failed can try again.", 409);
4430
4373
  thread.retry();
4431
4374
  return context.json({ ok: true });
4432
4375
  });
4433
- app.post("/:id/ask/:threadId/cancel", reviewAccess("read"), async (context) => {
4376
+ app.post("/:id/ask/:threadId/cancel", async (context) => {
4434
4377
  await readThread(context.req.param("id"), context.req.param("threadId")).cancel();
4435
4378
  return context.json({ ok: true });
4436
4379
  });
@@ -4440,12 +4383,12 @@ function createReviewApi(store, data, open, shared, capabilities = () => ({
4440
4383
  store.askHistory.markViewed(threadId);
4441
4384
  return context.json({ ok: true });
4442
4385
  });
4443
- app.post("/:id/ask/:threadId/close", reviewAccess("read"), (context) => {
4386
+ app.post("/:id/ask/:threadId/close", (context) => {
4444
4387
  readThread(context.req.param("id"), context.req.param("threadId"));
4445
4388
  ask.threads.close(context.req.param("threadId"), !readReview(context.req.param("id")).trashedAt);
4446
4389
  return context.json({ ok: true });
4447
4390
  });
4448
- app.delete("/:id/ask/:threadId", reviewAccess("edit"), (context) => {
4391
+ app.delete("/:id/ask/:threadId", writable, (context) => {
4449
4392
  context.req.param("id");
4450
4393
  const threadId = context.req.param("threadId");
4451
4394
  if (!store.askHistory.get(threadId)) throw new ReviewInputError("This conversation was not found.", 404);
@@ -4456,14 +4399,14 @@ function createReviewApi(store, data, open, shared, capabilities = () => ({
4456
4399
  }
4457
4400
  app.get("/:id/place", (context) => {
4458
4401
  const id = context.req.param("id");
4459
- if (!isShared(id)) store.assertExists(id);
4402
+ store.assertExists(id);
4460
4403
  return context.json(placeOf(id));
4461
4404
  });
4462
4405
  app.get("/:id/stack", async (context) => {
4463
4406
  const query = readQuerySchemas.get.parse(context.req.query());
4464
4407
  const id = context.req.param("id");
4465
4408
  let snapshot = readReview(id, query.version);
4466
- if (isShared(id) || !snapshot.pins) return context.json({ layers: [] });
4409
+ if (!snapshot.pins) return context.json({ layers: [] });
4467
4410
  if (data) snapshot = await data.discoverPullRequest(snapshot);
4468
4411
  const repositoryPath = store.repositories().find((repository) => repository.id === snapshot.pins.repositoryId)?.path;
4469
4412
  const layers = await resolveReviewStackLayers(snapshot, store.list(), { repositoryPath });
@@ -4481,10 +4424,6 @@ function createReviewApi(store, data, open, shared, capabilities = () => ({
4481
4424
  });
4482
4425
  app.get("/:id/project", (context) => {
4483
4426
  const id = context.req.param("id");
4484
- if (isShared(id)) return context.json({
4485
- project: null,
4486
- boards: []
4487
- });
4488
4427
  const project = store.summary(id)?.project ?? null;
4489
4428
  return context.json({
4490
4429
  project,
@@ -4493,15 +4432,6 @@ function createReviewApi(store, data, open, shared, capabilities = () => ({
4493
4432
  });
4494
4433
  app.get("/:id/links", (context) => {
4495
4434
  const id = context.req.param("id");
4496
- if (isShared(id)) return context.json({
4497
- outgoing: boardLinks(id, readReview(id).document).map(({ from, to }) => ({
4498
- from,
4499
- to,
4500
- missing: true,
4501
- elsewhere: true
4502
- })),
4503
- backlinks: []
4504
- });
4505
4435
  const { version } = readQuerySchemas.get.pick({ version: true }).parse({ version: context.req.query("version") });
4506
4436
  const { outgoing, backlinks } = store.links(id, version);
4507
4437
  return context.json({
@@ -4511,7 +4441,6 @@ function createReviewApi(store, data, open, shared, capabilities = () => ({
4511
4441
  });
4512
4442
  app.get("/:id/links/peek", (context) => {
4513
4443
  const id = context.req.param("id");
4514
- if (isShared(id)) return context.json({});
4515
4444
  const { version } = readQuerySchemas.get.pick({ version: true }).parse({ version: context.req.query("version") });
4516
4445
  const board = context.req.query("board");
4517
4446
  const block = context.req.query("block");
@@ -4522,15 +4451,7 @@ function createReviewApi(store, data, open, shared, capabilities = () => ({
4522
4451
  }, version));
4523
4452
  });
4524
4453
  app.get("/:id/history", (context) => {
4525
- const id = context.req.param("id");
4526
- if (!isShared(id)) return context.json(store.history(id));
4527
- const snapshot = readReview(id);
4528
- return context.json([{
4529
- version: snapshot.version,
4530
- title: snapshot.title,
4531
- createdAt: snapshot.createdAt,
4532
- author: "agent"
4533
- }]);
4454
+ return context.json(store.history(context.req.param("id")));
4534
4455
  });
4535
4456
  /** A board's relations as session_list reports them: only those it has. */
4536
4457
  const listedRelations = ({ parentId, children, dependsOn }) => {
@@ -4557,15 +4478,15 @@ function createReviewApi(store, data, open, shared, capabilities = () => ({
4557
4478
  const query = inspectQuerySchema.parse(context.req.query());
4558
4479
  const id = context.req.param("id");
4559
4480
  const snapshot = readReview(id, query.version);
4560
- return context.json(query.format === "text" ? documentText(snapshot, query.targetId, Boolean(query.full), isShared(id) ? void 0 : relatedBoards(id)) : query.targetId !== void 0 ? inspectSnapshot(snapshot, query.targetId) : {
4481
+ return context.json(query.format === "text" ? documentText(snapshot, query.targetId, Boolean(query.full), relatedBoards(id)) : query.targetId !== void 0 ? inspectSnapshot(snapshot, query.targetId) : {
4561
4482
  ...query.full ? snapshot : inspectSnapshot(snapshot),
4562
- ...!isShared(id) && listedRelations(store.relations(id))
4483
+ ...listedRelations(store.relations(id))
4563
4484
  });
4564
4485
  });
4565
4486
  app.get("/:id", async (context) => {
4566
4487
  const query = readQuerySchemas.get.parse(context.req.query());
4567
4488
  let snapshot = { ...readReview(context.req.param("id"), query.version) };
4568
- if (data && query.full && query.version === void 0 && !isShared(snapshot.reviewId)) snapshot = await data.discoverPullRequest(snapshot);
4489
+ if (data && query.full && query.version === void 0) snapshot = await data.discoverPullRequest(snapshot);
4569
4490
  if (data && query.full) try {
4570
4491
  const pins = await data.sourcePins(snapshot);
4571
4492
  if (pins) snapshot.pins = pins;
@@ -4597,35 +4518,16 @@ function createReviewApi(store, data, open, shared, capabilities = () => ({
4597
4518
  const target = "targetId" in edit ? elements(readReview(reviewId).document).find((element) => element.id === edit.targetId) : void 0;
4598
4519
  if (incoming.some((element) => element.type === "animation") || target?.type === "animation") throw new ReviewInputError("Animation authoring is disabled. Enable Animations in Settings under Experimental Features to edit animations. Existing animations can still be viewed.", 409);
4599
4520
  }
4600
- const command = sharedCommandSchema.safeParse(input);
4601
- if (command.success && command.data.operation.reviewId?.startsWith("shared-")) {
4602
- const parsed = commandSchema.parse(input);
4603
- if (shared && parsed.operation.type === "delete") {
4604
- if (!shared.get(parsed.operation.reviewId).snapshot.trashedAt) throw new ReviewInputError("Move this whiteboard to Trash before deleting it.", 409);
4605
- await shared.removeLocal(parsed.operation.reviewId);
4606
- return context.json({
4607
- reviewId: parsed.operation.reviewId,
4608
- version: 0,
4609
- deleted: true
4610
- });
4611
- }
4612
- if (shared && (parsed.operation.type === "attention" || parsed.operation.type === "lifecycle")) {
4613
- if (parsed.operation.type === "lifecycle") await shared.setLifecycle(parsed.operation.reviewId, parsed.operation.state);
4614
- else await shared.setAttention(parsed.operation.reviewId);
4615
- return context.json({
4616
- reviewId: parsed.operation.reviewId,
4617
- version: shared.get(parsed.operation.reviewId).snapshot.version,
4618
- attention: true
4619
- });
4620
- }
4621
- throw new ReviewInputError("Shared reviews are read-only.", 409);
4622
- }
4623
4521
  const threadId = remoteCaller(context) ? void 0 : context.req.header("x-review-thread")?.trim() || void 0;
4624
4522
  const author = !remoteCaller(context) && context.req.header("x-review-person") !== void 0 ? { kind: "person" } : {
4625
4523
  kind: "agent",
4626
4524
  ...threadId && { threadId }
4627
4525
  };
4526
+ const copy = input.operation.type === "delete" && store.has(input.operation.reviewId) ? store.read(input.operation.reviewId) : void 0;
4628
4527
  const result = await store.execute(input, void 0, author);
4528
+ const key = copy?.shared?.key;
4529
+ const kept = copy && boardRepositoryId(copy);
4530
+ if (result.deleted && key && kept) await Promise.resolve().then(() => forgetSharedCommits(store.repositoryPath(kept), key)).catch((error) => console.error("Could not drop a copy's kept commits:", error));
4629
4531
  if (input.operation.type === "edit" && data && !result.deleted) {
4630
4532
  const { quotes, unquoted } = await anchorQuotes(result.version > 0 ? store.read(result.reviewId, result.version - 1) : void 0, store.read(result.reviewId, result.version), async (pins, side, file) => (await data.file(pins, side, file)).text);
4631
4533
  return context.json({
@@ -4947,7 +4849,7 @@ function answerErrorsAsJson(app) {
4947
4849
  });
4948
4850
  }
4949
4851
  function createWhiteboardCore(input) {
4950
- const { store, data, shared } = input.profile;
4852
+ const { store, data } = input.profile;
4951
4853
  const app = createReviewServerApp({
4952
4854
  token: input.token,
4953
4855
  instanceId: input.instanceId,
@@ -4964,22 +4866,20 @@ function createWhiteboardCore(input) {
4964
4866
  takesMcp: async (agent) => askAgentTakesMcp(agent, await ask.env),
4965
4867
  ...ask.tools
4966
4868
  });
4967
- const closeDeletedThreads = () => askThreads?.closeDeletedReviews((id) => id.startsWith("shared-") ? !shared?.has(id) || Boolean(shared.get(id).snapshot.trashedAt) : !store.has(id) || Boolean(store.read(id).trashedAt));
4869
+ const closeDeletedThreads = () => askThreads?.closeDeletedReviews((id) => !store.has(id) || Boolean(store.read(id).trashedAt));
4968
4870
  const stopLocal = askThreads && store.subscribe((result) => {
4969
4871
  if (result.deleted || result.lifecycle) closeDeletedThreads();
4970
4872
  });
4971
- const stopShared = askThreads && shared?.subscribe(closeDeletedThreads);
4972
- const api = createReviewApi(store, data, callbacks.open, shared, callbacks.capabilities, () => traceMachineEnabled(), input.status, input.hooks, askThreads && {
4873
+ const api = createReviewApi(store, data, callbacks.open, input.sharing, callbacks.capabilities, () => traceMachineEnabled(), input.status, input.hooks, askThreads && {
4973
4874
  threads: askThreads,
4974
4875
  agents: async () => detectAskAgents(await ask?.env)
4975
4876
  }, input.animations);
4976
- if (!shared) mountSharingPublisher(api, store, data);
4877
+ if (!input.sharing) mountSharingPublisher(api, store, data);
4977
4878
  return {
4978
4879
  app,
4979
4880
  api,
4980
4881
  close: () => {
4981
4882
  stopLocal?.();
4982
- stopShared?.();
4983
4883
  closeDeletedThreads();
4984
4884
  askThreads?.closeAll();
4985
4885
  }