@dev.fast/whiteboard 0.2.5-preview.20261015.116 → 0.2.5-preview.20261015.117

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 (51) hide show
  1. package/dist/{account-alias-ChZR3RIU.js → account-alias-3-HnWkjz.js} +1 -1
  2. package/dist/{agent-cli-CR2PnA9G.js → agent-cli-BabaNr6w.js} +6 -6
  3. package/dist/agent-cli-CGL0kFJI.js +2 -0
  4. package/dist/{agent-client-DVd-prfp.js → agent-client-DHKowVF7.js} +3 -3
  5. package/dist/{authoring-tools-BXKEwhfi.js → authoring-tools-CuGm_pVC.js} +2 -2
  6. package/dist/{background-server-Cw7CcfaZ.js → background-server-3SrIevG5.js} +2 -2
  7. package/dist/{background-server-D2Rrr_e2.js → background-server-xlTnBsup.js} +1 -1
  8. package/dist/build-info.json +1 -1
  9. package/dist/{cli-CSrz_wGk.js → cli-C8eB7Ot9.js} +1 -1
  10. package/dist/{cli-runner-PlGqBeMw.js → cli-runner-VEEG16zJ.js} +19 -19
  11. package/dist/cli.js +2 -2
  12. package/dist/{client-DhZzPePs.js → client-BBBwu0P7.js} +2 -2
  13. package/dist/{headless-host-Cp37C-v0.js → headless-host-CEaaG0YT.js} +1 -1
  14. package/dist/{headless-host-CVXTSIlx.js → headless-host-DI82imwx.js} +17 -11
  15. package/dist/{local-data-yfLp5PiJ.js → local-data-DcnL0cBy.js} +85 -15
  16. package/dist/{mcp-DBbDwG4y.js → mcp-Cjs0MnoL.js} +3 -3
  17. package/dist/profile-BIwE4tC6.js +2 -0
  18. package/dist/{profile-pAzg7fxC.js → profile-BoWYDne6.js} +1 -1
  19. package/dist/{remote-attach-BAt2eZx3.js → remote-attach-xGlaS772.js} +3 -3
  20. package/dist/{remote-extensions-2Cxf3hMo.js → remote-extensions-ByiuSl1D.js} +1 -1
  21. package/dist/{remote-extensions-BiNx6NQK.js → remote-extensions-D_4DSOhO.js} +6 -18
  22. package/dist/{remote-language-server-DQgWUNeV.js → remote-language-server-Chz8NQvC.js} +6 -8
  23. package/dist/{remote-uninstall-DLtDG0o7.js → remote-uninstall-DGdIgpEg.js} +4 -4
  24. package/dist/{request-origin-DaRIV3cB.js → request-origin-DjOEwBAK.js} +1 -1
  25. package/dist/{review-app-launcher-D7I4DGoH.js → review-app-launcher-BkNRvyip.js} +2 -2
  26. package/dist/{review-telemetry-BqB4CiTD.js → review-telemetry-ycbk-YeT.js} +2 -2
  27. package/dist/server/desktop-host.js +13 -15
  28. package/dist/{server-discovery-CifQ3yQS.js → server-discovery-BY_MN7tj.js} +1 -1
  29. package/dist/sharing/index.d.ts +11 -5
  30. package/dist/sharing/index.js +1 -1
  31. package/dist/{src-Bgvf1sEN.js → src-B8adXKNt.js} +0 -1
  32. package/dist/{stored-document-migration-CFOvjRwW.js → stored-document-migration-CVMl7Te5.js} +15 -3
  33. package/dist/{tool-failure-BXWzQG8T.js → tool-failure--Jih7UeH.js} +1 -1
  34. package/package.json +6 -5
  35. package/remote/install.sh +444 -0
  36. package/src/agent-selection.ts +11 -5
  37. package/src/cli-runner.ts +3 -3
  38. package/src/pinned-download.ts +4 -17
  39. package/src/remote-extensions.ts +4 -3
  40. package/src/remote-language-server.ts +3 -9
  41. package/src/review-api/document.ts +24 -1
  42. package/src/review-api/http.ts +1 -3
  43. package/src/review-api/local-data.ts +17 -8
  44. package/src/review-api/store.ts +91 -4
  45. package/src/review-api/version-cache.ts +36 -0
  46. package/src/server/hono-http.ts +16 -1
  47. package/src/server/review-gateway-hosts.ts +1 -7
  48. package/src/server/review-gateway.ts +2 -1
  49. package/src/vsix.ts +1 -12
  50. package/dist/agent-cli-CRPPmS3l.js +0 -2
  51. package/dist/profile-BSOkA6A-.js +0 -2
@@ -1,4 +1,4 @@
1
- import { w as ReviewCliInstallStampSchema } from "./src-Bgvf1sEN.js";
1
+ import { w as ReviewCliInstallStampSchema } from "./src-B8adXKNt.js";
2
2
  import { $ as removeAgentTraceHook, C as disableTraceMachine, E as traceMachineStatus, G as listTraceRepositoryRoots, K as traceRepositoryStatus, Q as installHarnessHooks, S as configureTraceMachine, T as traceMachineEnabled, U as disableAllTraceRepositories, W as enableTraceRepository, Y as AGENT_TRACE_HOOK_AGENTS, Z as describeTraceHookOwners, ft as StoreClient, n as reviewDesktopStateDir, rt as traceScope, st as readStoreAuth } from "./review-home-paths-zwevIq4I.js";
3
3
  import { A as withFileLock, F as writeFileAtomicAsync, I as writePrivateJsonAtomic, M as devReviewHome } from "./s3-config-DDWWMh5x.js";
4
4
  import { i as readReviewPackageVersion } from "./package-paths-BsuI4WUf.js";
@@ -1,11 +1,11 @@
1
1
  import { n as isJsonObject, u as parseJsonText } from "./src-X9phtB2j.js";
2
2
  import { T as traceMachineEnabled } from "./review-home-paths-zwevIq4I.js";
3
- import { a as toolResultText, i as connectReviewInstance, s as ReviewApiError } from "./agent-client-DVd-prfp.js";
3
+ import { a as toolResultText, i as connectReviewInstance, s as ReviewApiError } from "./agent-client-DHKowVF7.js";
4
4
  import { M as devReviewHome } from "./s3-config-DDWWMh5x.js";
5
- import { b as selectReviewInstance, u as REVIEW_INSTANCE_ENV } from "./server-discovery-CifQ3yQS.js";
6
- import { n as reviewSessionAgent } from "./review-telemetry-BqB4CiTD.js";
7
- import { n as REVIEW_VIA_HEADER, t as REVIEW_AGENT_HEADER } from "./request-origin-DaRIV3cB.js";
8
- import { i as publicTool, n as RECOVERY, r as callPublicTool, t as toolFailure } from "./tool-failure-BXWzQG8T.js";
5
+ import { b as selectReviewInstance, u as REVIEW_INSTANCE_ENV } from "./server-discovery-BY_MN7tj.js";
6
+ import { n as reviewSessionAgent } from "./review-telemetry-ycbk-YeT.js";
7
+ import { n as REVIEW_VIA_HEADER, t as REVIEW_AGENT_HEADER } from "./request-origin-DjOEwBAK.js";
8
+ import { i as publicTool, n as RECOVERY, r as callPublicTool, t as toolFailure } from "./tool-failure--Jih7UeH.js";
9
9
  //#region src/review-api/agent-cli.ts
10
10
  const reviewAgentCliHelp = "whiteboard api tools (one line per tool)\nwhiteboard api tools <tool-name> (its description and input schema)\nwhiteboard api <tool-name> '<json>'\nwhiteboard api <tool-name> - (read JSON from stdin)\nwhiteboard mcp (stdio MCP adapter; Whiteboard Desktop or whiteboard server start must be running)\nSelect headless state with DEV_REVIEW_SERVER_DIR or whiteboard --state-dir <path> api/mcp.\n";
