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

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 (54) hide show
  1. package/dist/{account-alias-ChZR3RIU.js → account-alias-C1ixP6bt.js} +1 -1
  2. package/dist/agent-cli-CGapivA1.js +2 -0
  3. package/dist/{agent-cli-CR2PnA9G.js → agent-cli-CPSnWc9V.js} +6 -6
  4. package/dist/{agent-client-DVd-prfp.js → agent-client-Dogh8I83.js} +3 -3
  5. package/dist/{authoring-tools-BXKEwhfi.js → authoring-tools-CtsslOEf.js} +2 -2
  6. package/dist/{background-server-D2Rrr_e2.js → background-server-BKR_lpYb.js} +1 -1
  7. package/dist/{background-server-Cw7CcfaZ.js → background-server-DJT0CnYH.js} +2 -2
  8. package/dist/build-info.json +1 -1
  9. package/dist/{cli-CSrz_wGk.js → cli-Df6p-C19.js} +1 -1
  10. package/dist/{cli-runner-PlGqBeMw.js → cli-runner-ZktCwlLk.js} +19 -19
  11. package/dist/cli.js +2 -2
  12. package/dist/{client-DhZzPePs.js → client-_ltX1mah.js} +2 -2
  13. package/dist/{headless-host-Cp37C-v0.js → headless-host-Pj2xfGAo.js} +1 -1
  14. package/dist/{headless-host-CVXTSIlx.js → headless-host-ZNZ_6ZRo.js} +17 -11
  15. package/dist/{local-data-yfLp5PiJ.js → local-data-D_9S--yI.js} +188 -18
  16. package/dist/{mcp-DBbDwG4y.js → mcp-Blb7rzGw.js} +3 -3
  17. package/dist/profile-BUoTujrK.js +2 -0
  18. package/dist/{profile-pAzg7fxC.js → profile-CKSB6UDi.js} +1 -1
  19. package/dist/{remote-attach-BAt2eZx3.js → remote-attach-CiOwonJ3.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-BQjBk81F.js} +6 -8
  23. package/dist/{remote-uninstall-DLtDG0o7.js → remote-uninstall-pzHb-Hfd.js} +4 -4
  24. package/dist/{request-origin-DaRIV3cB.js → request-origin-C-V9f3Fr.js} +1 -1
  25. package/dist/{review-app-launcher-D7I4DGoH.js → review-app-launcher-S2FZBY4W.js} +2 -2
  26. package/dist/{review-telemetry-BqB4CiTD.js → review-telemetry-9ceqh4wz.js} +2 -2
  27. package/dist/server/desktop-host.js +13 -15
  28. package/dist/{server-discovery-CifQ3yQS.js → server-discovery-CqRxbsER.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-HnIPjtjx.js} +2 -2
  32. package/dist/{stored-document-migration-CFOvjRwW.js → stored-document-migration-DenDEYdI.js} +15 -3
  33. package/dist/{tool-failure-BXWzQG8T.js → tool-failure-aSYcHDxP.js} +1 -1
  34. package/package.json +5 -4
  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/azure-devops-provider.ts +165 -0
  42. package/src/review-api/document.ts +24 -1
  43. package/src/review-api/http.ts +1 -3
  44. package/src/review-api/local-data.ts +19 -10
  45. package/src/review-api/origin.ts +1 -1
  46. package/src/review-api/provider.ts +2 -1
  47. package/src/review-api/store.ts +91 -4
  48. package/src/review-api/version-cache.ts +36 -0
  49. package/src/server/hono-http.ts +16 -1
  50. package/src/server/review-gateway-hosts.ts +1 -7
  51. package/src/server/review-gateway.ts +2 -1
  52. package/src/vsix.ts +1 -12
  53. package/dist/agent-cli-CRPPmS3l.js +0 -2
  54. 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-HnIPjtjx.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";
