@dev.fast/whiteboard 0.3.4 → 0.3.5-preview.20261021.131

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 (120) hide show
  1. package/dist/{account-alias-CprZojXd.js → account-alias-CisiYUZT.js} +13 -8
  2. package/dist/agent-cli-BglNRDZN.js +2 -0
  3. package/dist/{agent-cli-CThN7Q59.js → agent-cli-TJ7_5_B3.js} +15 -10
  4. package/dist/{agent-client-BOOi7EUR.js → agent-client-H53ajvrm.js} +13 -3
  5. package/dist/{authoring-tools-BNwuMnz6.js → authoring-tools-BCquUsB3.js} +34 -27
  6. package/dist/{background-server-DLSDogkI.js → background-server-BSYXD9au.js} +1 -1
  7. package/dist/{background-server-CkvxA-6O.js → background-server-Xom8cA_u.js} +5 -5
  8. package/dist/build-info.json +1 -1
  9. package/dist/{cli-R4yfa5DT.js → cli-BOnV4cCh.js} +1 -1
  10. package/dist/{cli-runner-Cuc9FSzu.js → cli-runner-cRY5NOno.js} +70 -34
  11. package/dist/cli.js +4 -3
  12. package/dist/{client-BN_FDxFr.js → client-UH1bEG7Z.js} +260 -49
  13. package/dist/{fuzzy-match-BO9bK37N.js → fuzzy-match-D_x3wumC.js} +6 -4
  14. package/dist/{headless-host-CVBOQyRS.js → headless-host-BEe-aFtB.js} +1 -1
  15. package/dist/{headless-host-Cog6SpS9.js → headless-host-CEb5d-A0.js} +563 -175
  16. package/dist/lens-selection-BxPk53c8.js +214 -0
  17. package/dist/{local-data-D_9tvdsp.js → local-data-mbCb1HDH.js} +2448 -347
  18. package/dist/{mcp-D1hziANa.js → mcp-cBtzXsmN.js} +9 -12
  19. package/dist/{package-paths-C--__jEv.js → package-paths-BkeQsdaw.js} +1 -1
  20. package/dist/profile-Dbyidrcr.js +2 -0
  21. package/dist/{profile-DokFw782.js → profile-duqp5uPa.js} +20 -8
  22. package/dist/{remote-attach-CEJ-kvDY.js → remote-attach-BeKuMm_x.js} +4 -4
  23. package/dist/{remote-extensions-GN8Gf_qG.js → remote-extensions-CX2fAec1.js} +1 -1
  24. package/dist/{remote-extensions-Blcjr9Ze.js → remote-extensions-CYWNTVSf.js} +3 -3
  25. package/dist/{remote-language-server-BTKBQ7x4.js → remote-language-server-DLWluAeR.js} +5 -5
  26. package/dist/{remote-uninstall-DW7Gdryh.js → remote-uninstall-Y3S0YmJs.js} +6 -6
  27. package/dist/{request-origin-Zcn_CjX4.js → request-origin-icXPv6jh.js} +55 -18
  28. package/dist/{review-app-launcher-CkwLclzR.js → review-app-launcher-DZvG5ZAh.js} +2 -2
  29. package/dist/{review-home-paths-j2BRAaNv.js → review-home-paths-BwH08ihj.js} +19 -17
  30. package/dist/{review-telemetry-BLNwYnLV.js → review-telemetry-AZ0imsmJ.js} +6 -6
  31. package/dist/runtime-CF4JXdb8.js +27 -0
  32. package/dist/runtime.d.ts +1 -1
  33. package/dist/runtime.js +1 -1
  34. package/dist/{s3-DtJFWC0G.js → s3-CsG2EWED.js} +1 -1
  35. package/dist/{s3-config-BTO12tCC.js → s3-config-CjcLyFV5.js} +4 -2
  36. package/dist/{s3-config-CSy3fx8H.js → s3-config-zz4GWh2T.js} +1 -1
  37. package/dist/server/desktop-host.js +28 -104
  38. package/dist/{server-discovery-Bey8IqXH.js → server-discovery-DeVTdcqK.js} +3 -3
  39. package/dist/sharing/index.d.ts +484 -74
  40. package/dist/sharing/index.js +1 -1
  41. package/dist/{src-DsM3jO8G.js → src-DYZLCUP_.js} +98 -18
  42. package/dist/{src-BMPuxWv4.js → src-DtfSndqS.js} +269 -227
  43. package/dist/{stored-document-migration-CChxtFAy.js → stored-document-migration-B29lve9-.js} +318 -271
  44. package/dist/{thread-state-5pYr6_zM.js → thread-state-BPpvKFwQ.js} +12 -2
  45. package/dist/{tool-failure-C9MpbXJJ.js → tool-failure-CQhyLht3.js} +10 -3
  46. package/instructions/authoring.md +20 -5
  47. package/package.json +4 -4
  48. package/src/agent-selection.ts +6 -0
  49. package/src/ask/agents.ts +13 -1
  50. package/src/ask/checkout-files.ts +2 -5
  51. package/src/ask/thread-state.ts +4 -0
  52. package/src/ask/thread.ts +61 -31
  53. package/src/ask/threads.ts +24 -3
  54. package/src/cli-install.ts +13 -2
  55. package/src/cli-runner.ts +4 -1
  56. package/src/cli.ts +4 -1
  57. package/src/diff-selection-migration.ts +48 -34
  58. package/src/heading-anchors.ts +42 -0
  59. package/src/lens-selection.ts +38 -2
  60. package/src/markdown.ts +42 -0
  61. package/src/posthog-capture-client.ts +1 -1
  62. package/src/review-api/README.md +52 -13
  63. package/src/review-api/activity.ts +1 -4
  64. package/src/review-api/agent-cli.ts +9 -1
  65. package/src/review-api/anchor-quotes.ts +14 -7
  66. package/src/review-api/ask-history.ts +118 -51
  67. package/src/review-api/authoring-tools.ts +45 -30
  68. package/src/review-api/azure-devops-provider.ts +2 -20
  69. package/src/review-api/azure-remote.ts +21 -0
  70. package/src/review-api/blocks/definition.ts +1 -1
  71. package/src/review-api/blocks/markdown.ts +2 -2
  72. package/src/review-api/board-order.ts +144 -0
  73. package/src/review-api/checkout-groups.ts +586 -0
  74. package/src/review-api/document-headings.ts +58 -27
  75. package/src/review-api/document-text.ts +31 -2
  76. package/src/review-api/document.ts +362 -67
  77. package/src/review-api/foreign-references.ts +328 -0
  78. package/src/review-api/github-provider.ts +51 -13
  79. package/src/review-api/http.ts +734 -135
  80. package/src/review-api/instructions.ts +0 -19
  81. package/src/review-api/link-preview.ts +113 -0
  82. package/src/review-api/local-data.ts +310 -46
  83. package/src/review-api/mcp.ts +7 -13
  84. package/src/review-api/onboarding.ts +1 -1
  85. package/src/review-api/profile.ts +37 -6
  86. package/src/review-api/public-tools.ts +24 -2
  87. package/src/review-api/pull-request.ts +82 -49
  88. package/src/review-api/request-origin.ts +10 -0
  89. package/src/review-api/review-progress.ts +61 -27
  90. package/src/review-api/scratchpad-plan-upgrade.ts +250 -0
  91. package/src/review-api/store-migrations.ts +355 -0
  92. package/src/review-api/store-schema.ts +38 -29
  93. package/src/review-api/store.ts +2176 -495
  94. package/src/review-api/worktree-events.ts +3 -5
  95. package/src/review-api/worktree-source.ts +3 -7
  96. package/src/review-api/worktree-structural.integration.ts +72 -0
  97. package/src/review-stack.ts +15 -23
  98. package/src/runtime.ts +4 -30
  99. package/src/server/desktop-host.ts +6 -1
  100. package/src/server/desktop-server.ts +1 -36
  101. package/src/server/headless-host.ts +0 -2
  102. package/src/server/hono-http.ts +2 -1
  103. package/src/server/review-gateway-list.ts +3 -5
  104. package/src/server/review-gateway-memory.ts +1 -1
  105. package/src/server/review-lifecycle-telemetry.ts +2 -2
  106. package/src/server/review-server-core.ts +1 -5
  107. package/src/server/structural-comparisons.ts +47 -0
  108. package/src/sharing/export.ts +36 -7
  109. package/src/sharing/host.ts +34 -14
  110. package/src/sharing/import.ts +156 -24
  111. package/src/sharing/repository.ts +143 -24
  112. package/src/startup-trace.ts +3 -3
  113. package/src/stored-document-migration.ts +110 -19
  114. package/src/ui-telemetry-events.ts +44 -18
  115. package/dist/agent-cli-C6SPSII0.js +0 -2
  116. package/dist/profile-CYPmvBoA.js +0 -2
  117. package/dist/runtime-CKiWhGrf.js +0 -52
  118. package/instructions/scratchpad.md +0 -23
  119. package/src/review-preferences.ts +0 -88
  120. package/src/server/legacy-tutorial-cleanup.ts +0 -38