11
11
  async function runReviewAgentCli(input) {
@@ -20,7 +20,7 @@ async function runReviewAgentCli(input) {
20
20
  if (extra.length || mode === "mcp" && name) throw new Error("Unexpected arguments. Use whiteboard api --help.");
21
21
  const headers = Object.fromEntries([[REVIEW_VIA_HEADER, mode === "mcp" ? "mcp" : "api"], [REVIEW_AGENT_HEADER, reviewSessionAgent(env)]]);
22
22
  if (mode === "mcp") {
23
- const { serveReviewMcp } = await import("./mcp-DBbDwG4y.js");
23
+ const { serveReviewMcp } = await import("./mcp-Cjs0MnoL.js");
24
24
  await serveReviewMcp(async (key, agentKind) => {
25
25
  const connected = await connectReviewInstance(key ? {
26
26
  ...env,
@@ -0,0 +1,2 @@
1
+ import { n as runReviewAgentCli } from "./agent-cli-BabaNr6w.js";
2
+ export { runReviewAgentCli };
@@ -1,7 +1,7 @@
1
1
  import { d as isBooleanValue, f as isNumberValue, m as isStringValue, u as parseJsonText } from "./src-X9phtB2j.js";
2
- import "./src-Bgvf1sEN.js";
2
+ import "./src-B8adXKNt.js";
3
3
  import { t as ReviewInputError } from "./input-error-OLgB_h31.js";
4
- import { b as selectReviewInstance, c as reviewServerStateDir, d as ReviewInstanceUnavailableError, i as readReviewServerDiscovery, l as serverNotReady, m as healthyReviewInstance, s as reviewServerIsHealthy, y as reviewInstanceUnavailable } from "./server-discovery-CifQ3yQS.js";
4
+ import { b as selectReviewInstance, c as reviewServerStateDir, d as ReviewInstanceUnavailableError, i as readReviewServerDiscovery, l as serverNotReady, m as healthyReviewInstance, s as reviewServerIsHealthy, y as reviewInstanceUnavailable } from "./server-discovery-BY_MN7tj.js";
5
5
  //#region ../review-protocol/src/review-api-client.ts
6
6
  const defaultRequest = (url, init) => fetch(url, init);
7
7
  /** A non-2xx reply; the status tells a caller whether retrying can help. */
@@ -250,7 +250,7 @@ async function connectReviewInstance(env = process.env, headers = {}, options =
250
250
  const selection = await selectReviewInstance({ env });
251
251
  const discovery = healthyReviewInstance(selection);
252
252
  if (!discovery) {
253
- const { ensureServerWithoutDesktop } = await import("./background-server-D2Rrr_e2.js");
253
+ const { ensureServerWithoutDesktop } = await import("./background-server-xlTnBsup.js");
254
254
  const server = await ensureServerWithoutDesktop({
255
255
  selection,
256
256
  env,
@@ -1,5 +1,5 @@
1
- import { _ as activityUpdateSchema, g as activityEndSchema, h as activityBeginSchema, i as commandSchema, n as uploadSchema } from "./local-data-yfLp5PiJ.js";
2
- import { x as publishedEditSchema, y as pathTargetSchema } from "./stored-document-migration-CFOvjRwW.js";
1
+ import { _ as activityUpdateSchema, g as activityEndSchema, h as activityBeginSchema, i as commandSchema, n as uploadSchema } from "./local-data-DcnL0cBy.js";
2
+ import { S as publishedEditSchema, y as pathTargetSchema } from "./stored-document-migration-CVMl7Te5.js";
3
3
  import { t as ReviewInputError } from "./input-error-OLgB_h31.js";
4
4
  import { t as findReviewPackageRoot } from "./package-paths-BsuI4WUf.js";
5
5
  import { z } from "zod";
@@ -1,8 +1,8 @@
1
1
  import "./review-home-paths-zwevIq4I.js";
2
2
  import { D as liveLockOwner, O as processIsAlive } from "./s3-config-DDWWMh5x.js";
3
3
  import { t as findReviewPackageRoot } from "./package-paths-BsuI4WUf.js";
4
- import { a as readReviewServerHealth, c as reviewServerStateDir, i as readReviewServerDiscovery, n as desktopOwnsHome, p as desktopUsesHome, r as headlessServerLockPath } from "./server-discovery-CifQ3yQS.js";
5
- import { t as desktopApplicationInstalled } from "./review-app-launcher-D7I4DGoH.js";
4
+ import { a as readReviewServerHealth, c as reviewServerStateDir, i as readReviewServerDiscovery, n as desktopOwnsHome, p as desktopUsesHome, r as headlessServerLockPath } from "./server-discovery-BY_MN7tj.js";
5
+ import { t as desktopApplicationInstalled } from "./review-app-launcher-BkNRvyip.js";
6
6
  import { mkdir, open, readFile, realpath, stat } from "node:fs/promises";
7
7
  import path from "node:path";
8
8
  import { spawn } from "node:child_process";
@@ -1,2 +1,2 @@
1
- import { a as headlessServerOwner, c as stopBackgroundServer, i as ensureServerWithoutDesktop, r as ensureBackgroundServer } from "./background-server-Cw7CcfaZ.js";
1
+ import { a as headlessServerOwner, c as stopBackgroundServer, i as ensureServerWithoutDesktop, r as ensureBackgroundServer } from "./background-server-3SrIevG5.js";
2
2
  export { ensureBackgroundServer, ensureServerWithoutDesktop, headlessServerOwner, stopBackgroundServer };
@@ -1 +1 @@
1
- {"version":"0.2.5-preview.20261015.116","commit":"5f769d5d5d8db0ee9b2a6ef0746c6dd2b5a322a0","dirty":true,"builtAt":"2026-10-15T00:57:30.329Z"}
1
+ {"version":"0.2.5-preview.20261015.117","commit":"b93a4fc67175685fab7275af8c2d80577d50513b","dirty":true,"builtAt":"2026-10-15T14:00:53.987Z"}
@@ -1,4 +1,4 @@
1
- import { r as connectReviewApi } from "./agent-client-DVd-prfp.js";
1
+ import { r as connectReviewApi } from "./agent-client-DHKowVF7.js";
2
2
  import { randomUUID } from "node:crypto";
3
3
  import { z } from "zod";
4
4
  //#region src/sharing/cli.ts
@@ -1,19 +1,19 @@
1
1
  import { C as setLocalVcsCommandObserver, E as __toESM, T as __require, w as __commonJSMin } from "./src-BMPuxWv4.js";
2
2
  import { c as jsonString, n as isJsonObject, u as parseJsonText } from "./src-X9phtB2j.js";
3
- import { _ as REVIEW_REMOTE_AGENT_IDS, p as REVIEW_CLI_INSTALL_TARGET_LABELS, v as REVIEW_REMOTE_ATTACH_BEGIN, y as REVIEW_REMOTE_ATTACH_END } from "./src-Bgvf1sEN.js";
3
+ import { _ as REVIEW_REMOTE_AGENT_IDS, p as REVIEW_CLI_INSTALL_TARGET_LABELS, v as REVIEW_REMOTE_ATTACH_BEGIN, y as REVIEW_REMOTE_ATTACH_END } from "./src-B8adXKNt.js";
4
4
  import { A as runTraceDeny, E as traceMachineStatus, F as describeSelection, H as resolveTraceRepositoryTarget, J as HOSTED_CAPTURE_SCOPE_DESCRIPTION, K as traceRepositoryStatus, L as loadS3TraceStorage, M as runTraceSessions, N as runTraceStoreDelete, P as runTraceStoreInfo, T as traceMachineEnabled, V as requireTraceConsent, X as agentTraceHomeDirectory, _ as runTraceRepair, _t as jsonRequestedInArgv, b as runTraceGitHook, c as registerTraceCommands, ct as runStoreLogin, d as runTraceList$1, dt as StoreApiError, et as resolveTraceCommand, f as runTracePull$1, ft as StoreClient, g as runTraceInstallMachine, gt as humanStream, h as runTraceEnable, ht as failWithJsonError, it as DEFAULT_STORE_ORIGIN, j as runTraceOnboard, k as runTraceAllow, l as resolveTraceReadStorage, lt as runStoreLogout, m as runTraceDisable, mt as emitJsonEvent, nt as traceHomeDir, p as runTraceShow, pt as withStoreAuthorization, q as readTraceUserConfig, rt as traceScope, st as readStoreAuth, u as runTraceBlame, ut as runStoreWhoami, v as runTraceStatus, w as readLegacyCaptureSettings, x as runTraceHook, y as runTraceSync, z as selectTraceStorage } from "./review-home-paths-zwevIq4I.js";
5
- import { o as ReviewApiClient, s as ReviewApiError } from "./agent-client-DVd-prfp.js";
5
+ import { o as ReviewApiClient, s as ReviewApiError } from "./agent-client-DHKowVF7.js";
6
6
  import { t as resolveReviewRoot } from "./runtime-CKiWhGrf.js";
7
7
  import { C as s3ProfileSchema, E as writeTraceConfigFile, M as devReviewHome, N as normalizeStoreOrigin, S as readTraceConfigFile, T as sameS3Profile, _ as emptyTraceConfig, c as resolveS3Setup, g as currentStore, h as TraceConfigurationError, i as isS3MockMode, n as clearTraceEnvCache, o as readTraceEnvFile, p as traceSettingsPath, w as s3Store } from "./s3-config-DDWWMh5x.js";
8
8
  import { t as errorMessage } from "./error-message-OtiDonty.js";
9
9
  import { i as readReviewPackageVersion, r as readBuildInfo, t as findReviewPackageRoot } from "./package-paths-BsuI4WUf.js";
10
- import { a as readReviewServerHealth, b as selectReviewInstance, c as reviewServerStateDir, d as ReviewInstanceUnavailableError, f as clearDefaultReviewInstance, g as requireHealthyReviewDesktop, h as readDefaultReviewInstance, i as readReviewServerDiscovery, l as serverNotReady, m as healthyReviewInstance, p as desktopUsesHome, t as DESKTOP_TAKEOVER_SIGNAL, u as REVIEW_INSTANCE_ENV, x as writeDefaultReviewInstance, y as reviewInstanceUnavailable } from "./server-discovery-CifQ3yQS.js";
10
+ import { a as readReviewServerHealth, b as selectReviewInstance, c as reviewServerStateDir, d as ReviewInstanceUnavailableError, f as clearDefaultReviewInstance, g as requireHealthyReviewDesktop, h as readDefaultReviewInstance, i as readReviewServerDiscovery, l as serverNotReady, m as healthyReviewInstance, p as desktopUsesHome, t as DESKTOP_TAKEOVER_SIGNAL, u as REVIEW_INSTANCE_ENV, x as writeDefaultReviewInstance, y as reviewInstanceUnavailable } from "./server-discovery-BY_MN7tj.js";
11
11
  import { t as isDirectory } from "./fs-utils-BMPLt0cr.js";
12
- import { t as ReviewTelemetry } from "./review-telemetry-BqB4CiTD.js";
13
- import { _ as connectPrompts, a as isOwnedShim, f as windowsInstallerCommand, g as CONNECT_COMMANDS, h as isInstallTarget, m as ALL_INSTALL_TARGETS, o as pathShimPath, p as scanLegacySkills, t as aliasInstallationToAccount } from "./account-alias-ChZR3RIU.js";
12
+ import { t as ReviewTelemetry } from "./review-telemetry-ycbk-YeT.js";
13
+ import { _ as connectPrompts, a as isOwnedShim, f as windowsInstallerCommand, g as CONNECT_COMMANDS, h as isInstallTarget, m as ALL_INSTALL_TARGETS, o as pathShimPath, p as scanLegacySkills, t as aliasInstallationToAccount } from "./account-alias-3-HnWkjz.js";
14
14
  import { t as fuzzyRank } from "./fuzzy-match-BoAmcyak.js";
15
- import { n as focusReviewDesktop, r as runReviewAppLaunch } from "./review-app-launcher-D7I4DGoH.js";
16
- import { t as reviewAgentCliHelp } from "./agent-cli-CR2PnA9G.js";
15
+ import { n as focusReviewDesktop, r as runReviewAppLaunch } from "./review-app-launcher-BkNRvyip.js";
16
+ import { t as reviewAgentCliHelp } from "./agent-cli-BabaNr6w.js";
17
17
  import { z } from "zod";
18
18
  import { constants, existsSync, mkdirSync, renameSync, writeFileSync } from "node:fs";
19
19
  import { access, readFile, realpath } from "node:fs/promises";
@@ -4451,7 +4451,7 @@ async function runReviewCli(input) {
4451
4451
  if (!Number.isInteger(port) || port < 0 || port > 65535) throw new ReviewCliUsageError("--port must be an integer between 0 and 65535.");
4452
4452
  const stateDir = reviewServerStateDir(authoringEnv(options.stateDir));
4453
4453
  if (options.detach) {
4454
- const { ensureBackgroundServer } = await import("./background-server-D2Rrr_e2.js");
4454
+ const { ensureBackgroundServer } = await import("./background-server-xlTnBsup.js");
4455
4455
  const { discovery, started } = await ensureBackgroundServer({
4456
4456
  stateDir,
4457
4457
  env,
@@ -4473,7 +4473,7 @@ async function runReviewCli(input) {
4473
4473
  process.once("SIGINT", stop);
4474
4474
  process.once("SIGTERM", stop);
4475
4475
  process.once(DESKTOP_TAKEOVER_SIGNAL, takeover);
4476
- const { HeadlessServerBusyError, runHeadlessServer } = await import("./headless-host-Cp37C-v0.js");
4476
+ const { HeadlessServerBusyError, runHeadlessServer } = await import("./headless-host-CEaaG0YT.js");
4477
4477
  try {
4478
4478
  await runHeadlessServer({
4479
4479
  stateDir,
@@ -4510,7 +4510,7 @@ async function runReviewCli(input) {
4510
4510
  configureJsonOutput(serverCommand.command("stop").description("Stop a background server started by --detach, the CLI, or Desktop").option("--state-dir <path>", "directory selected when starting the server"), "plain").action(async (_options, command) => {
4511
4511
  const options = command.optsWithGlobals();
4512
4512
  const stateDir = reviewServerStateDir(authoringEnv(options.stateDir));
4513
- const { headlessServerOwner, stopBackgroundServer } = await import("./background-server-D2Rrr_e2.js");
4513
+ const { headlessServerOwner, stopBackgroundServer } = await import("./background-server-xlTnBsup.js");
4514
4514
  const owner = await headlessServerOwner(stateDir);
4515
4515
  const discovery = await readReviewServerDiscovery(stateDir);
4516
4516
  if (owner === void 0) {
@@ -4538,7 +4538,7 @@ async function runReviewCli(input) {
4538
4538
  if (!existsSync(path.join(stateDir, "review-api.db"))) throw new Error(`No saved reviews in ${stateDir}, so there is no server id to reset. Check --state-dir.`);
4539
4539
  const inUse = `A Whiteboard server is using ${stateDir}. Stop it first, then run whiteboard server reset-id again.`;
4540
4540
  if (await desktopUsesHome(stateDir, env)) return refuse(inUse, options.json);
4541
- const [{ openReviewProfile }, { withHeadlessServerLock }] = await Promise.all([import("./profile-BSOkA6A-.js"), import("./headless-host-Cp37C-v0.js")]);
4541
+ const [{ openReviewProfile }, { withHeadlessServerLock }] = await Promise.all([import("./profile-BIwE4tC6.js"), import("./headless-host-CEaaG0YT.js")]);
4542
4542
  const reset = await withHeadlessServerLock(await realpath(stateDir), async () => {
4543
4543
  const local = await openReviewProfile(stateDir, { manageWorkspaces: false });
4544
4544
  try {
@@ -4560,7 +4560,7 @@ async function runReviewCli(input) {
4560
4560
  configureJsonOutput(remote.command("attach").description("Start a background server if none is healthy and report how to reach it").option("--state-dir <path>", "directory for saved reviews and server discovery").option("--groups <groups>", "comma-separated optional extension groups the Desktop has enabled, such as go").option("--replace", "stop a running server of another version that the CLI or Desktop started, and start this one"), "plain").action(async (_options, command) => {
4561
4561
  const options = command.optsWithGlobals();
4562
4562
  const stateDir = reviewServerStateDir(authoringEnv(options.stateDir));
4563
- const { remoteAttach } = await import("./remote-attach-BAt2eZx3.js");
4563
+ const { remoteAttach } = await import("./remote-attach-xGlaS772.js");
4564
4564
  let attach;
4565
4565
  try {
4566
4566
  attach = await remoteAttach({
@@ -4583,11 +4583,11 @@ async function runReviewCli(input) {
4583
4583
  state.exitCode = 1;
4584
4584
  return;
4585
4585
  }
4586
- input.stdout.write(options.json ? `${REVIEW_REMOTE_ATTACH_BEGIN}\n${JSON.stringify(attach)}\n${REVIEW_REMOTE_ATTACH_END}\n` : `Whiteboard server ${attach.startedServer ? "started" : "already running"} at ${attach.url}\nLanguage features: ${attach.languageServer ? `VS Code server on port ${attach.languageServer.port}` : `unavailable (${attach.languageServerDetail})`}\n`);
4586
+ input.stdout.write(options.json ? `${REVIEW_REMOTE_ATTACH_BEGIN}\n${JSON.stringify(attach)}\n${REVIEW_REMOTE_ATTACH_END}\n` : `Whiteboard server ${attach.startedServer ? "started" : "already running"} at ${attach.url}\nLanguage features: ${attach.languageServer ? `language server on port ${attach.languageServer.port}` : `unavailable (${attach.languageServerDetail})`}\n`);
4587
4587
  });
4588
- configureJsonOutput(remote.command("extensions").description("Language extensions for the VS Code server on a remote").command("ensure").description("Download the curated extensions for this machine and list them for the VS Code server").option("--groups <groups>", "comma-separated optional extension groups the Desktop has enabled, such as go"), "plain").action(async (_options, command) => {
4588
+ configureJsonOutput(remote.command("extensions").description("Language extensions for the language server on a remote").command("ensure").description("Download the curated extensions for this machine and list them for the language server").option("--groups <groups>", "comma-separated optional extension groups the Desktop has enabled, such as go"), "plain").action(async (_options, command) => {
4589
4589
  const { json, groups } = command.optsWithGlobals();
4590
- const { ensureRemoteExtensions } = await import("./remote-extensions-2Cxf3hMo.js");
4590
+ const { ensureRemoteExtensions } = await import("./remote-extensions-ByiuSl1D.js");
4591
4591
  const result = await ensureRemoteExtensions({
4592
4592
  env,
4593
4593
  groups: groups?.split(",").map((group) => group.trim())
@@ -4602,7 +4602,7 @@ async function runReviewCli(input) {
4602
4602
  configureJsonOutput(remote.command("uninstall").description("Remove what Whiteboard Desktop installed on this host, stopping a server it started").addOption(new Option("--keep-reviews", "leave the saved reviews in place").conflicts("deleteReviews")).addOption(new Option("--delete-reviews", "also delete the saved reviews")).option("--state-dir <path>", "directory for saved reviews and server discovery"), "plain").action(async (_options, command) => {
4603
4603
  const options = command.optsWithGlobals();
4604
4604
  const stateDir = reviewServerStateDir(authoringEnv(options.stateDir));
4605
- const { remoteUninstall } = await import("./remote-uninstall-DLtDG0o7.js");
4605
+ const { remoteUninstall } = await import("./remote-uninstall-DGdIgpEg.js");
4606
4606
  let deleteReviews = options.deleteReviews ? true : options.keepReviews ? false : void 0;
4607
4607
  if (deleteReviews === void 0 && !options.json && input.stdin?.isTTY) {
4608
4608
  const prompt = createInterface$1({
@@ -4810,14 +4810,14 @@ async function runReviewCli(input) {
4810
4810
  command.showHelpAfterError(false).error("Markdown review migration is no longer supported. Ask your agent to migrate your old Whiteboard reviews.");
4811
4811
  });
4812
4812
  configureJsonOutput(configureJsonOutput(program.command("share").description("Upload an immutable review snapshot and return its share link"), "plain").option("--review <id>", "review ID").option("--version <number>", "Saved version to share").option("--preview", "Open the share link in Whiteboard Preview by default").option("--request-id <uuid>", "Reuse this ID when retrying the same immutable upload").action(async (options) => {
4813
- const { runShareCli } = await import("./cli-CSrz_wGk.js");
4813
+ const { runShareCli } = await import("./cli-C8eB7Ot9.js");
4814
4814
  state.exitCode = await runShareCli({
4815
4815
  ...input,
4816
4816
  ...options,
4817
4817
  env: authoringEnv()
4818
4818
  });
4819
4819
  }).command("revoke <share-id>").description("Revoke future downloads of a share"), "plain").action(async (shareId, options) => {
4820
- const { runShareCli } = await import("./cli-CSrz_wGk.js");
4820
+ const { runShareCli } = await import("./cli-C8eB7Ot9.js");
4821
4821
  state.exitCode = await runShareCli({
4822
4822
  ...input,
4823
4823
  ...options,
@@ -4898,7 +4898,7 @@ async function runReviewCli(input) {
4898
4898
  return commands;
4899
4899
  } });
4900
4900
  for (const [name, description] of [["api", "Call a JSON review authoring tool on the running server"], ["mcp", "Serve the JSON review authoring tools over stdio MCP"]]) configureOutput(program.command(name).description(description).argument("[args...]", "tool name and JSON input").allowUnknownOption().allowExcessArguments().helpOption(false).passThroughOptions().addHelpText("after", `\n${reviewAgentCliHelp}`), "plain").action(async (args) => {
4901
- const { runReviewAgentCli } = await import("./agent-cli-CRPPmS3l.js");
4901
+ const { runReviewAgentCli } = await import("./agent-cli-CGL0kFJI.js");
4902
4902
  state.exitCode = await runReviewAgentCli({
4903
4903
  ...input,
4904
4904
  env: authoringEnv(),
package/dist/cli.js CHANGED
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
  import { a as jsonNumber, c as jsonString, o as jsonObject, u as parseJsonText } from "./src-X9phtB2j.js";
3
- import "./src-Bgvf1sEN.js";
3
+ import "./src-B8adXKNt.js";
4
4
  import { existsSync, readFileSync, readdirSync, realpathSync } from "node:fs";
5
5
  import path from "node:path";
6
6
  import os from "node:os";
@@ -17,7 +17,7 @@ async function runCli(effectivePath) {
17
17
  `);
18
18
  return 1;
19
19
  }
20
- const { runReviewCli } = await import("./cli-runner-PlGqBeMw.js");
20
+ const { runReviewCli } = await import("./cli-runner-VEEG16zJ.js");
21
21
  return runReviewCli({
22
22
  argv,
23
23
  cliPaths: {
@@ -1,7 +1,7 @@
1
1
  import { f as gitAt } from "./src-BMPuxWv4.js";
2
2
  import { n as isJsonObject, u as parseJsonText } from "./src-X9phtB2j.js";
3
- import "./src-Bgvf1sEN.js";
4
- import { B as parseMarkdown, C as resourceReferences, E as sourceReferences, K as sourceAnchors, M as lensSchema, N as lensSelections, O as checkReferences, S as resourceReference, _ as hasCodeReferences, b as pinsSchema, c as traceSchema, h as elements, l as textIncludesQuote, n as migrateStoredDocument, o as normalizedSoftwareElementSchema, p as documentSchema, r as migrateStoredLenses, s as normalizedSoftwareRelationshipSchema, t as liftFileLenses, u as anchorPins, z as markdownNodes } from "./stored-document-migration-CFOvjRwW.js";
3
+ import "./src-B8adXKNt.js";
4
+ import { B as markdownNodes, C as resourceReference, D as sourceReferences, N as lensSchema, P as lensSelections, V as parseMarkdown, _ as hasCodeReferences, c as traceSchema, h as elements, k as checkReferences, l as textIncludesQuote, n as migrateStoredDocument, o as normalizedSoftwareElementSchema, p as documentSchema, q as sourceAnchors, r as migrateStoredLenses, s as normalizedSoftwareRelationshipSchema, t as liftFileLenses, u as anchorPins, w as resourceReferences, x as pinsSchema } from "./stored-document-migration-CVMl7Te5.js";
5
5
  import { t as ReviewInputError } from "./input-error-OLgB_h31.js";
6
6
  import { createHash, randomUUID } from "node:crypto";
7
7
  import { z } from "zod";
@@ -1,2 +1,2 @@
1
- import { i as withHeadlessServerLock, n as runHeadlessServer, r as takeOverServerHome, t as HeadlessServerBusyError } from "./headless-host-CVXTSIlx.js";
1
+ import { i as withHeadlessServerLock, n as runHeadlessServer, r as takeOverServerHome, t as HeadlessServerBusyError } from "./headless-host-DI82imwx.js";
2
2
  export { HeadlessServerBusyError, runHeadlessServer, takeOverServerHome, withHeadlessServerLock };
@@ -1,20 +1,20 @@
1
1
  import { d as git, r as defaultBranch } from "./src-BMPuxWv4.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 { a as sharedReviewId, c as verifyShareRepository, d as StreamLimitError, f as readBoundedStream, l as exportShare, m as shareIdSchema, n as ShareClient, p as parseShareLink, r as SharePreflightError, s as readShareRepository, t as ShareAuthError, u as validateShareSources } from "./client-DhZzPePs.js";
4
- import { a as inspectSnapshot, b as diffrMissingError, c as askAgentIds, d as askPicksSchema, f as askQuestionSchema, i as commandSchema, l as askChoiceKinds, m as selectionMarkdown, p as AgentSelectionSchema, r as SCRATCHPAD_ID, s as applyAskChange, u as askPermissionsSchema, v as invalidateStructuralComparisons, y as diffrExecutable } from "./local-data-yfLp5PiJ.js";
5
- import { A as reviewDiffrSummarizerInputSchema, D as ReviewOnboardingRunRequestSchema, a as parseReviewVerbRequest, g as REVIEW_HOST_HEADER, i as parseReviewDesktopVerbResult, j as decodeStructuralDiffEvent } from "./src-Bgvf1sEN.js";
3
+ import { a as sharedReviewId, c as verifyShareRepository, d as StreamLimitError, f as readBoundedStream, l as exportShare, m as shareIdSchema, n as ShareClient, p as parseShareLink, r as SharePreflightError, s as readShareRepository, t as ShareAuthError, u as validateShareSources } from "./client-BBBwu0P7.js";
4
+ import { a as inspectSnapshot, b as diffrMissingError, c as askAgentIds, d as askPicksSchema, f as askQuestionSchema, i as commandSchema, l as askChoiceKinds, m as selectionMarkdown, p as AgentSelectionSchema, r as SCRATCHPAD_ID, s as applyAskChange, u as askPermissionsSchema, v as invalidateStructuralComparisons, y as diffrExecutable } from "./local-data-DcnL0cBy.js";
5
+ import { A as reviewDiffrSummarizerInputSchema, D as ReviewOnboardingRunRequestSchema, a as parseReviewVerbRequest, g as REVIEW_HOST_HEADER, i as parseReviewDesktopVerbResult, j as decodeStructuralDiffEvent } from "./src-B8adXKNt.js";
6
6
  import { B as TraceStorageDeniedError, D as listReviewTraceSessions, I as isTraceStorageConfigured, O as loadReviewAgentTrace, R as resolveTraceStorage, T as traceMachineEnabled, at as clearStoreAuth, ct as runStoreLogin, it as DEFAULT_STORE_ORIGIN, ot as openUrlInBrowser, st as readStoreAuth, tt as shellQuote, z as selectTraceStorage } from "./review-home-paths-zwevIq4I.js";
7
- import { $ as unionIntervals, G as selectionKey, H as comparisonKey, I as fileLineRangeSchema, J as coverageSources, N as lensSelections, Q as subtractIntervals, T as selectionReferences, U as formatAnchor, V as anchorSelection, W as resolveDiffSelection, Y as emptyCoverage, Z as scopedCoverage, h as elements, k as UNCATEGORIZED_LENS_ID, u as anchorPins } from "./stored-document-migration-CFOvjRwW.js";
7
+ import { $ as subtractIntervals, A as UNCATEGORIZED_LENS_ID, E as selectionReferences, G as resolveDiffSelection, H as anchorSelection, K as selectionKey, L as fileLineRangeSchema, P as lensSelections, Q as scopedCoverage, U as comparisonKey, W as formatAnchor, X as emptyCoverage, Y as coverageSources, et as unionIntervals, h as elements, u as anchorPins } from "./stored-document-migration-CVMl7Te5.js";
8
8
  import { t as ReviewInputError } from "./input-error-OLgB_h31.js";
9
9
  import { A as withFileLock, I as writePrivateJsonAtomic, M as devReviewHome, h as TraceConfigurationError, i as isS3MockMode } from "./s3-config-DDWWMh5x.js";
10
10
  import { t as errorMessage } from "./error-message-OtiDonty.js";
11
11
  import { i as readReviewPackageVersion, n as readBuildCommit, t as findReviewPackageRoot } from "./package-paths-BsuI4WUf.js";
12
- import { i as readReviewServerDiscovery, n as desktopOwnsHome, o as reviewServerDiscoveryPath, p as desktopUsesHome, r as headlessServerLockPath, t as DESKTOP_TAKEOVER_SIGNAL } from "./server-discovery-CifQ3yQS.js";
13
- import { t as openReviewProfile } from "./profile-pAzg7fxC.js";
14
- import { a as REVIEW_APP_SESSION_ID_HEADER, c as sanitizeUiTelemetryEvent, i as BUNDLE_FRAME_PATTERN, l as cleanTelemetryText, r as reviewRequestOrigin, s as isReportableCleanedMessage } from "./request-origin-DaRIV3cB.js";
15
- import { a as scratchpadAvailable, c as readQuerySchemas, i as renderInstructions, o as inspectQuerySchema, r as instructionsQuerySchema, s as queryAnchor, t as authoringTools } from "./authoring-tools-BXKEwhfi.js";
12
+ import { i as readReviewServerDiscovery, n as desktopOwnsHome, o as reviewServerDiscoveryPath, p as desktopUsesHome, r as headlessServerLockPath, t as DESKTOP_TAKEOVER_SIGNAL } from "./server-discovery-BY_MN7tj.js";
13
+ import { t as openReviewProfile } from "./profile-BoWYDne6.js";
14
+ import { a as REVIEW_APP_SESSION_ID_HEADER, c as sanitizeUiTelemetryEvent, i as BUNDLE_FRAME_PATTERN, l as cleanTelemetryText, r as reviewRequestOrigin, s as isReportableCleanedMessage } from "./request-origin-DjOEwBAK.js";
15
+ import { a as scratchpadAvailable, c as readQuerySchemas, i as renderInstructions, o as inspectQuerySchema, r as instructionsQuerySchema, s as queryAnchor, t as authoringTools } from "./authoring-tools-CuGm_pVC.js";
16
16
  import { t as fuzzyRank } from "./fuzzy-match-BoAmcyak.js";
17
- import { a as headlessServerOwner, c as stopBackgroundServer } from "./background-server-Cw7CcfaZ.js";
17
+ import { a as headlessServerOwner, c as stopBackgroundServer } from "./background-server-3SrIevG5.js";
18
18
  import { createRequire } from "node:module";
19
19
  import crypto$1, { createHash, randomBytes, randomUUID } from "node:crypto";
20
20
  import { z } from "zod";
@@ -2146,8 +2146,14 @@ function applyCorsHeaders(request, response) {
2146
2146
  response.headers.set("access-control-expose-headers", REVIEW_HOST_HEADER);
2147
2147
  return response;
2148
2148
  }
2149
+ /** The allowed origin, headers and methods hold for the server's lifetime, so
2150
+ * the client may reuse a preflight. Chromium otherwise keeps one for 5 s, and
2151
+ * nearly every request paid a second round trip. Two hours is Chromium's cap. */
2152
+ const PREFLIGHT_MAX_AGE_SECONDS = 7200;
2149
2153
  function corsPreflightResponse(request) {
2150
- return applyCorsHeaders(request, new Response(null, { status: 204 }));
2154
+ const response = applyCorsHeaders(request, new Response(null, { status: 204 }));
2155
+ response.headers.set("access-control-max-age", String(PREFLIGHT_MAX_AGE_SECONDS));
2156
+ return response;
2151
2157
  }
2152
2158
  function isAuthorizedRequest(request, expectedToken) {
2153
2159
  const supplied = request.headers.get("x-review-token") ?? new URL(request.url).searchParams.get("token");
@@ -4262,7 +4268,7 @@ function createReviewApi(store, data, open, shared, capabilities = () => ({
4262
4268
  head: `b/${diff.newPath}`
4263
4269
  } : void 0);
4264
4270
  return [
4265
- target.kind === "review" ? `Whiteboard: ${snapshot.title}` : `Selected ${target.kind === "text" ? "text" : "code"} from Whiteboard: ${snapshot.title}`,
4271
+ `Whiteboard: ${snapshot.title}`,
4266
4272
  `Session ID: ${snapshot.reviewId}`,
4267
4273
  `Version: ${snapshot.version}`,
4268
4274
  ...selection.apiSource?.commit ? [`Selected commit: ${selection.apiSource.commit}`] : [],
@@ -1,8 +1,8 @@
1
1
  import { _ as parseGitRemote, a as diff, b as resolveRepoContextSync, c as diffFileSummariesWorkingTree, d as git, g as listTrackedFilesAtCommit, h as listCommitRange, i as detectLocalVcs, l as diffTrees, m as jjRevisionIsConflicted, n as createBlobBatchReader, p as gitCommonDir, s as diffFileSummariesTrees, t as LocalVcsToolsMissingError, u as diffWorkingTree, v as readFileAtCommit, x as resolveRevision, y as resolveRepoContext } from "./src-BMPuxWv4.js";
2
2
  import { c as jsonString, o as jsonObject, u as parseJsonText } from "./src-X9phtB2j.js";
3
- import { C as ReviewApiSelectionSourceSchema, M as structuralChangeCounts, O as ReviewSelectedDiffSchema, j as decodeStructuralDiffEvent, k as SCRATCHPAD_REVIEW_ID } from "./src-Bgvf1sEN.js";
3
+ import { C as ReviewApiSelectionSourceSchema, M as structuralChangeCounts, O as ReviewSelectedDiffSchema, j as decodeStructuralDiffEvent, k as SCRATCHPAD_REVIEW_ID } from "./src-B8adXKNt.js";
4
4
  import { s as safeStorageSegment } from "./review-home-paths-zwevIq4I.js";
5
- import { $ as unionIntervals, A as applyLensEdit, C as resourceReferences, D as summarizeEdit, E as sourceReferences, F as checkSourcePath, I as fileLineRangeSchema, K as sourceAnchors, L as requireVisibleSource, N as lensSelections, O as checkReferences, P as SourceRangeError, Q as subtractIntervals, R as sliceSourceRange, S as resourceReference, X as intersectIntervals, Y as emptyCoverage, a as defineSoftwareMap, b as pinsSchema, c as traceSchema, d as applyEdit, et as updateCoverage, f as assignFreshIds, g as explicitPins, h as elements, i as SoftwareModelValidationError, j as lensEditSchema, l as textIncludesQuote, m as editSchema, n as migrateStoredDocument, p as documentSchema, q as coverageSchema, r as migrateStoredLenses, t as liftFileLenses, u as anchorPins, v as isUnit, w as reviewTargetSchema } from "./stored-document-migration-CFOvjRwW.js";
5
+ import { $ as subtractIntervals, C as resourceReference, D as sourceReferences, F as SourceRangeError, I as checkSourcePath, J as coverageSchema, L as fileLineRangeSchema, M as lensEditSchema, O as summarizeEdit, P as lensSelections, R as requireVisibleSource, T as reviewTargetSchema, X as emptyCoverage, Z as intersectIntervals, a as defineSoftwareMap, b as pinnedRepositoryIds, c as traceSchema, d as applyEdit, et as unionIntervals, f as assignFreshIds, g as explicitPins, h as elements, i as SoftwareModelValidationError, j as applyLensEdit, k as checkReferences, l as textIncludesQuote, m as editSchema, n as migrateStoredDocument, p as documentSchema, q as sourceAnchors, r as migrateStoredLenses, t as liftFileLenses, tt as updateCoverage, u as anchorPins, v as isUnit, w as resourceReferences, x as pinsSchema, z as sliceSourceRange } from "./stored-document-migration-CVMl7Te5.js";
6
6
  import { t as ReviewInputError } from "./input-error-OLgB_h31.js";
7
7
  import { A as withFileLock, I as writePrivateJsonAtomic, O as processIsAlive } from "./s3-config-DDWWMh5x.js";
8
8
  import { t as errorMessage } from "./error-message-OtiDonty.js";
@@ -1698,14 +1698,14 @@ const AgentSelectionSchema = z.strictObject({
1698
1698
  });
1699
1699
  function selectionMarkdown(selection, sourceExcerpt = "", diffPaths) {
1700
1700
  const target = selection.target;
1701
- if (selection.selectedDiff) return selectedDiffMarkdown(selection.selectedDiff, diffPaths);
1701
+ if (selection.selectedDiff) return `Selected diff:\n${selectedDiffMarkdown(selection.selectedDiff, diffPaths)}`;
1702
1702
  if (target.kind === "review") return "";
1703
- if (target.kind === "text") return target.quote.split(/\r?\n/).map((line) => `> ${line}`).join("\n");
1704
- return [
1703
+ if (target.kind === "text") return `Selected text:\n${target.quote.split(/\r?\n/).map((line) => `> ${line}`).join("\n")}`;
1704
+ return `Selected code:\n${[
1705
1705
  `## ${selection.title}`,
1706
1706
  selection.detail ?? "",
1707
1707
  sourceExcerpt
1708
- ].filter(Boolean).join("\n\n");
1708
+ ].filter(Boolean).join("\n\n")}`;
1709
1709
  }
1710
1710
  function selectedDiffMarkdown(diff, paths) {
1711
1711
  const oldCount = diff.rows.filter((row) => row.kind !== "added").length;
@@ -2376,16 +2376,63 @@ var ReviewStore = class {
2376
2376
  };
2377
2377
  }
2378
2378
  refreshPending;
2379
+ /** What the last refresh saw of each review. A live worktree's snapshot is
2380
+ * kept, since every pass projects it again. */
2381
+ refreshed = /* @__PURE__ */ new Map();
2382
+ availableRepositories;
2383
+ /** Reviews whose source may have moved since the last pass: every live
2384
+ * worktree, and any other review that is new, edited, restored from trash or
2385
+ * reads from a repository that appeared or went away. */
2386
+ *reviewsToRefresh() {
2387
+ const available = new Set(this.repositories().filter((repository) => existsSync(repository.path)).map((repository) => repository.id));
2388
+ const previous = this.availableRepositories;
2389
+ this.availableRepositories = available;
2390
+ const flipped = (id) => !previous || previous.has(id) !== available.has(id);
2391
+ const rows = this.db.prepare("SELECT reviews.id, reviews.version, review_attention.deleted_at FROM reviews LEFT JOIN review_attention ON review_attention.review_id=reviews.id").all();
2392
+ const present = /* @__PURE__ */ new Set();
2393
+ for (const row of rows) {
2394
+ const reviewId = String(row.id);
2395
+ const version = Number(row.version);
2396
+ const state = `${version}\0${row.deleted_at ?? ""}`;
2397
+ present.add(reviewId);
2398
+ let seen = this.refreshed.get(reviewId);
2399
+ if (seen?.state === state) {
2400
+ if (seen.live) yield seen.live;
2401
+ else if ([...seen.repositories].some(flipped)) yield this.read(reviewId, version);
2402
+ continue;
2403
+ }
2404
+ const snapshot = row.deleted_at ? void 0 : this.read(reviewId, version);
2405
+ if (!snapshot) {
2406
+ this.refreshed.set(reviewId, {
2407
+ state,
2408
+ repositories: /* @__PURE__ */ new Set()
2409
+ });
2410
+ continue;
2411
+ }
2412
+ const live = snapshot.target?.kind === "worktree";
2413
+ seen = {
2414
+ state,
2415
+ repositories: live ? /* @__PURE__ */ new Set() : /* @__PURE__ */ new Set([...snapshot.pins ? [snapshot.pins.repositoryId] : [], ...pinnedRepositoryIds(snapshot.document)]),
2416
+ live: live ? snapshot : void 0
2417
+ };
2418
+ this.refreshed.set(reviewId, seen);
2419
+ yield snapshot;
2420
+ }
2421
+ for (const reviewId of this.refreshed.keys()) if (!present.has(reviewId)) this.refreshed.delete(reviewId);
2422
+ }
2379
2423
  /** Refresh source state without writing authored document versions. Serialized with edits. */
2380
2424
  refreshWorktrees() {
2381
2425
  if (this.closing || !this.providers.resolveTarget) return Promise.resolve();
2382
2426
  if (this.refreshPending) return this.refreshPending;
2383
2427
  const run = this.pending.then(async () => {
2384
- for (const summary of this.list()) {
2385
- if (summary.trashedAt) continue;
2386
- const snapshot = this.read(summary.reviewId, summary.version);
2428
+ let slice = performance.now();
2429
+ for (const snapshot of this.reviewsToRefresh()) {
2430
+ if (performance.now() - slice > 16) {
2431
+ await new Promise((resolve) => setImmediate(resolve));
2432
+ slice = performance.now();
2433
+ }
2387
2434
  try {
2388
- const live = this.liveSources.get(summary.reviewId);
2435
+ const live = this.liveSources.get(snapshot.reviewId);
2389
2436
  const last = live?.version === snapshot.version ? live : snapshot;
2390
2437
  const previous = last.pins;
2391
2438
  const projected = await this.projectLiveSource(snapshot, last);
@@ -3139,6 +3186,27 @@ function inspectSnapshot(snapshot, targetId) {
3139
3186
  }));
3140
3187
  }
3141
3188
  //#endregion
3189
+ //#region src/review-api/version-cache.ts
3190
+ /** A stored version never changes, so work derived from it runs once. Keeps
3191
+ * the `capacity` most recently used results. */
3192
+ var VersionCache = class {
3193
+ derive;
3194
+ options;
3195
+ results = /* @__PURE__ */ new Map();
3196
+ constructor(derive, options) {
3197
+ this.derive = derive;
3198
+ this.options = options;
3199
+ }
3200
+ get(input) {
3201
+ const key = this.options.key?.(input) ?? `${input.reviewId}\0${input.version}`;
3202
+ const result = this.results.get(key) ?? this.derive(input);
3203
+ this.results.delete(key);
3204
+ if (this.results.size >= this.options.capacity) this.results.delete(this.results.keys().next().value);
3205
+ this.results.set(key, result);
3206
+ return result;
3207
+ }
3208
+ };
3209
+ //#endregion
3142
3210
  //#region src/review-api/workspaces.ts
3143
3211
  const OWNED_ELSEWHERE = "Another Whiteboard server is managing this review's language workspaces. Retry once it stops.";
3144
3212
  /** Local lifecycle only: source and authored history never depend on preparation.
@@ -3942,18 +4010,20 @@ var LocalReviewData = class {
3942
4010
  if (!existsSync(this.store.repositoryPath(anchor.repositoryId))) throw unavailableCheckout();
3943
4011
  return anchorPins({ pins: anchor }, void 0);
3944
4012
  }
3945
- /** References whose own repository is unregistered or gone from disk. */
4013
+ /** References whose own repository is unregistered or gone from disk.
4014
+ * `snapshot` is a stored version. */
3946
4015
  async unavailableAnchors(snapshot) {
3947
4016
  const missing = /* @__PURE__ */ new Set();
3948
- for (const pins of explicitPins(sourceReferences(snapshot.document, { tolerant: true }))) try {
3949
- await this.anchorSourcePins(pins);
4017
+ for (const repositoryId of pinnedRepositoryIds(snapshot.document)) try {
4018
+ if (!existsSync(this.store.repositoryPath(repositoryId))) missing.add(repositoryId);
3950
4019
  } catch (error) {
3951
4020
  if (!(error instanceof ReviewInputError)) throw error;
3952
- missing.add(pins.repositoryId);
4021
+ missing.add(repositoryId);
3953
4022
  }
3954
4023
  if (!missing.size) return [];
3955
- return sourceReferences(snapshot.document, { tolerant: true }).filter((ref) => ref.source.pins && missing.has(ref.source.pins.repositoryId)).map((ref) => ref.id);
4024
+ return this.storedReferences.get(snapshot).filter((ref) => ref.source.pins && missing.has(ref.source.pins.repositoryId)).map((ref) => ref.id);
3956
4025
  }
4026
+ storedReferences = new VersionCache((snapshot) => sourceReferences(snapshot.document, { tolerant: true }), { capacity: 512 });
3957
4027
  trackedFiles = /* @__PURE__ */ new Map();
3958
4028
  repositories = /* @__PURE__ */ new Map();
3959
4029
  readers = /* @__PURE__ */ new Map();
@@ -1,7 +1,7 @@
1
- import { a as toolResultText, s as ReviewApiError } from "./agent-client-DVd-prfp.js";
1
+ import { a as toolResultText, s as ReviewApiError } from "./agent-client-DHKowVF7.js";
2
2
  import { t as ReviewInputError } from "./input-error-OLgB_h31.js";
3
- import { n as REVIEW_STATUS_TOOL, t as authoringTools } from "./authoring-tools-BXKEwhfi.js";
4
- import { i as publicTool, n as RECOVERY, r as callPublicTool, t as toolFailure } from "./tool-failure-BXWzQG8T.js";
3
+ import { n as REVIEW_STATUS_TOOL, t as authoringTools } from "./authoring-tools-CuGm_pVC.js";
4
+ import { i as publicTool, n as RECOVERY, r as callPublicTool, t as toolFailure } from "./tool-failure--Jih7UeH.js";
5
5
  import { Server } from "@modelcontextprotocol/sdk/server/index.js";
6
6
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
7
7
  import { CallToolRequestSchema, ListToolsRequestSchema } from "@modelcontextprotocol/sdk/types.js";
@@ -0,0 +1,2 @@
1
+ import { t as openReviewProfile } from "./profile-BoWYDne6.js";
2
+ export { openReviewProfile };
@@ -1,4 +1,4 @@
1
- import { o as initializeReviewStoreSchema, t as openLocalReviewStore } from "./local-data-yfLp5PiJ.js";
1
+ import { o as initializeReviewStoreSchema, t as openLocalReviewStore } from "./local-data-DcnL0cBy.js";
2
2
  import "./review-home-paths-zwevIq4I.js";
3
3
  import { A as withFileLock } from "./s3-config-DDWWMh5x.js";
4
4
  import { access, mkdir } from "node:fs/promises";
@@ -1,7 +1,7 @@
1
1
  import { i as readReviewPackageVersion } from "./package-paths-BsuI4WUf.js";
2
- import { a as readReviewServerHealth, l as serverNotReady } from "./server-discovery-CifQ3yQS.js";
3
- import { c as stopBackgroundServer, o as recordedBackgroundServer, r as ensureBackgroundServer } from "./background-server-Cw7CcfaZ.js";
4
- import { t as ensureRemoteLanguageServer } from "./remote-language-server-DQgWUNeV.js";
2
+ import { a as readReviewServerHealth, l as serverNotReady } from "./server-discovery-BY_MN7tj.js";
3
+ import { c as stopBackgroundServer, o as recordedBackgroundServer, r as ensureBackgroundServer } from "./background-server-3SrIevG5.js";
4
+ import { t as ensureRemoteLanguageServer } from "./remote-language-server-Chz8NQvC.js";
5
5
  import path from "node:path";
6
6
  import { spawn } from "node:child_process";
7
7
  import { gt, valid } from "semver";
@@ -1,2 +1,2 @@
1
- import { t as ensureRemoteExtensions } from "./remote-extensions-BiNx6NQK.js";
1
+ import { t as ensureRemoteExtensions } from "./remote-extensions-D_4DSOhO.js";
2
2
  export { ensureRemoteExtensions };
@@ -15,7 +15,7 @@ import yauzl from "yauzl";
15
15
  //#region src/pinned-download.ts
16
16
  const DOWNLOAD_TIMEOUT_MS = 12e4;
17
17
  /** Streams `url` into `file` through a temporary file and returns its sha256. */
18
- async function downloadFile(url, file, { size, timeoutMs = DOWNLOAD_TIMEOUT_MS, signal } = {}) {
18
+ async function downloadFile(url, file, { timeoutMs = DOWNLOAD_TIMEOUT_MS, signal } = {}) {
19
19
  const { host } = new URL(url);
20
20
  const partial = `${file}.${process.pid}.part`;
21
21
  let response;
@@ -27,13 +27,9 @@ async function downloadFile(url, file, { size, timeoutMs = DOWNLOAD_TIMEOUT_MS,
27
27
  }
28
28
  if (!response.ok || !response.body) throw new Error(`${host} answered ${response.status} for ${url}`);
29
29
  const hash = createHash("sha256");
30
- let received = 0;
31
- const tooLarge = /* @__PURE__ */ new Error(`${url} is larger than its pinned ${size} bytes`);
32
30
  try {
33
31
  await pipeline(Readable.fromWeb(response.body), async function* (chunks) {
34
32
  for await (const chunk of chunks) {
35
- received += chunk.length;
36
- if (size !== void 0 && received > size) throw tooLarge;
37
33
  hash.update(chunk);
38
34
  yield chunk;
39
35
  }
@@ -41,7 +37,6 @@ async function downloadFile(url, file, { size, timeoutMs = DOWNLOAD_TIMEOUT_MS,
41
37
  await rename(partial, file);
42
38
  } catch (error) {
43
39
  await rm(partial, { force: true });
44
- if (error === tooLarge) throw error;
45
40
  throw new Error(`Network error downloading from ${host}: ${error instanceof Error ? networkCause(error) : String(error)}`);
46
41
  }
47
42
  return hash.digest("hex");
@@ -49,10 +44,7 @@ async function downloadFile(url, file, { size, timeoutMs = DOWNLOAD_TIMEOUT_MS,
49
44
  /** Leaves a matching `file` in place; otherwise downloads it and checks the pin. */
50
45
  async function downloadPinned(pin, file, options = {}) {
51
46
  if (await sha256File(file).catch(() => void 0) === pin.sha256) return;
52
- const actual = await downloadFile(pin.url, file, {
53
- ...options,
54
- size: pin.size
55
- });
47
+ const actual = await downloadFile(pin.url, file, options);
56
48
  if (actual !== pin.sha256) {
57
49
  await rm(file, { force: true });
58
50
  throw new Error(`Checksum mismatch for ${pin.url}: expected ${pin.sha256}, got ${actual}. The download was deleted.`);
@@ -73,8 +65,7 @@ const manifestSchema = z.looseObject({
73
65
  engines: z.looseObject({ vscode: z.string().optional() }).optional()
74
66
  });
75
67
  /** Unpacks a VSIX's `extension/` payload into `destination`. */
76
- async function extractVsix(vsix, destination, maxBytes = Number.POSITIVE_INFINITY) {
77
- let unpacked = 0;
68
+ async function extractVsix(vsix, destination) {
78
69
  const zip = await new Promise((resolve, reject) => yauzl.open(vsix, { lazyEntries: true }, (error, opened) => error ? reject(error) : resolve(opened)));
79
70
  const openReadStream = (entry) => new Promise((resolve, reject) => zip.openReadStream(entry, (error, stream) => error ? reject(error) : resolve(stream)));
80
71
  try {
@@ -90,8 +81,6 @@ async function extractVsix(vsix, destination, maxBytes = Number.POSITIVE_INFINIT
90
81
  const mode = entry.externalFileAttributes >>> 16 & 65535;
91
82
  if (parts.some((part) => part === ".." || part === "." || part === "" || part.includes(":")) || path.posix.isAbsolute(relative)) throw new Error(`VSIX contains an unsafe path: ${name}`);
92
83
  if ((mode & 61440) === 40960) throw new Error(`VSIX contains a symlink: ${name}`);
93
- unpacked += entry.uncompressedSize;
94
- if (unpacked > maxBytes) throw new Error(`VSIX unpacks to more than ${maxBytes} bytes`);
95
84
  const output = path.join(destination, ...parts);
96
85
  await mkdir(path.dirname(output), { recursive: true });
97
86
  await pipeline(await openReadStream(entry), createWriteStream(output));
@@ -127,8 +116,7 @@ const STAMP_FILE = ".curated.json";
127
116
  const curatedDownloadSchema = z.object({
128
117
  universal: z.boolean(),
129
118
  url: z.string(),
130
- sha256: z.string(),
131
- size: z.number()
119
+ sha256: z.string()
132
120
  });
133
121
  const curatedExtensionSchema = z.object({
134
122
  id: z.string(),
@@ -256,7 +244,7 @@ function requestedGroups(curated, groups, failed) {
256
244
  async function readPackagedCurated() {
257
245
  const file = path.join(findReviewPackageRoot(import.meta.url), "vscode-server", "curated.json");
258
246
  const text = await readFile(file, "utf8").catch(() => {
259
- throw new Error(`This package has no VS Code server (${file} is missing).`);
247
+ throw new Error(`This Whiteboard server has no language server (${file} is missing).`);
260
248
  });
261
249
  return z.object({ extensions: z.array(curatedExtensionSchema) }).parse(JSON.parse(text)).extensions;
262
250
  }
@@ -278,7 +266,7 @@ async function install(extension, download, directory, stamp, timeoutMs, signal)
278
266
  recursive: true,
279
267
  force: true
280
268
  });
281
- await extractVsix(part, staging, download.size * 8);
269
+ await extractVsix(part, staging);
282
270
  await sanitizeVsixManifest(staging, extension);
283
271
  await checkExecutables(staging, extension);
284
272
  await writeFile(path.join(staging, STAMP_FILE), `${JSON.stringify(stamp)}\n`);