@@ -0,0 +1,2 @@
1
+ import { n as runReviewAgentCli } from "./agent-cli-CPSnWc9V.js";
2
+ export { runReviewAgentCli };
@@ -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-Dogh8I83.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-CqRxbsER.js";
6
+ import { n as reviewSessionAgent } from "./review-telemetry-9ceqh4wz.js";
7
+ import { n as REVIEW_VIA_HEADER, t as REVIEW_AGENT_HEADER } from "./request-origin-C-V9f3Fr.js";
8
+ import { i as publicTool, n as RECOVERY, r as callPublicTool, t as toolFailure } from "./tool-failure-aSYcHDxP.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-Blb7rzGw.js");
24
24
  await serveReviewMcp(async (key, agentKind) => {
25
25
  const connected = await connectReviewInstance(key ? {
26
26
  ...env,
@@ -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-HnIPjtjx.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-CqRxbsER.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-BKR_lpYb.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-D_9S--yI.js";
2
+ import { S as publishedEditSchema, y as pathTargetSchema } from "./stored-document-migration-DenDEYdI.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,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-DJT0CnYH.js";
2
2
  export { ensureBackgroundServer, ensureServerWithoutDesktop, headlessServerOwner, stopBackgroundServer };
@@ -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-CqRxbsER.js";
5
+ import { t as desktopApplicationInstalled } from "./review-app-launcher-S2FZBY4W.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 +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.118","commit":"dc3ea1aee29b6ccf8860a763db26bc86ca52532c","dirty":true,"builtAt":"2026-10-15T17:51:10.990Z"}
@@ -1,4 +1,4 @@
1
- import { r as connectReviewApi } from "./agent-client-DVd-prfp.js";
1
+ import { r as connectReviewApi } from "./agent-client-Dogh8I83.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-HnIPjtjx.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-Dogh8I83.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-CqRxbsER.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-9ceqh4wz.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-C1ixP6bt.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-S2FZBY4W.js";
16
+ import { t as reviewAgentCliHelp } from "./agent-cli-CPSnWc9V.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-BKR_lpYb.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-Pj2xfGAo.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-BKR_lpYb.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-BUoTujrK.js"), import("./headless-host-Pj2xfGAo.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-CiOwonJ3.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-pzHb-Hfd.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-Df6p-C19.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-Df6p-C19.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-CGapivA1.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-HnIPjtjx.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-ZktCwlLk.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-HnIPjtjx.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-DenDEYdI.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-ZNZ_6ZRo.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-_ltX1mah.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-D_9S--yI.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-HnIPjtjx.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-DenDEYdI.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-CqRxbsER.js";
13
+ import { t as openReviewProfile } from "./profile-CKSB6UDi.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-C-V9f3Fr.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-CtsslOEf.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-DJT0CnYH.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-HnIPjtjx.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-DenDEYdI.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";
@@ -1388,6 +1388,106 @@ function firstLine(text) {
1388
1388
  return text.split("\n")[0].slice(0, 300);
1389
1389
  }
1390
1390
  //#endregion
1391
+ //#region src/review-api/azure-devops-provider.ts
1392
+ const AZ_TIMEOUT_MS = 3e4;
1393
+ const PULL_REQUEST_URLS = [/^https:\/\/dev\.azure\.com\/([A-Za-z0-9-]+)\/([^/]+)\/_git\/([^/]+)\/pullrequest\/([1-9]\d*)$/, /^https:\/\/([A-Za-z0-9-]+)\.visualstudio\.com\/(?:DefaultCollection\/)?([^/]+)\/_git\/([^/]+)\/pullrequest\/([1-9]\d*)$/];
1394
+ const REMOTE_URLS = [
1395
+ /^https:\/\/(?:[^@]+@)?dev\.azure\.com\/([^/]+)\/([^/]+)\/_git\/([^/]+?)(?:\.git)?\/?$/,
1396
+ /^https:\/\/([^.]+)\.visualstudio\.com\/(?:DefaultCollection\/)?([^/]+)\/_git\/([^/]+?)(?:\.git)?\/?$/,
1397
+ /^git@ssh\.dev\.azure\.com:v3\/([^/]+)\/([^/]+)\/([^/]+?)(?:\.git)?\/?$/,
1398
+ /^[^@]+@vs-ssh\.visualstudio\.com:v3\/([^/]+)\/([^/]+)\/([^/]+?)(?:\.git)?\/?$/
1399
+ ];
1400
+ const metadataSchema$1 = z.object({
1401
+ title: z.string(),
1402
+ sourceRefName: z.string().min(1),
1403
+ targetRefName: z.string().min(1),
1404
+ lastMergeTargetCommit: z.object({ commitId: z.string().min(1) }).optional(),
1405
+ repository: z.object({
1406
+ name: z.string(),
1407
+ project: z.object({ name: z.string() })
1408
+ })
1409
+ });
1410
+ /** org/project/repository, decoded; undefined for malformed escapes. */
1411
+ function slugOf(patterns, url) {
1412
+ const match = patterns.map((pattern) => pattern.exec(url)).find(Boolean);
1413
+ try {
1414
+ return match && match.slice(1, 4).map(decodeURIComponent).join("/");
1415
+ } catch {
1416
+ return;
1417
+ }
1418
+ }
1419
+ function azFailure(url, failure) {
1420
+ const unreadable = (hint) => new ReviewInputError(`Could not read ${url}: az failed (${failure}). ${hint}`, 409);
1421
+ if (/not recognized as an internal or external command|command not found|enoent/i.test(failure)) return unreadable("Install the Azure CLI and the azure-devops extension, then retry.");
1422
+ if (/extension|unknown command ['"]repos['"]|'repos' is not in the 'az' command group/i.test(failure)) return unreadable("Run `az extension add --name azure-devops`, then retry.");
1423
+ if (/az login|aadsts/i.test(failure)) return unreadable("Run `az login` to sign in, then retry.");
1424
+ if (/tf401027|not found|does not exist/i.test(failure)) return new ReviewInputError(`${url} was not found: it does not exist, or it is private and az is not signed in to an account that can read it. az said: ${failure}`, 404);
1425
+ return unreadable("Check your Azure DevOps connection and credentials, then retry.");
1426
+ }
1427
+ const azureDevOpsProvider = {
1428
+ id: "azure-devops",
1429
+ parseUrl(url) {
1430
+ const slug = slugOf(PULL_REQUEST_URLS, url);
1431
+ const number = Number(url.split("/").at(-1));
1432
+ if (!slug || !Number.isSafeInteger(number)) return void 0;
1433
+ return {
1434
+ host: "dev.azure.com",
1435
+ slug,
1436
+ number
1437
+ };
1438
+ },
1439
+ async readPullRequest({ host, slug, number }, deps) {
1440
+ const [org, project, repo] = slug.split("/");
1441
+ const url = `https://dev.azure.com/${org}/${project}/_git/${repo}/pullrequest/${number}`;
1442
+ const args = [
1443
+ "repos",
1444
+ "pr",
1445
+ "show",
1446
+ "--id",
1447
+ String(number),
1448
+ "--detect",
1449
+ "false",
1450
+ "--organization",
1451
+ `https://dev.azure.com/${org}`,
1452
+ "--output",
1453
+ "json",
1454
+ "--only-show-errors"
1455
+ ];
1456
+ let stdout;
1457
+ try {
1458
+ stdout = process.platform === "win32" ? await deps.run("cmd.exe", [
1459
+ "/d",
1460
+ "/s",
1461
+ "/c",
1462
+ "az",
1463
+ ...args
1464
+ ], { timeoutMs: AZ_TIMEOUT_MS }) : await deps.run("az", args, { timeoutMs: AZ_TIMEOUT_MS });
1465
+ } catch (error) {
1466
+ throw azFailure(url, firstLine(errorMessage(error)));
1467
+ }
1468
+ let pr;
1469
+ try {
1470
+ pr = metadataSchema$1.parse(JSON.parse(stdout));
1471
+ } catch {
1472
+ throw new ReviewInputError(`Could not read ${url}: the Azure DevOps response was malformed.`, 409);
1473
+ }
1474
+ const actual = `${pr.repository.project.name}/${pr.repository.name}`;
1475
+ if (actual.toLowerCase() !== `${project}/${repo}`.toLowerCase()) throw new ReviewInputError(`${url} was not found: PR ${number} belongs to ${actual}.`, 404);
1476
+ return {
1477
+ host,
1478
+ slug,
1479
+ number,
1480
+ title: pr.title,
1481
+ headRef: pr.sourceRefName,
1482
+ baseRefName: pr.targetRefName.replace(/^refs\/heads\//, ""),
1483
+ baseRefOid: pr.lastMergeTargetCommit?.commitId
1484
+ };
1485
+ },
1486
+ matchesRemote(remoteUrl, address) {
1487
+ return slugOf(REMOTE_URLS, remoteUrl.trim())?.toLowerCase() === address.slug.toLowerCase();
1488
+ }
1489
+ };
1490
+ //#endregion
1391
1491
  //#region src/review-api/github-provider.ts
1392
1492
  const GH_TIMEOUT_MS = 2e4;
1393
1493
  const API_TIMEOUT_MS = 15e3;
@@ -1479,7 +1579,7 @@ const providers = [{
1479
1579
  const remote = parseGitRemote(remoteUrl);
1480
1580
  return remote?.host === address.host && remote.slug.toLowerCase() === address.slug.toLowerCase();
1481
1581
  }
1482
- }];
1582
+ }, azureDevOpsProvider];
1483
1583
  function pullRequestProvider(url) {
1484
1584
  for (const provider of providers) {
1485
1585
  const address = provider.parseUrl(url);
@@ -1698,14 +1798,14 @@ const AgentSelectionSchema = z.strictObject({
1698
1798
  });
1699
1799
  function selectionMarkdown(selection, sourceExcerpt = "", diffPaths) {
1700
1800
  const target = selection.target;
1701
- if (selection.selectedDiff) return selectedDiffMarkdown(selection.selectedDiff, diffPaths);
1801
+ if (selection.selectedDiff) return `Selected diff:\n${selectedDiffMarkdown(selection.selectedDiff, diffPaths)}`;
1702
1802
  if (target.kind === "review") return "";
1703
- if (target.kind === "text") return target.quote.split(/\r?\n/).map((line) => `> ${line}`).join("\n");
1704
- return [
1803
+ if (target.kind === "text") return `Selected text:\n${target.quote.split(/\r?\n/).map((line) => `> ${line}`).join("\n")}`;
1804
+ return `Selected code:\n${[
1705
1805
  `## ${selection.title}`,
1706
1806
  selection.detail ?? "",
1707
1807
  sourceExcerpt
1708
- ].filter(Boolean).join("\n\n");
1808
+ ].filter(Boolean).join("\n\n")}`;
1709
1809
  }
1710
1810
  function selectedDiffMarkdown(diff, paths) {
1711
1811
  const oldCount = diff.rows.filter((row) => row.kind !== "added").length;
@@ -2196,7 +2296,7 @@ var AskHistory = class {
2196
2296
  };
2197
2297
  //#endregion
2198
2298
  //#region src/review-api/origin.ts
2199
- const pullRequestUrl = z.string().refine((url) => pullRequestProvider(url) !== void 0, "Use a canonical GitHub PR URL: https://github.com/owner/repository/pull/123, or the same path on a GitHub Enterprise host.");
2299
+ const pullRequestUrl = z.string().refine((url) => pullRequestProvider(url) !== void 0, "Use a canonical PR URL: https://github.com/owner/repository/pull/123, the same path on a GitHub Enterprise host, or https://dev.azure.com/org/project/_git/repository/pullrequest/123.");
2200
2300
  /** One key per PR: GitHub owner and repository names are case-insensitive,
2201
2301
  * and the URL pattern admits only ASCII, so lowercasing is canonical. */
2202
2302
  function pullRequestKey(url) {
@@ -2376,16 +2476,63 @@ var ReviewStore = class {
2376
2476
  };
2377
2477
  }
2378
2478
  refreshPending;
2479
+ /** What the last refresh saw of each review. A live worktree's snapshot is
2480
+ * kept, since every pass projects it again. */
2481
+ refreshed = /* @__PURE__ */ new Map();
2482
+ availableRepositories;
2483
+ /** Reviews whose source may have moved since the last pass: every live
2484
+ * worktree, and any other review that is new, edited, restored from trash or
2485
+ * reads from a repository that appeared or went away. */
2486
+ *reviewsToRefresh() {
2487
+ const available = new Set(this.repositories().filter((repository) => existsSync(repository.path)).map((repository) => repository.id));
2488
+ const previous = this.availableRepositories;
2489
+ this.availableRepositories = available;
2490
+ const flipped = (id) => !previous || previous.has(id) !== available.has(id);
2491
+ 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();
2492
+ const present = /* @__PURE__ */ new Set();
2493
+ for (const row of rows) {
2494
+ const reviewId = String(row.id);
2495
+ const version = Number(row.version);
2496
+ const state = `${version}\0${row.deleted_at ?? ""}`;
2497
+ present.add(reviewId);
2498
+ let seen = this.refreshed.get(reviewId);
2499
+ if (seen?.state === state) {
2500
+ if (seen.live) yield seen.live;
2501
+ else if ([...seen.repositories].some(flipped)) yield this.read(reviewId, version);
2502
+ continue;
2503
+ }
2504
+ const snapshot = row.deleted_at ? void 0 : this.read(reviewId, version);
2505
+ if (!snapshot) {
2506
+ this.refreshed.set(reviewId, {
2507
+ state,
2508
+ repositories: /* @__PURE__ */ new Set()
2509
+ });
2510
+ continue;
2511
+ }
2512
+ const live = snapshot.target?.kind === "worktree";
2513
+ seen = {
2514
+ state,
2515
+ repositories: live ? /* @__PURE__ */ new Set() : /* @__PURE__ */ new Set([...snapshot.pins ? [snapshot.pins.repositoryId] : [], ...pinnedRepositoryIds(snapshot.document)]),
2516
+ live: live ? snapshot : void 0
2517
+ };
2518
+ this.refreshed.set(reviewId, seen);
2519
+ yield snapshot;
2520
+ }
2521
+ for (const reviewId of this.refreshed.keys()) if (!present.has(reviewId)) this.refreshed.delete(reviewId);
2522
+ }
2379
2523
  /** Refresh source state without writing authored document versions. Serialized with edits. */
2380
2524
  refreshWorktrees() {
2381
2525
  if (this.closing || !this.providers.resolveTarget) return Promise.resolve();
2382
2526
  if (this.refreshPending) return this.refreshPending;
2383
2527
  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);
2528
+ let slice = performance.now();
2529
+ for (const snapshot of this.reviewsToRefresh()) {
2530
+ if (performance.now() - slice > 16) {
2531
+ await new Promise((resolve) => setImmediate(resolve));
2532
+ slice = performance.now();
2533
+ }
2387
2534
  try {
2388
- const live = this.liveSources.get(summary.reviewId);
2535
+ const live = this.liveSources.get(snapshot.reviewId);
2389
2536
  const last = live?.version === snapshot.version ? live : snapshot;
2390
2537
  const previous = last.pins;
2391
2538
  const projected = await this.projectLiveSource(snapshot, last);
@@ -3139,6 +3286,27 @@ function inspectSnapshot(snapshot, targetId) {
3139
3286
  }));
3140
3287
  }
3141
3288
  //#endregion
3289
+ //#region src/review-api/version-cache.ts
3290
+ /** A stored version never changes, so work derived from it runs once. Keeps
3291
+ * the `capacity` most recently used results. */
3292
+ var VersionCache = class {
3293
+ derive;
3294
+ options;
3295
+ results = /* @__PURE__ */ new Map();
3296
+ constructor(derive, options) {
3297
+ this.derive = derive;
3298
+ this.options = options;
3299
+ }
3300
+ get(input) {
3301
+ const key = this.options.key?.(input) ?? `${input.reviewId}\0${input.version}`;
3302
+ const result = this.results.get(key) ?? this.derive(input);
3303
+ this.results.delete(key);
3304
+ if (this.results.size >= this.options.capacity) this.results.delete(this.results.keys().next().value);
3305
+ this.results.set(key, result);
3306
+ return result;
3307
+ }
3308
+ };
3309
+ //#endregion
3142
3310
  //#region src/review-api/workspaces.ts
3143
3311
  const OWNED_ELSEWHERE = "Another Whiteboard server is managing this review's language workspaces. Retry once it stops.";
3144
3312
  /** Local lifecycle only: source and authored history never depend on preparation.
@@ -3942,18 +4110,20 @@ var LocalReviewData = class {
3942
4110
  if (!existsSync(this.store.repositoryPath(anchor.repositoryId))) throw unavailableCheckout();
3943
4111
  return anchorPins({ pins: anchor }, void 0);
3944
4112
  }
3945
- /** References whose own repository is unregistered or gone from disk. */
4113
+ /** References whose own repository is unregistered or gone from disk.
4114
+ * `snapshot` is a stored version. */
3946
4115
  async unavailableAnchors(snapshot) {
3947
4116
  const missing = /* @__PURE__ */ new Set();
3948
- for (const pins of explicitPins(sourceReferences(snapshot.document, { tolerant: true }))) try {
3949
- await this.anchorSourcePins(pins);
4117
+ for (const repositoryId of pinnedRepositoryIds(snapshot.document)) try {
4118
+ if (!existsSync(this.store.repositoryPath(repositoryId))) missing.add(repositoryId);
3950
4119
  } catch (error) {
3951
4120
  if (!(error instanceof ReviewInputError)) throw error;
3952
- missing.add(pins.repositoryId);
4121
+ missing.add(repositoryId);
3953
4122
  }
3954
4123
  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);
4124
+ return this.storedReferences.get(snapshot).filter((ref) => ref.source.pins && missing.has(ref.source.pins.repositoryId)).map((ref) => ref.id);
3956
4125
  }
4126
+ storedReferences = new VersionCache((snapshot) => sourceReferences(snapshot.document, { tolerant: true }), { capacity: 512 });
3957
4127
  trackedFiles = /* @__PURE__ */ new Map();
3958
4128
  repositories = /* @__PURE__ */ new Map();
3959
4129
  readers = /* @__PURE__ */ new Map();
@@ -4337,7 +4507,7 @@ var LocalReviewData = class {
4337
4507
  }
4338
4508
  const { host, slug } = address;
4339
4509
  const name = host === "github.com" ? slug : `${host}/${slug}`;
4340
- throw new ReviewInputError(repository.id ? `That checkout has no GitHub remote for ${name}. Add one, or omit repositoryId.` : `No registered checkout has a GitHub remote for ${name}. Register a checkout of ${name} with review_register_repository first, or pass a target.`, 404);
4510
+ throw new ReviewInputError(repository.id ? `That checkout has no remote for ${name}. Add one, or omit repositoryId.` : `No registered checkout has a remote for ${name}. Register a checkout of ${name} with review_register_repository first, or pass a target.`, 404);
4341
4511
  }
4342
4512
  async resolvePins(repositoryId, base, head) {
4343
4513
  const vcs = await this.vcs(repositoryId);