@@ -1,10 +1,10 @@
1
1
  import { n as isJsonObject } from "./src-X9phtB2j.js";
2
- import { w as ReviewCliInstallStampSchema } from "./src-DsM3jO8G.js";
3
- import { C as disableTraceMachine, E as traceMachineStatus, G as listTraceRepositoryRoots, K as traceRepositoryStatus, Q as removeAgentTraceHook, S as configureTraceMachine, T as traceMachineEnabled, U as disableAllTraceRepositories, W as enableTraceRepository, X as describeTraceHookOwners, Y as AGENT_TRACE_HOOK_AGENTS, Z as installHarnessHooks, dt as StoreClient, n as reviewDesktopStateDir, nt as traceScope, ot as readStoreAuth } from "./review-home-paths-j2BRAaNv.js";
4
- import { I as writeFileAtomicAsync, L as writePrivateJsonAtomic, N as devReviewHome, j as withFileLock } from "./s3-config-BTO12tCC.js";
5
- import { i as readReviewPackageVersion } from "./package-paths-C--__jEv.js";
2
+ import { E as ReviewCliInstallStampSchema } from "./src-DYZLCUP_.js";
3
+ import { C as disableTraceMachine, E as traceMachineStatus, G as listTraceRepositoryRoots, K as traceRepositoryStatus, Q as removeAgentTraceHook, S as configureTraceMachine, T as traceMachineEnabled, U as disableAllTraceRepositories, W as enableTraceRepository, X as describeTraceHookOwners, Y as AGENT_TRACE_HOOK_AGENTS, Z as installHarnessHooks, at as readStoreAuth, n as reviewDesktopStateDir, nt as traceScope, ut as StoreClient } from "./review-home-paths-BwH08ihj.js";
4
+ import { I as writeFileAtomicAsync, L as writePrivateJsonAtomic, N as devReviewHome, j as withFileLock } from "./s3-config-CjcLyFV5.js";
5
+ import { i as readReviewPackageVersion } from "./package-paths-BkeQsdaw.js";
6
6
  import { n as isFile, t as isDirectory } from "./fs-utils-BMPLt0cr.js";
7
- import { c as findExecutable, s as executableCommand } from "./fuzzy-match-BO9bK37N.js";
7
+ import { c as findExecutable, s as executableCommand } from "./fuzzy-match-D_x3wumC.js";
8
8
  import { createHash } from "node:crypto";
9
9
  import { constants } from "node:fs";
10
10
  import { access, chmod, lstat, mkdir, readFile, rm, stat, writeFile } from "node:fs/promises";
@@ -1093,9 +1093,9 @@ async function isOwnedShim(shimPath) {
1093
1093
  * (ELECTRON_RUN_AS_NODE) — the exact runtime the server uses. System Node is
1094
1094
  * the last resort and gets a clear version check instead of a cryptic crash.
1095
1095
  */
1096
- async function writePathShim(shimPath, cliPath, runtimePath, devHome) {
1096
+ async function writePathShim(shimPath, cliPath, runtimePath, devHome, previewHome = devHome) {
1097
1097
  if (process.platform === "win32") {
1098
- await writeFileAtomicAsync(shimPath, windowsCliShim(cliPath, runtimePath ?? process.execPath, devHome), { replaceSymlink: true });
1098
+ await writeFileAtomicAsync(shimPath, windowsCliShim(cliPath, runtimePath ?? process.execPath, devHome === previewHome ? devHome : void 0), { replaceSymlink: true });
1099
1099
  shimPath = shimPath.replace(/\.cmd$/i, "");
1100
1100
  }
1101
1101
  const source = `#!/bin/sh
@@ -1103,6 +1103,8 @@ async function writePathShim(shimPath, cliPath, runtimePath, devHome) {
1103
1103
  FALLBACK_CLI=${shSingleQuote(cliPath)}
1104
1104
  FALLBACK_RUNTIME=${shSingleQuote(runtimePath ?? "")}
1105
1105
  DEFAULT_HOME=${shSingleQuote(devHome)}
1106
+ channel="\${DEV_FAST_REVIEW_CHANNEL:-\${DEV_REVIEW_INSTANCE:-}}"
1107
+ [ "$channel" = preview ] && DEFAULT_HOME=${shSingleQuote(previewHome)}
1106
1108
  export DEV_REVIEW_HOME="\${DEV_REVIEW_HOME:-$DEFAULT_HOME}"
1107
1109
  DESKTOP="$DEV_REVIEW_HOME/review-desktop"
1108
1110
 
@@ -1203,7 +1205,10 @@ async function installReviewCommand(input) {
1203
1205
  output: `[skip] kept the existing whiteboard command at ${shimPath}\n`
1204
1206
  };
1205
1207
  const shadowingCommand = await resolvePathCommand("whiteboard", shimPath, env);
1206
- await writePathShim(shimPath, input.cliPath, input.cliRuntimePath, devReviewHome(env, homeDir));
1208
+ await writePathShim(shimPath, input.cliPath, input.cliRuntimePath, devReviewHome({ DEV_REVIEW_HOME: env.DEV_REVIEW_HOME }, homeDir), devReviewHome({
1209
+ DEV_REVIEW_HOME: env.DEV_REVIEW_HOME,
1210
+ DEV_REVIEW_INSTANCE: "preview"
1211
+ }, homeDir));
1207
1212
  if (await traceMachineEnabled({
1208
1213
  homeDir,
1209
1214
  env
@@ -0,0 +1,2 @@
1
+ import { n as runReviewAgentCli } from "./agent-cli-TJ7_5_B3.js";
2
+ export { runReviewAgentCli };
@@ -1,12 +1,12 @@
1
1
  import { n as isJsonObject, u as parseJsonText } from "./src-X9phtB2j.js";
2
- import { T as traceMachineEnabled } from "./review-home-paths-j2BRAaNv.js";
3
- import { a as toolResultText, i as connectReviewInstance, s as ReviewApiError } from "./agent-client-BOOi7EUR.js";
4
- import { N as devReviewHome } from "./s3-config-BTO12tCC.js";
5
- import { n as readBuildCommit } from "./package-paths-C--__jEv.js";
6
- import { b as selectReviewInstance, u as REVIEW_INSTANCE_ENV } from "./server-discovery-Bey8IqXH.js";
7
- import { n as reviewSessionAgent } from "./review-telemetry-BLNwYnLV.js";
8
- import { n as REVIEW_VIA_HEADER, t as REVIEW_AGENT_HEADER } from "./request-origin-Zcn_CjX4.js";
9
- import { i as publicTool, n as RECOVERY, r as callPublicTool, t as toolFailure } from "./tool-failure-C9MpbXJJ.js";
2
+ import { T as traceMachineEnabled } from "./review-home-paths-BwH08ihj.js";
3
+ import { a as toolResultText, i as connectReviewInstance, s as ReviewApiError } from "./agent-client-H53ajvrm.js";
4
+ import { N as devReviewHome } from "./s3-config-CjcLyFV5.js";
5
+ import { n as readBuildCommit } from "./package-paths-BkeQsdaw.js";
6
+ import { b as selectReviewInstance, u as REVIEW_INSTANCE_ENV } from "./server-discovery-DeVTdcqK.js";
7
+ import { n as reviewSessionAgent } from "./review-telemetry-AZ0imsmJ.js";
8
+ import { a as REVIEW_VIA_HEADER, i as REVIEW_THREAD_HEADER, n as REVIEW_AGENT_HEADER, t as ASK_THREAD_ENV } from "./request-origin-icXPv6jh.js";
9
+ import { i as publicTool, n as RECOVERY, r as callPublicTool, t as toolFailure } from "./tool-failure-CQhyLht3.js";
10
10
  import { existsSync } from "node:fs";
11
11
  import path from "node:path";
12
12
  //#region src/review-api/install-update.ts
@@ -36,9 +36,14 @@ async function runReviewAgentCli(input) {
36
36
  return 0;
37
37
  }
38
38
  if (extra.length || mode === "mcp" && name) throw new Error("Unexpected arguments. Use whiteboard api --help.");
39
- const headers = Object.fromEntries([[REVIEW_VIA_HEADER, mode === "mcp" ? "mcp" : "api"], [REVIEW_AGENT_HEADER, reviewSessionAgent(env)]]);
39
+ const thread = env[ASK_THREAD_ENV]?.trim();
40
+ const headers = Object.fromEntries([
41
+ [REVIEW_VIA_HEADER, mode === "mcp" ? "mcp" : "api"],
42
+ [REVIEW_AGENT_HEADER, reviewSessionAgent(env)],
43
+ [REVIEW_THREAD_HEADER, thread ?? ""]
44
+ ]);
40
45
  if (mode === "mcp") {
41
- const { serveReviewMcp } = await import("./mcp-D1hziANa.js");
46
+ const { serveReviewMcp } = await import("./mcp-cBtzXsmN.js");
42
47
  exitOnInstallUpdate(input.stderr);
43
48
  await serveReviewMcp(async (key, agentKind) => {
44
49
  const connected = await connectReviewInstance(key ? {
@@ -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-DsM3jO8G.js";
2
+ import "./src-DYZLCUP_.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-Bey8IqXH.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-DeVTdcqK.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. */
@@ -43,6 +43,16 @@ var ReviewApiClient = class {
43
43
  signal
44
44
  })).json();
45
45
  }
46
+ /** A command the person gave from the app, not an agent: its version is
47
+ * credited to them, and a block they hold never stops it. The header is
48
+ * the one request-origin.ts names. */
49
+ async personCommand(operation) {
50
+ return (await this.response("/commands", {
51
+ method: "POST",
52
+ headers: { "x-review-person": "1" },
53
+ body: JSON.stringify({ operation })
54
+ })).json();
55
+ }
46
56
  async *watch(subscriptions, signal) {
47
57
  const reader = (await this.response(`/watch?subscriptions=${encodeURIComponent(JSON.stringify(subscriptions))}`, { signal })).body.pipeThrough(new TextDecoderStream()).getReader();
48
58
  const cancel = () => {
@@ -249,7 +259,7 @@ async function connectReviewInstance(env = process.env, headers = {}, options =
249
259
  const selection = await selectReviewInstance({ env });
250
260
  const discovery = healthyReviewInstance(selection);
251
261
  if (!discovery) {
252
- const { ensureServerWithoutDesktop } = await import("./background-server-DLSDogkI.js");
262
+ const { ensureServerWithoutDesktop } = await import("./background-server-BSYXD9au.js");
253
263
  const server = await ensureServerWithoutDesktop({
254
264
  selection,
255
265
  env,
@@ -1,7 +1,8 @@
1
- import { c as activityEndSchema, i as commandSchema, l as activityUpdateSchema, n as uploadSchema, s as activityBeginSchema } from "./local-data-D_9tvdsp.js";
2
- import { S as publishedEditSchema, y as pathTargetSchema } from "./stored-document-migration-CChxtFAy.js";
1
+ import { n as isJsonObject } from "./src-X9phtB2j.js";
2
+ import { c as activityBeginSchema, l as activityEndSchema, n as uploadSchema, r as commandSchema, u as activityUpdateSchema } from "./local-data-mbCb1HDH.js";
3
+ import { C as pathTargetSchema, D as publishedEditSchema } from "./stored-document-migration-B29lve9-.js";
3
4
  import { t as ReviewInputError } from "./input-error-OLgB_h31.js";
4
- import { t as findReviewPackageRoot } from "./package-paths-C--__jEv.js";
5
+ import { t as findReviewPackageRoot } from "./package-paths-BkeQsdaw.js";
5
6
  import { z } from "zod";
6
7
  import { readFile } from "node:fs/promises";
7
8
  import path from "node:path";
@@ -102,12 +103,8 @@ const COMPONENT_REFERENCE = [
102
103
  const instructionsQuerySchema = z.strictObject({ topic: z.enum([
103
104
  "authoring",
104
105
  "file-lenses",
105
- "scratchpad",
106
106
  "trace-archaeology"
107
107
  ]).default("authoring") });
108
- function scratchpadAvailable(context) {
109
- return context.desktopAvailable && context.scratchpadEnabled;
110
- }
111
108
  const cache = /* @__PURE__ */ new Map();
112
109
  function read(root, name) {
113
110
  const file = path.join(root, "instructions", `${name}.md`);
@@ -122,11 +119,9 @@ function read(root, name) {
122
119
  return content;
123
120
  }
124
121
  async function renderInstructions(topic, context, root = findReviewPackageRoot(import.meta.url)) {
125
- if (topic === "scratchpad" && !scratchpadAvailable(context)) return "The Whiteboard scratchpad is turned off or Whiteboard Desktop is not running. Answer in chat; the scratchpad can be turned on in Whiteboard Desktop Settings.";
126
122
  if (topic === "trace-archaeology" && !context.traceEnabled) return "Trace capture is off on this machine, so no agent traces are available. It can be turned on in Whiteboard Desktop Settings under Experimental Features.";
127
- if (topic === "scratchpad") return `${await read(root, topic)}\n\n${COMPONENT_REFERENCE}`;
128
123
  if (topic !== "authoring") return read(root, topic);
129
- const more = [...scratchpadAvailable(context) ? ["- Explaining code visually outside a review: `session_get_instructions({topic:\"scratchpad\"})`"] : [], ...context.traceEnabled ? ["- Why code exists / past agent sessions: `session_get_instructions({topic:\"trace-archaeology\"})`"] : []];
124
+ const more = [...context.traceEnabled ? ["- Why code exists / past agent sessions: `session_get_instructions({topic:\"trace-archaeology\"})`"] : []];
130
125
  return [
131
126
  await read(root, "authoring"),
132
127
  COMPONENT_REFERENCE,
@@ -153,7 +148,7 @@ const REVIEW_STATUS_TOOL = {
153
148
  /** The host publishes its input schemas, except where agents write less than
154
149
  * the host accepts (publishedEditSchema, publishedLensEditSchema) or name a
155
150
  * checkout by path (pathTargetSchema); adapters validate nothing. */
156
- function authoringTools(scratchpadAvailable = false, traceEnabled = false) {
151
+ function authoringTools(traceEnabled = false) {
157
152
  const id = z.string().min(1);
158
153
  const review = { reviewId: id };
159
154
  const [image, trace] = uploadSchema.options;
@@ -161,19 +156,19 @@ function authoringTools(scratchpadAvailable = false, traceEnabled = false) {
161
156
  ...image.shape,
162
157
  ...trace.shape,
163
158
  kind: z.enum(["image", "trace"])
164
- }).partial().required({
159
+ }).omit({ repositoryId: true }).extend({ repositoryPath: id }).partial().required({
165
160
  id: true,
166
- repositoryId: true,
161
+ repositoryPath: true,
167
162
  kind: true
168
163
  });
169
164
  const descriptions = {
170
- create: "Create a review of saved working files, immutable commits or a GitHub PR. Revisions are resolved on acceptance. A worktree target reviews the saved files in its checkout, uncommitted ones included and untracked ones left out (git add -N a new file to include it), against base: the branch to compare against, by default the default branch of the repository. The diff starts at the merge base of base and HEAD, which follows rebases. Omitted commits base means source at head with no diff; supply the parent to review introduced changes. For a branch stacked on another, pass the branch below it as base; Desktop links reviews into a stack through their bases. For a GitHub PR, pullRequestUrl alone is enough: target and title become optional, and the host fetches the PR into a registered checkout of its repository and pins the current PR head and GitHub diff base, titled from the PR. When the existing review for that PR is in Trash, creation reports its ID; restore it with review_lifecycle state:\"active\" before continuing. When an active review for that PR exists, it is returned instead, reporting whether its head moved and whether an agent is working on it; update it in place, move its target with review_set_target, or create a separate review with reuseExisting. kind:\"scratchpad\" names the one scratchpad, which the host creates itself. The result carries review, the review as review_list shows it: its target with resolved commits, origin (its PR), repositoryName and repositoryPath, so no follow-up read is needed before diffing. When Desktop is available the review opens there and the result reports opened, softwareMapEnabled and environmentIssues, as review_open does; set open:false to author in the background without taking over Desktop.",
171
- set_target: "Move the review to a new target, such as the commits after a rebase or amend, preserving the document and component IDs. Revisions are resolved on acceptance. Returns warnings for retained source ranges in files the new commits changed, to verify, and resources that no longer match; fix them with review_edit. Earlier versions keep their source. Omitted pullRequestUrl keeps the PR within the same repository; changing repositories clears it. Supply a URL to replace it or null to detach.",
165
+ create: "Create a whiteboard. target names its repository by the checkout's path, and what it shows: {kind:\"project\"} for a plan in that repository's project with no change; {kind:\"worktree\", base?} for the saved files in the checkout, uncommitted ones included and untracked ones left out (git add -N a new file to include it), against base, by default the repository's default branch, from the merge base of base and HEAD, which follows rebases; {kind:\"commits\", head, base?} for immutable commits (omitted base means source at head with no diff; supply the parent to review introduced changes); {kind:\"pullRequest\", pullRequest} for a GitHub PR by URL or number, fetched into that checkout and pinned at the PR head and GitHub diff base. For a branch stacked on another, pass the branch below it as base. Group plan implementations or a PR stack under one project-target overview with parentId, and order its children with dependsOn; Git bases do not set these relationships. Revisions are resolved on acceptance. With a PR and no title the PR's title is used. When the existing review for that PR is in Trash, creation reports its ID; restore it with review_lifecycle state:\"active\" before continuing. When an active review for that PR exists, it is returned instead, reporting whether its head moved and whether an agent is working on it; update it in place, move its target with review_set_target, or create a separate review with reuseExisting:false. The result carries review, as review_list shows it, with its target and repositoryPath, so no follow-up read is needed before diffing. When Desktop is available the review opens there and the result reports opened, softwareMapEnabled and environmentIssues, as review_open does; set open:false to author in the background without taking over Desktop. parentId and dependsOn set its place among the project's reviews, as review_set_parent and review_set_dependencies do; projectId files it in another project, by project.id from review_list.",
166
+ set_target: "Move the review to a new target (the same targets review_create takes), such as the commits after a rebase or amend, or commits or a PR for a board that had none. The document and component IDs are kept. Revisions are resolved on acceptance. Returns warnings for retained source ranges in files the new commits changed, to verify, and resources that no longer match; fix them with review_edit. Earlier versions keep their source. A PR target names the PR; other targets keep it within the same repository and clear it across repositories.",
172
167
  edit: [
173
- "Edit one document component. content is one component, named by its type; each type's fields are in the Components section of session_get_instructions (authoring or scratchpad).",
174
- "Anchors are head/path#L10-L20 or base/path#L7, or diff/path#L84-R90 for a range across sides, with repository-relative paths and 1-based inclusive lines. pins {repositoryId, head, base?} say which commits anchors quote: a step, frame, attachment or operation without pins uses its block's, and a block without them the review's.",
175
- "insert adds content: parentId nests it, afterId places it, and omitted placement appends (on the scratchpad, it lands at the top). update patches a component's own fields; null removes an optional one. A section's children and a diagram's units change through their own IDs; a call stack's frames change by replacing the call stack. move and remove take a targetId; removing a flow_node removes its edges.",
176
- "The host assigns short durable IDs. The result names the edited component and, for an insert or replace, its first-level children, and quotes the first and last line of each anchor the edit added or changed (the first 20; unquotedAnchors counts the rest): check they are the lines you meant. Accepted edits save at once.",
168
+ "Edit one document component. content is one component, named by its type; each type's fields are in the Components section of session_get_instructions.",
169
+ "Anchors are head/path#L10-L20 or base/path#L7, or diff/path#L84-R90 for a range across sides, with repository-relative paths and 1-based inclusive lines. pins {head, base?} say which commits of the whiteboard's repository anchors quote: a step, frame, attachment or operation without pins uses its block's, and a block without them the review's.",
170
+ "insert adds content: parentId nests it, afterId places it, and omitted placement appends. update patches a component's own fields; null removes an optional one. A section's children and a diagram's units change through their own IDs; a call stack's frames change by replacing the call stack. move and remove take a targetId; removing a flow_node removes its edges.",
171
+ "The host assigns short durable IDs. The result names the edited component and, for an insert or replace, its first-level children, and quotes the first and last line of each anchor the edit added or changed (the first 20; unquotedAnchors counts the rest): check they are the lines you meant. Accepted edits save at once. An edit to a block the user is editing is refused with an error: edit another block, or retry later.",
177
172
  "While a reader may be watching, write small and often: one paragraph per edit. Insert a new diagram whole. Change one already drawn a unit at a time (a flow_node, flow_edge or step, with parentId naming the diagram), and link each added flow_node to a node already drawn."
178
173
  ].join("\n"),
179
174
  lens_edit: "Edit one Diff-view lens. Lenses partition the review's change for the Diff view; they sit beside the document (never in it) and version with it. The host assigns durable lens IDs; updates replace only the fields supplied. Write one lens per call while a reader may be watching; each draws in on the Diffs page. Pass your activityId so your courier draws each lens. The result identifies the lens and reports uncategorized: changed lines no lens selects yet, grouped by file. Keep adding lenses until it is empty or what remains is deliberate. review_lens_get reads the current lenses and gaps.",
@@ -181,13 +176,15 @@ function authoringTools(scratchpadAvailable = false, traceEnabled = false) {
181
176
  restore: "Restore title, source pins, PR identity and content from a saved version.",
182
177
  attention: "Mark an active review viewed without changing its content or lifecycle. Restore a review from Trash before marking it viewed.",
183
178
  lifecycle: "Move a review between active and trash. Trash retains the review for 30 days before automatic deletion. Moving it back to active restores it; moving it to trash again does not restart that period.",
184
- delete: "Permanently delete a review and its history. The review must be in Trash. This cannot be undone."
179
+ delete: "Permanently delete a review and its history. The review must be in Trash. This cannot be undone. Its children move up to top level, and dependencies on it are dropped.",
180
+ set_parent: "Nest the review under another review of the same project, or make it top-level with parentId:null. Nesting is one level deep: the parent must be top-level, and a review with children can't become a child. The sidebar shows children under their parent. Saves no review version. Returns the review's relations: parentId, children in sibling order, and dependsOn.",
181
+ set_dependencies: "Replace the list of reviews this one builds on: any reviews of the same project. Siblings list after what they depend on, directly or through other reviews; a PR stack is a chain. A list that would close a cycle is refused, naming the cycle. Saves no review version. Returns the review's relations."
185
182
  };
186
183
  const tool = (name, description, schema, method, path, commandType) => ({
187
184
  name: `review_${name === "restore" ? "restore_version" : name}`,
188
185
  description,
189
186
  inputSchema: {
190
- ...z.toJSONSchema(schema, { io: "input" }),
187
+ ...withoutRepositoryIds(z.toJSONSchema(schema, { io: "input" })),
191
188
  type: "object"
192
189
  },
193
190
  method,
@@ -197,18 +194,17 @@ function authoringTools(scratchpadAvailable = false, traceEnabled = false) {
197
194
  return [
198
195
  REVIEW_STATUS_TOOL,
199
196
  tool("capabilities", "Discover whether Desktop is available and optional software-map generation is enabled. Read before authoring.", z.strictObject({}), "GET", "/capabilities"),
200
- tool("get_instructions", "Read Whiteboard's guidance before creating or editing a review. The default topic gives the authoring workflow; \"file-lenses\" covers Diff-view file lenses." + (traceEnabled ? " Call review_get_instructions({topic:\"trace-archaeology\"}) for why code exists, what an agent was thinking, or whether an agent solved this before." : "") + (scratchpadAvailable ? " When the user asks in conversation to be shown how code works or wants a diagram, without asking for a review, draw it on the scratchpad rather than answering only in chat: call review_get_instructions({topic:\"scratchpad\"}) first. A request for a review or to use Whiteboard means authoring a review with the default topic." : ""), instructionsQuerySchema.partial(), "GET", "/instructions"),
197
+ tool("get_instructions", "Read Whiteboard's guidance before creating or editing a review. The default topic gives the authoring workflow; \"file-lenses\" covers Diff-view file lenses." + (traceEnabled ? " Call review_get_instructions({topic:\"trace-archaeology\"}) for why code exists, what an agent was thinking, or whether an agent solved this before." : ""), instructionsQuerySchema.partial(), "GET", "/instructions"),
201
198
  tool("activity_begin", "Show that you are working on a review: the reader sees your focus and your courier drawing your edits, and the review reads as finished only once every agent has ended. Returns an activityId: pass it to session_edit and session_lens_edit so your courier draws them, and to review_activity_update and review_activity_end. Nothing is locked; other agents can work on the review too. The activity expires after 3 minutes without an edit or update.", activityBeginSchema.extend(review), "POST", "/:reviewId/activity/begin"),
202
199
  tool("activity_update", "Change your focus, or keep your activity alive during long reads or pauses; an edit carrying your activityId already keeps it alive. Omitted focus preserves it and null clears it. Fails once the activity has expired: begin a new one.", activityUpdateSchema.extend(review), "POST", "/:reviewId/activity/update"),
203
200
  tool("activity_end", "End your activity when your part is finished: readers treat a review with content and no live activity as ready. Ending it creates no document version. Ending twice, or an expired activity, changes nothing.", activityEndSchema.extend(review), "POST", "/:reviewId/activity/end"),
204
201
  ...commandSchema.shape.operation.options.map((operation) => {
205
202
  const type = operation.shape.type.value;
206
- const { type: _type, repositoryId: _repositoryId, ...fields } = operation.shape;
203
+ const { type: _type, repositoryId: _repositoryId, pullRequestUrl: _pullRequestUrl, ...fields } = operation.shape;
207
204
  return tool(type, descriptions[type], z.strictObject({
208
205
  ...fields,
209
206
  ...type === "create" && {
210
- target: pathTargetSchema.optional(),
211
- repositoryPath: id.optional().describe("Only with pullRequestUrl and no target: the local checkout to fetch the PR into. Default: the existing review's, else the first registered checkout with a remote for the PR's repository."),
207
+ target: pathTargetSchema,
212
208
  open: z.boolean().optional()
213
209
  },
214
210
  ...type === "set_target" && { target: pathTargetSchema },
@@ -228,9 +224,20 @@ function authoringTools(scratchpadAvailable = false, traceEnabled = false) {
228
224
  retry: z.boolean().optional()
229
225
  }), "POST", "/:reviewId/environment"),
230
226
  tool("workspace_cleanup", "Inspect failed cleanup of retired review-owned checkouts. Supply workspaceId to retry removal of that checkout. This does not remove active review checkouts.", z.strictObject({ workspaceId: id.optional() }), "POST", "/workspace-cleanup"),
231
- tool("register_repository", "Register a local Git or jj repository and return its id, for pins on scratchpad blocks. session_create and session_set_target take the checkout's path instead.", z.strictObject({ path: id }), "POST", "/repositories"),
232
227
  tool("upload", "Retain an image or trace for use in a review. kind:\"image\" takes base64; kind:\"trace\" takes trace. Reusing an upload ID requires identical content; rejected uploads are not saved.", uploadInput, "POST", "/resources")
233
228
  ];
234
229
  }
230
+ /** Drops repositoryId from pins: the host fills in the board's own. */
231
+ function withoutRepositoryIds(schema) {
232
+ return JSON.parse(JSON.stringify(schema), (_key, value) => {
233
+ if (!isJsonObject(value) || !isJsonObject(value.properties) || !("repositoryId" in value.properties) || !("head" in value.properties)) return value;
234
+ const { repositoryId: _repositoryId, ...properties } = value.properties;
235
+ return {
236
+ ...value,
237
+ properties,
238
+ ...Array.isArray(value.required) && { required: value.required.filter((name) => name !== "repositoryId") }
239
+ };
240
+ });
241
+ }
235
242
  //#endregion
236
- export { scratchpadAvailable as a, readQuerySchemas as c, renderInstructions as i, REVIEW_STATUS_TOOL as n, inspectQuerySchema as o, instructionsQuerySchema as r, queryAnchor as s, authoringTools as t };
243
+ export { inspectQuerySchema as a, renderInstructions as i, REVIEW_STATUS_TOOL as n, queryAnchor as o, instructionsQuerySchema as r, readQuerySchemas as s, authoringTools as t };
@@ -1,2 +1,2 @@
1
- import { a as headlessServerOwner, c as stopBackgroundServer, i as ensureServerWithoutDesktop, r as ensureBackgroundServer } from "./background-server-CkvxA-6O.js";
1
+ import { a as headlessServerOwner, c as stopBackgroundServer, i as ensureServerWithoutDesktop, r as ensureBackgroundServer } from "./background-server-Xom8cA_u.js";
2
2
  export { ensureBackgroundServer, ensureServerWithoutDesktop, headlessServerOwner, stopBackgroundServer };
@@ -1,8 +1,8 @@
1
- import "./review-home-paths-j2BRAaNv.js";
2
- import { D as liveLockOwner, k as processIsAlive } from "./s3-config-BTO12tCC.js";
3
- import { t as findReviewPackageRoot } from "./package-paths-C--__jEv.js";
4
- import { a as readReviewServerHealth, c as reviewServerStateDir, i as readReviewServerDiscovery, n as desktopOwnsHome, p as desktopUsesHome, r as headlessServerLockPath } from "./server-discovery-Bey8IqXH.js";
5
- import { t as desktopApplicationInstalled } from "./review-app-launcher-CkwLclzR.js";
1
+ import "./review-home-paths-BwH08ihj.js";
2
+ import { D as liveLockOwner, k as processIsAlive } from "./s3-config-CjcLyFV5.js";
3
+ import { t as findReviewPackageRoot } from "./package-paths-BkeQsdaw.js";
4
+ import { a as readReviewServerHealth, c as reviewServerStateDir, i as readReviewServerDiscovery, n as desktopOwnsHome, p as desktopUsesHome, r as headlessServerLockPath } from "./server-discovery-DeVTdcqK.js";
5
+ import { t as desktopApplicationInstalled } from "./review-app-launcher-DZvG5ZAh.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.3.4","commit":"f33bd6620742fe65ccc1d5fef416fbe8254dcf79","dirty":true,"builtAt":"2026-10-20T15:23:18.478Z"}
1
+ {"version":"0.3.5-preview.20261021.131","commit":"6bf7b672e0c4d566b582d555044a624620c6311f","dirty":true,"builtAt":"2026-10-21T16:51:06.481Z"}
@@ -1,4 +1,4 @@
1
- import { r as connectReviewApi } from "./agent-client-BOOi7EUR.js";
1
+ import { r as connectReviewApi } from "./agent-client-H53ajvrm.js";
2
2
  import { randomUUID } from "node:crypto";
3
3
  import { z } from "zod";
4
4
  //#region src/sharing/cli.ts
@@ -1,18 +1,18 @@
1
- import { C as setLocalVcsCommandObserver, E as __toESM, T as __require, w as __commonJSMin } from "./src-BMPuxWv4.js";
1
+ import { D as __commonJSMin, E as setLocalVcsCommandObserver, O as __require, k as __toESM } from "./src-DtfSndqS.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-DsM3jO8G.js";
4
- import { $ as resolveTraceCommand, 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, _ as runTraceRepair, b as runTraceGitHook, c as registerTraceCommands, ct as runStoreLogout, d as runTraceList$1, dt as StoreClient, f as runTracePull$1, ft as withStoreAuthorization, g as runTraceInstallMachine, gt as jsonRequestedInArgv, h as runTraceEnable, ht as humanStream, j as runTraceOnboard, k as runTraceAllow, l as resolveTraceReadStorage, lt as runStoreWhoami, m as runTraceDisable, mt as failWithJsonError, nt as traceScope, ot as readStoreAuth, p as runTraceShow, pt as emitJsonEvent, q as readTraceUserConfig, rt as DEFAULT_STORE_ORIGIN, st as runStoreLogin, tt as traceHomeDir, u as runTraceBlame, ut as StoreApiError, v as runTraceStatus, w as readLegacyCaptureSettings, x as runTraceHook, y as runTraceSync, z as selectTraceStorage } from "./review-home-paths-j2BRAaNv.js";
5
- import { o as ReviewApiClient, s as ReviewApiError } from "./agent-client-BOOi7EUR.js";
6
- import { t as resolveReviewRoot } from "./runtime-CKiWhGrf.js";
7
- import { C as s3ProfileSchema, E as writeTraceConfigFile, N as devReviewHome, P 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-BTO12tCC.js";
3
+ import { S as REVIEW_REMOTE_ATTACH_END, b as REVIEW_REMOTE_AGENT_IDS, h as REVIEW_CLI_INSTALL_TARGET_LABELS, l as boardTitle, x as REVIEW_REMOTE_ATTACH_BEGIN } from "./src-DYZLCUP_.js";
4
+ import { $ as resolveTraceCommand, 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, _ as runTraceRepair, at as readStoreAuth, b as runTraceGitHook, c as registerTraceCommands, ct as runStoreWhoami, d as runTraceList$1, dt as withStoreAuthorization, f as runTracePull$1, ft as emitJsonEvent, g as runTraceInstallMachine, h as runTraceEnable, ht as jsonRequestedInArgv, j as runTraceOnboard, k as runTraceAllow, l as resolveTraceReadStorage, lt as StoreApiError, m as runTraceDisable, mt as humanStream, nt as traceScope, ot as runStoreLogin, p as runTraceShow, pt as failWithJsonError, q as readTraceUserConfig, rt as DEFAULT_STORE_ORIGIN, st as runStoreLogout, tt as traceHomeDir, u as runTraceBlame, ut as StoreClient, v as runTraceStatus, w as readLegacyCaptureSettings, x as runTraceHook, y as runTraceSync, z as selectTraceStorage } from "./review-home-paths-BwH08ihj.js";
5
+ import { o as ReviewApiClient, s as ReviewApiError } from "./agent-client-H53ajvrm.js";
6
+ import { t as resolveReviewRoot } from "./runtime-CF4JXdb8.js";
7
+ import { C as s3ProfileSchema, E as writeTraceConfigFile, N as devReviewHome, P 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-CjcLyFV5.js";
8
8
  import { t as errorMessage } from "./error-message-OtiDonty.js";
9
- import { i as readReviewPackageVersion, r as readBuildInfo, t as findReviewPackageRoot } from "./package-paths-C--__jEv.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-Bey8IqXH.js";
11
- import { t as ReviewTelemetry } from "./review-telemetry-BLNwYnLV.js";
12
- import { t as fuzzyRank } from "./fuzzy-match-BO9bK37N.js";
13
- import { _ as isInstallTarget, a as isOwnedShim, f as windowsInstallerCommand, g as ALL_INSTALL_TARGETS, h as detectConnectAgents, m as connectAgent, o as pathShimPath, p as scanLegacySkills, t as aliasInstallationToAccount, v as connectPrompts } from "./account-alias-CprZojXd.js";
14
- import { n as focusReviewDesktop, r as runReviewAppLaunch } from "./review-app-launcher-CkwLclzR.js";
15
- import { t as reviewAgentCliHelp } from "./agent-cli-CThN7Q59.js";
9
+ import { i as readReviewPackageVersion, r as readBuildInfo, t as findReviewPackageRoot } from "./package-paths-BkeQsdaw.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-DeVTdcqK.js";
11
+ import { t as ReviewTelemetry } from "./review-telemetry-AZ0imsmJ.js";
12
+ import { t as fuzzyRank } from "./fuzzy-match-D_x3wumC.js";
13
+ import { _ as isInstallTarget, a as isOwnedShim, f as windowsInstallerCommand, g as ALL_INSTALL_TARGETS, h as detectConnectAgents, m as connectAgent, o as pathShimPath, p as scanLegacySkills, t as aliasInstallationToAccount, v as connectPrompts } from "./account-alias-CisiYUZT.js";
14
+ import { n as focusReviewDesktop, r as runReviewAppLaunch } from "./review-app-launcher-DZvG5ZAh.js";
15
+ import { t as reviewAgentCliHelp } from "./agent-cli-TJ7_5_B3.js";
16
16
  import { existsSync, mkdirSync, renameSync, writeFileSync } from "node:fs";
17
17
  import { realpath } from "node:fs/promises";
18
18
  import path from "node:path";
@@ -1787,6 +1787,20 @@ var require_delete_log_property = /* @__PURE__ */ __commonJSMin(((exports, modul
1787
1787
  //#endregion
1788
1788
  //#region ../../node_modules/fast-copy/dist/cjs/index.cjs
1789
1789
  var require_cjs = /* @__PURE__ */ __commonJSMin(((exports) => {
1790
+ /**
1791
+ * Error thrown when the copier traverses deeper than the configured `maxDepth`.
1792
+ *
1793
+ * @note
1794
+ * Extends `RangeError` for backwards compatibility, since exceeding the maximum depth
1795
+ * previously surfaced as a native `RangeError` from stack exhaustion.
1796
+ */
1797
+ var MaxDepthExceededError = class extends RangeError {
1798
+ constructor(maxDepth) {
1799
+ super(`Maximum copy depth of ${String(maxDepth)} exceeded; the value copied is nested too deeply.`);
1800
+ this.maxDepth = maxDepth;
1801
+ this.name = "MaxDepthExceededError";
1802
+ }
1803
+ };
1790
1804
  const toStringFunction = Function.prototype.toString;
1791
1805
  const toStringObject = Object.prototype.toString;
1792
1806
  /**
@@ -1951,10 +1965,19 @@ var require_cjs = /* @__PURE__ */ __commonJSMin(((exports) => {
1951
1965
  function copySetStrict(set, state) {
1952
1966
  return copyOwnPropertiesStrict(set, copySetLoose(set, state), state);
1953
1967
  }
1968
+ /**
1969
+ * The maximum depth traversed when none is provided.
1970
+ *
1971
+ * @note
1972
+ * This is deliberately below the depth at which the native call stack is exhausted
1973
+ * (~1875 for strict copies in node), so that untrusted, deeply-nested values fail with a
1974
+ * catchable, descriptive error instead of a raw `RangeError`.
1975
+ */
1976
+ const DEFAULT_MAX_DEPTH = 1e3;
1954
1977
  function createDefaultCache() {
1955
1978
  return /* @__PURE__ */ new WeakMap();
1956
1979
  }
1957
- function getOptions({ createCache: createCacheOverride, methods: methodsOverride, strict }) {
1980
+ function getOptions({ createCache: createCacheOverride, maxDepth, methods: methodsOverride, strict }) {
1958
1981
  const defaultMethods = {
1959
1982
  array: strict ? copyArrayStrict : copyArrayLoose,
1960
1983
  arrayBuffer: copyArrayBuffer,
@@ -1976,6 +1999,7 @@ var require_cjs = /* @__PURE__ */ __commonJSMin(((exports) => {
1976
1999
  return {
1977
2000
  createCache,
1978
2001
  copiers,
2002
+ maxDepth: maxDepth == null ? DEFAULT_MAX_DEPTH : maxDepth,
1979
2003
  methods,
1980
2004
  strict: Boolean(strict)
1981
2005
  };
@@ -2024,25 +2048,32 @@ var require_cjs = /* @__PURE__ */ __commonJSMin(((exports) => {
2024
2048
  * - `strict` mode to copy all properties with their descriptors
2025
2049
  */
2026
2050
  function createCopier(options = {}) {
2027
- const { createCache, copiers } = getOptions(options);
2051
+ const { createCache, copiers, maxDepth } = getOptions(options);
2028
2052
  const { Array: copyArray, Object: copyObject } = copiers;
2029
2053
  function copier(value, state) {
2030
2054
  state.prototype = state.Constructor = void 0;
2031
2055
  if (!value || typeof value !== "object") return value;
2032
2056
  if (state.cache.has(value)) return state.cache.get(value);
2057
+ if (++state.depth > maxDepth) throw new MaxDepthExceededError(maxDepth);
2033
2058
  state.prototype = Object.getPrototypeOf(value);
2034
2059
  state.Constructor = state.prototype && state.prototype.constructor;
2035
- if (!state.Constructor || state.Constructor === Object) return copyObject(value, state);
2036
- if (Array.isArray(value)) return copyArray(value, state);
2037
- const tagSpecificCopier = copiers[getTag(value)];
2038
- if (tagSpecificCopier) return tagSpecificCopier(value, state);
2039
- return typeof value.then === "function" ? value : copyObject(value, state);
2060
+ let clone;
2061
+ if (!state.Constructor || state.Constructor === Object) clone = copyObject(value, state);
2062
+ else if (Array.isArray(value)) clone = copyArray(value, state);
2063
+ else {
2064
+ const tagSpecificCopier = copiers[getTag(value)];
2065
+ if (tagSpecificCopier) clone = tagSpecificCopier(value, state);
2066
+ else clone = typeof value.then === "function" ? value : copyObject(value, state);
2067
+ }
2068
+ --state.depth;
2069
+ return clone;
2040
2070
  }
2041
2071
  return function copy(value) {
2042
2072
  return copier(value, {
2043
2073
  Constructor: void 0,
2044
2074
  cache: createCache(),
2045
2075
  copier,
2076
+ depth: 0,
2046
2077
  prototype: void 0
2047
2078
  });
2048
2079
  };
@@ -2053,7 +2084,12 @@ var require_cjs = /* @__PURE__ */ __commonJSMin(((exports) => {
2053
2084
  * original property descriptors on both objects and arrays.
2054
2085
  */
2055
2086
  const copyStrict = createCopier({ strict: true });
2056
- exports.copy = createCopier();
2087
+ /**
2088
+ * Copy an value deeply as much as possible.
2089
+ */
2090
+ const copy = createCopier();
2091
+ exports.MaxDepthExceededError = MaxDepthExceededError;
2092
+ exports.copy = copy;
2057
2093
  exports.copyStrict = copyStrict;
2058
2094
  exports.createCopier = createCopier;
2059
2095
  }));
@@ -3898,8 +3934,8 @@ function resolveTraceFile(env) {
3898
3934
  }
3899
3935
  if (traceEnabled) {
3900
3936
  process.on("exit", flushTrace);
3901
- setLocalVcsCommandObserver({ start({ file, args, cwd }) {
3902
- const handle = startSpan(commandSpanName(file, args), { detail: commandDetail(file, args, cwd) });
3937
+ setLocalVcsCommandObserver({ start({ tool, args, cwd }) {
3938
+ const handle = startSpan(commandSpanName(tool, args), { detail: commandDetail(tool, args, cwd) });
3903
3939
  return ({ ok }) => ok ? handle.end() : handle.fail();
3904
3940
  } });
3905
3941
  }
@@ -4297,7 +4333,7 @@ async function runReviewCli(input) {
4297
4333
  if (!Number.isInteger(port) || port < 0 || port > 65535) throw new ReviewCliUsageError("--port must be an integer between 0 and 65535.");
4298
4334
  const stateDir = reviewServerStateDir(authoringEnv(options.stateDir));
4299
4335
  if (options.detach) {
4300
- const { ensureBackgroundServer } = await import("./background-server-DLSDogkI.js");
4336
+ const { ensureBackgroundServer } = await import("./background-server-BSYXD9au.js");
4301
4337
  const { discovery, started } = await ensureBackgroundServer({
4302
4338
  stateDir,
4303
4339
  env,
@@ -4319,7 +4355,7 @@ async function runReviewCli(input) {
4319
4355
  process.once("SIGINT", stop);
4320
4356
  process.once("SIGTERM", stop);
4321
4357
  process.once(DESKTOP_TAKEOVER_SIGNAL, takeover);
4322
- const { HeadlessServerBusyError, runHeadlessServer } = await import("./headless-host-CVBOQyRS.js");
4358
+ const { HeadlessServerBusyError, runHeadlessServer } = await import("./headless-host-BEe-aFtB.js");
4323
4359
  try {
4324
4360
  await runHeadlessServer({
4325
4361
  stateDir,
@@ -4356,7 +4392,7 @@ async function runReviewCli(input) {
4356
4392
  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) => {
4357
4393
  const options = command.optsWithGlobals();
4358
4394
  const stateDir = reviewServerStateDir(authoringEnv(options.stateDir));
4359
- const { headlessServerOwner, stopBackgroundServer } = await import("./background-server-DLSDogkI.js");
4395
+ const { headlessServerOwner, stopBackgroundServer } = await import("./background-server-BSYXD9au.js");
4360
4396
  const owner = await headlessServerOwner(stateDir);
4361
4397
  const discovery = await readReviewServerDiscovery(stateDir);
4362
4398
  if (owner === void 0) {
@@ -4384,7 +4420,7 @@ async function runReviewCli(input) {
4384
4420
  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.`);
4385
4421
  const inUse = `A Whiteboard server is using ${stateDir}. Stop it first, then run whiteboard server reset-id again.`;
4386
4422
  if (await desktopUsesHome(stateDir, env)) return refuse(inUse, options.json);
4387
- const [{ openReviewProfile }, { withHeadlessServerLock }] = await Promise.all([import("./profile-CYPmvBoA.js"), import("./headless-host-CVBOQyRS.js")]);
4423
+ const [{ openReviewProfile }, { withHeadlessServerLock }] = await Promise.all([import("./profile-Dbyidrcr.js"), import("./headless-host-BEe-aFtB.js")]);
4388
4424
  const reset = await withHeadlessServerLock(await realpath(stateDir), async () => {
4389
4425
  const local = await openReviewProfile(stateDir, { manageWorkspaces: false });
4390
4426
  try {
@@ -4406,7 +4442,7 @@ async function runReviewCli(input) {
4406
4442
  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) => {
4407
4443
  const options = command.optsWithGlobals();
4408
4444
  const stateDir = reviewServerStateDir(authoringEnv(options.stateDir));
4409
- const { remoteAttach } = await import("./remote-attach-CEJ-kvDY.js");
4445
+ const { remoteAttach } = await import("./remote-attach-BeKuMm_x.js");
4410
4446
  let attach;
4411
4447
  try {
4412
4448
  attach = await remoteAttach({
@@ -4433,7 +4469,7 @@ async function runReviewCli(input) {
4433
4469
  });
4434
4470
  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) => {
4435
4471
  const { json, groups } = command.optsWithGlobals();
4436
- const { ensureRemoteExtensions } = await import("./remote-extensions-GN8Gf_qG.js");
4472
+ const { ensureRemoteExtensions } = await import("./remote-extensions-CX2fAec1.js");
4437
4473
  const result = await ensureRemoteExtensions({
4438
4474
  env,
4439
4475
  groups: groups?.split(",").map((group) => group.trim())
@@ -4448,7 +4484,7 @@ async function runReviewCli(input) {
4448
4484
  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) => {
4449
4485
  const options = command.optsWithGlobals();
4450
4486
  const stateDir = reviewServerStateDir(authoringEnv(options.stateDir));
4451
- const { remoteUninstall } = await import("./remote-uninstall-DW7Gdryh.js");
4487
+ const { remoteUninstall } = await import("./remote-uninstall-Y3S0YmJs.js");
4452
4488
  let deleteReviews = options.deleteReviews ? true : options.keepReviews ? false : void 0;
4453
4489
  if (deleteReviews === void 0 && !options.json && input.stdin?.isTTY) {
4454
4490
  const prompt = createInterface$1({
@@ -4522,7 +4558,7 @@ async function runReviewCli(input) {
4522
4558
  const writeAppEvent = (event, options) => {
4523
4559
  if (options.json) input.stdout.write(`${JSON.stringify(event)}\n`);
4524
4560
  else if (event.action === "launch") input.stdout.write(event.state === "running" ? "Whiteboard Desktop is already running.\n" : options.focus ? "Whiteboard Desktop is ready.\n" : "Whiteboard Desktop is ready in the background. Pass --focus to bring it forward.\n");
4525
- else input.stdout.write(`Whiteboard Desktop is showing "${event.title}".\n`);
4561
+ else input.stdout.write(`Whiteboard Desktop is showing "${boardTitle(event.title)}".\n`);
4526
4562
  };
4527
4563
  const pickReview = async (options) => {
4528
4564
  const event = await runtime.runReviewAppPick({
@@ -4670,14 +4706,14 @@ async function runReviewCli(input) {
4670
4706
  command.showHelpAfterError(false).error("Markdown review migration is no longer supported. Ask your agent to migrate your old Whiteboard reviews.");
4671
4707
  });
4672
4708
  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) => {
4673
- const { runShareCli } = await import("./cli-R4yfa5DT.js");
4709
+ const { runShareCli } = await import("./cli-BOnV4cCh.js");
4674
4710
  state.exitCode = await runShareCli({
4675
4711
  ...input,
4676
4712
  ...options,
4677
4713
  env: authoringEnv()
4678
4714
  });
4679
4715
  }).command("revoke <share-id>").description("Revoke future downloads of a share"), "plain").action(async (shareId, options) => {
4680
- const { runShareCli } = await import("./cli-R4yfa5DT.js");
4716
+ const { runShareCli } = await import("./cli-BOnV4cCh.js");
4681
4717
  state.exitCode = await runShareCli({
4682
4718
  ...input,
4683
4719
  ...options,
@@ -4758,7 +4794,7 @@ async function runReviewCli(input) {
4758
4794
  return commands;
4759
4795
  } });
4760
4796
  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) => {
4761
- const { runReviewAgentCli } = await import("./agent-cli-C6SPSII0.js");
4797
+ const { runReviewAgentCli } = await import("./agent-cli-BglNRDZN.js");
4762
4798
  state.exitCode = await runReviewAgentCli({
4763
4799
  ...input,
4764
4800
  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-DsM3jO8G.js";
3
+ import "./src-DYZLCUP_.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-Cuc9FSzu.js");
20
+ const { runReviewCli } = await import("./cli-runner-cRY5NOno.js");
21
21
  return runReviewCli({
22
22
  argv,
23
23
  cliPaths: {
@@ -45,7 +45,8 @@ async function maybeDelegateToDesktopCli(argv) {
45
45
  ].includes(argument) || /^--state-dir(?:=|$)/.test(argument)) || env.DEV_REVIEW_SERVER_DIR?.trim()) return null;
46
46
  const ownPath = fileURLToPath(import.meta.url);
47
47
  if (!/[\\/]dist[\\/]cli\.js$/.test(ownPath)) return null;
48
- const devHome = env.DEV_REVIEW_HOME?.trim() ? path.resolve(env.DEV_REVIEW_HOME.trim()) : path.join(os.homedir(), ".dev");
48
+ const channel = env.DEV_FAST_REVIEW_CHANNEL?.trim() || env.DEV_REVIEW_INSTANCE?.trim();
49
+ const devHome = env.DEV_REVIEW_HOME?.trim() ? path.resolve(env.DEV_REVIEW_HOME.trim()) : path.join(os.homedir(), channel === "preview" ? ".dev-preview" : ".dev");
49
50
  let cliPath;
50
51
  let runtimePath;
51
52
  const discoveryFile = selectedDiscoveryFile(devHome, env);