@dev.fast/whiteboard 0.2.5-preview.20261013.113 → 0.2.5-preview.20261015.115

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 (127) hide show
  1. package/THIRD_PARTY_NOTICES.md +29 -0
  2. package/dist/{account-alias-D0hF0sKk.js → account-alias-ChZR3RIU.js} +94 -8
  3. package/dist/{agent-cli-04xbDj23.js → agent-cli-CR2PnA9G.js} +9 -12
  4. package/dist/agent-cli-CRPPmS3l.js +2 -0
  5. package/dist/{agent-client-P3J4y8Zn.js → agent-client-DVd-prfp.js} +31 -14
  6. package/dist/{authoring-tools-fdA0RPIT.js → authoring-tools-BXKEwhfi.js} +5 -5
  7. package/dist/background-server-Cw7CcfaZ.js +156 -0
  8. package/dist/background-server-D2Rrr_e2.js +2 -0
  9. package/dist/build-info.json +1 -1
  10. package/dist/{cli-C0ziP6hg.js → cli-CSrz_wGk.js} +1 -1
  11. package/dist/{cli-runner-C3men22A.js → cli-runner-PlGqBeMw.js} +368 -252
  12. package/dist/cli.js +3 -2
  13. package/dist/{client-DzcSt6pV.js → client-DhZzPePs.js} +5 -3
  14. package/dist/{process-error-telemetry-BCBYp2t4.js → headless-host-CVXTSIlx.js} +3285 -2585
  15. package/dist/headless-host-Cp37C-v0.js +2 -0
  16. package/dist/input-error-OLgB_h31.js +11 -0
  17. package/dist/{local-data-Dy1pNtLF.js → local-data-yfLp5PiJ.js} +113 -62
  18. package/dist/{mcp-B8kuRSXM.js → mcp-DBbDwG4y.js} +4 -4
  19. package/dist/{package-paths-B9PDoAl6.js → package-paths-BsuI4WUf.js} +1 -1
  20. package/dist/profile-BSOkA6A-.js +2 -0
  21. package/dist/{profile-tbGWuWHu.js → profile-pAzg7fxC.js} +3 -3
  22. package/dist/remote-attach-BAt2eZx3.js +137 -0
  23. package/dist/remote-extensions-2Cxf3hMo.js +2 -0
  24. package/dist/remote-extensions-BiNx6NQK.js +323 -0
  25. package/dist/remote-language-server-DQgWUNeV.js +252 -0
  26. package/dist/remote-uninstall-DLtDG0o7.js +153 -0
  27. package/dist/{request-origin-DA4K1qWY.js → request-origin-DaRIV3cB.js} +5 -2
  28. package/dist/review-app-launcher-D7I4DGoH.js +222 -0
  29. package/dist/{review-home-paths-Y9mYWkCI.js → review-home-paths-zwevIq4I.js} +7 -7
  30. package/dist/{review-telemetry-CmM6tCbj.js → review-telemetry-BqB4CiTD.js} +5 -5
  31. package/dist/{s3-SzLpeCG6.js → s3-77-qkEhs.js} +1 -1
  32. package/dist/{s3-config-BgxkSoOy.js → s3-config-0xuCh-F_.js} +1 -1
  33. package/dist/{s3-config-CAxhO9u_.js → s3-config-DDWWMh5x.js} +5 -1
  34. package/dist/server/desktop-host.js +1663 -266
  35. package/dist/{desktop-discovery-Ba3dD5Cn.js → server-discovery-CifQ3yQS.js} +88 -3
  36. package/dist/sharing/index.d.ts +88 -32
  37. package/dist/sharing/index.js +1 -1
  38. package/dist/{src-DiHP11l3.js → src-BMPuxWv4.js} +6 -11
  39. package/dist/{src-WCbabvdS.js → src-Bgvf1sEN.js} +128 -8
  40. package/dist/{stored-document-migration-u4L_1VZa.js → stored-document-migration-CFOvjRwW.js} +5 -18
  41. package/dist/{tool-failure-CJHBCHMh.js → tool-failure-BXWzQG8T.js} +2 -2
  42. package/instructions/authoring.md +16 -12
  43. package/instructions/file-lenses.md +4 -2
  44. package/package.json +9 -14
  45. package/src/agent-connect.ts +507 -0
  46. package/src/agent-selection.ts +4 -0
  47. package/src/ask/agents.ts +59 -13
  48. package/src/ask/pi-mcp.ts +7 -3
  49. package/src/ask/thread-state.ts +9 -4
  50. package/src/ask/thread.ts +88 -50
  51. package/src/ask/threads.ts +53 -7
  52. package/src/cli-install.ts +3 -1
  53. package/src/cli-runner.ts +472 -81
  54. package/src/cli.ts +1 -1
  55. package/src/connect-agents.ts +285 -0
  56. package/src/connect-prompts.ts +60 -6
  57. package/src/desktop-discovery.ts +24 -0
  58. package/src/pinned-download.ts +118 -0
  59. package/src/remote-attach.ts +130 -0
  60. package/src/remote-extensions.ts +359 -0
  61. package/src/remote-language-server.ts +428 -0
  62. package/src/remote-toolchains.ts +107 -0
  63. package/src/remote-uninstall.ts +280 -0
  64. package/src/review-api/README.md +1 -3
  65. package/src/review-api/agent-cli.ts +4 -4
  66. package/src/review-api/agent-client.ts +23 -0
  67. package/src/review-api/ask-history.ts +31 -16
  68. package/src/review-api/authoring-tools.ts +1 -1
  69. package/src/review-api/blocks/index.ts +2 -4
  70. package/src/review-api/document.ts +1 -4
  71. package/src/review-api/http.ts +265 -237
  72. package/src/review-api/local-data.ts +18 -3
  73. package/src/review-api/onboarding.ts +478 -0
  74. package/src/review-api/origin.ts +7 -0
  75. package/src/review-api/recent-folders.ts +173 -0
  76. package/src/review-api/store.ts +56 -10
  77. package/src/review-api/workspaces.ts +56 -11
  78. package/src/review-app-launcher.ts +61 -3
  79. package/src/review-stack.ts +162 -36
  80. package/src/server/background-server.ts +250 -0
  81. package/src/server/desktop-host-shutdown.ts +24 -0
  82. package/src/server/desktop-host.ts +39 -24
  83. package/src/server/desktop-server.ts +68 -118
  84. package/src/server/global-verb-relay.ts +16 -0
  85. package/src/server/headless-host.ts +128 -3
  86. package/src/server/hono-http.ts +6 -1
  87. package/src/server/legacy-tutorial-cleanup.ts +38 -0
  88. package/src/server/review-gateway-hosts.ts +379 -0
  89. package/src/server/review-gateway-list.ts +109 -0
  90. package/src/server/review-gateway-memory.ts +130 -0
  91. package/src/server/review-gateway-pushes.ts +142 -0
  92. package/src/server/review-gateway-streams.ts +415 -0
  93. package/src/server/review-gateway-transport.ts +204 -0
  94. package/src/server/review-gateway.ts +433 -0
  95. package/src/server/review-server-core.ts +36 -6
  96. package/src/server-discovery.ts +11 -0
  97. package/src/sharing/import.ts +1 -0
  98. package/src/ui-telemetry-events.ts +5 -1
  99. package/src/vsix.ts +122 -0
  100. package/dist/agent-cli-BR_Jw5BL.js +0 -2
  101. package/dist/headless-host-BLLJ7Dyu.js +0 -95
  102. package/dist/profile-aMSw1oCM.js +0 -2
  103. package/dist/server-discovery-ZVi8Ay8J.js +0 -66
  104. package/src/fixtures/blocks/tutorial.json +0 -14
  105. package/src/review-api/blocks/tutorial.ts +0 -23
  106. package/src/server/tutorial-service.ts +0 -217
  107. package/tutorial/document.json +0 -216
  108. package/tutorial/lenses.json +0 -10
  109. package/tutorial/runtime-manifest.json +0 -11
  110. package/tutorial/sample-service/package.json +0 -9
  111. package/tutorial/sample-service/src/api/checkout-api.ts +0 -17
  112. package/tutorial/sample-service/src/app.ts +0 -26
  113. package/tutorial/sample-service/src/database/drizzle-orm.d.ts +0 -16
  114. package/tutorial/sample-service/src/database/schema.ts +0 -9
  115. package/tutorial/sample-service/src/fulfillment/fulfillment-queue.ts +0 -15
  116. package/tutorial/sample-service/src/fulfillment/fulfillment-worker.ts +0 -24
  117. package/tutorial/sample-service/src/inventory/forklift-mood.ts +0 -4
  118. package/tutorial/sample-service/src/inventory/inventory-service.ts +0 -28
  119. package/tutorial/sample-service/src/inventory/warehouse-lore.ts +0 -11
  120. package/tutorial/sample-service/src/orders/order-service.ts +0 -35
  121. package/tutorial/sample-service/src/orders/order.ts +0 -43
  122. package/tutorial/sample-service/src/orders/orders-repository.ts +0 -24
  123. package/tutorial/sample-service/src/payments/fraud-vibes.ts +0 -4
  124. package/tutorial/sample-service/src/payments/payment-gateway.ts +0 -29
  125. package/tutorial/sample-service/src/payments/raccoon-ledger.ts +0 -9
  126. package/tutorial/sample-service/src/shipping/shipping-gateway.ts +0 -15
  127. package/tutorial/sample-service/tsconfig.json +0 -12
@@ -1,6 +1,8 @@
1
1
  import { a as jsonNumber, n as isJsonObject, s as jsonProperty, u as parseJsonText } from "./src-X9phtB2j.js";
2
- import { n as parseReviewDesktopDiscovery } from "./src-WCbabvdS.js";
3
- import { a as reviewInstancesDir, o as reviewLegacyDiscoveryPath, r as reviewDevInstanceKey, t as reviewDefaultInstancePath } from "./review-home-paths-Y9mYWkCI.js";
2
+ import { n as parseReviewDesktopDiscovery } from "./src-Bgvf1sEN.js";
3
+ import { a as reviewInstancesDir, o as reviewLegacyDiscoveryPath, r as reviewDevInstanceKey, t as reviewDefaultInstancePath } from "./review-home-paths-zwevIq4I.js";
4
+ import { M as devReviewHome, O as processIsAlive, j as DEV_REVIEW_HOME_ENV } from "./s3-config-DDWWMh5x.js";
5
+ import { z } from "zod";
4
6
  import { mkdir, readFile, readdir, rm, writeFile } from "node:fs/promises";
5
7
  import path from "node:path";
6
8
  //#region src/desktop-discovery.ts
@@ -84,6 +86,17 @@ function reviewInstanceIdentity(env) {
84
86
  if (appVersion) identity.appVersion = appVersion;
85
87
  return identity;
86
88
  }
89
+ /** Any live Desktop holds the store, attached window or not. Only a gone
90
+ * process counts as stopped: a paused or busy one may not answer /health. */
91
+ async function desktopUsesHome(home, env = process.env) {
92
+ const desktops = await readReviewInstances({ env: {
93
+ ...env,
94
+ [DEV_REVIEW_HOME_ENV]: home
95
+ } });
96
+ const [problem] = desktops.broken.values();
97
+ if (problem) throw new Error(`Cannot tell whether a Whiteboard Desktop is using ${home}: ${problem.message}`);
98
+ return desktops.instances.some(({ discovery }) => [discovery.appPid, discovery.serverPid].some((pid) => processIsAlive(pid)));
99
+ }
87
100
  /** Records by key, plus the files that could not be read, by key. */
88
101
  async function readReviewInstances(dependencies) {
89
102
  const env = dependencies.env ?? process.env;
@@ -161,4 +174,76 @@ function reviewInstanceStartHint(selection) {
161
174
  return checkout ? `Start it with \`pnpm dev\` in ${checkout}` : "Start it with `pnpm dev` in its checkout";
162
175
  }
163
176
  //#endregion
164
- export { readDefaultReviewInstance as a, reviewInstanceIdentity as c, selectReviewInstance as d, writeDefaultReviewInstance as f, healthyReviewInstance as i, reviewInstanceStartHint as l, ReviewInstanceUnavailableError as n, readReviewInstances as o, clearDefaultReviewInstance as r, requireHealthyReviewDesktop as s, REVIEW_INSTANCE_ENV as t, reviewInstanceUnavailable as u };
177
+ //#region src/server-discovery.ts
178
+ const discoverySchema = z.object({
179
+ version: z.literal(1),
180
+ instanceId: z.uuid(),
181
+ url: z.url().refine((value) => {
182
+ const url = new URL(value);
183
+ return url.protocol === "http:" && url.hostname === "127.0.0.1";
184
+ }),
185
+ serverPid: z.number().int().positive(),
186
+ token: z.string().min(1),
187
+ startedBy: z.enum([
188
+ "user",
189
+ "cli",
190
+ "desktop"
191
+ ]).default("user")
192
+ });
193
+ function reviewServerStateDir(env = process.env) {
194
+ return env.DEV_REVIEW_SERVER_DIR?.trim() ? path.resolve(env.DEV_REVIEW_SERVER_DIR.trim()) : devReviewHome(env);
195
+ }
196
+ function reviewServerDiscoveryPath(stateDir) {
197
+ return path.join(stateDir, "review-server", "server.json");
198
+ }
199
+ /** Sent by a Desktop taking over the server's state directory. */
200
+ const DESKTOP_TAKEOVER_SIGNAL = "SIGUSR2";
201
+ function headlessServerLockPath(stateDir) {
202
+ return path.join(stateDir, "headless-server.lock");
203
+ }
204
+ async function readReviewServerDiscovery(stateDir) {
205
+ let source;
206
+ try {
207
+ source = await readFile(reviewServerDiscoveryPath(stateDir), "utf8");
208
+ } catch (error) {
209
+ if (error instanceof Error && "code" in error && error.code === "ENOENT") return null;
210
+ throw error;
211
+ }
212
+ try {
213
+ return discoverySchema.parse(JSON.parse(source));
214
+ } catch {
215
+ throw new Error(`Whiteboard server discovery in ${stateDir} is invalid or incompatible. Restart it with this version of whiteboard server start.`);
216
+ }
217
+ }
218
+ async function reviewServerIsHealthy(discovery) {
219
+ return await readReviewServerHealth(discovery) !== null;
220
+ }
221
+ /** The recorded server's /health, or null when another or none answers. */
222
+ async function readReviewServerHealth(discovery) {
223
+ try {
224
+ const response = await fetch(`${discovery.url}/health`, {
225
+ headers: { "x-review-token": discovery.token },
226
+ signal: AbortSignal.timeout(1500)
227
+ });
228
+ if (!response.ok) return null;
229
+ const health = z.object({
230
+ ok: z.literal(true),
231
+ instanceId: z.string(),
232
+ serverId: z.string().optional(),
233
+ serverPid: z.number().optional(),
234
+ version: z.string().optional(),
235
+ commit: z.string().nullable().optional()
236
+ }).safeParse(await response.json());
237
+ return health.success && health.data.instanceId === discovery.instanceId ? health.data : null;
238
+ } catch {
239
+ return null;
240
+ }
241
+ }
242
+ function desktopOwnsHome(stateDir) {
243
+ return `Whiteboard Desktop is serving ${stateDir}, so no other Whiteboard server can start there. Quit Desktop, or choose another --state-dir.`;
244
+ }
245
+ function serverNotReady(stateDir) {
246
+ return /* @__PURE__ */ new Error(`Whiteboard server is not ready in ${stateDir}. Run whiteboard server start --state-dir ${JSON.stringify(stateDir)}, then retry.`);
247
+ }
248
+ //#endregion
249
+ export { reviewInstanceIdentity as _, readReviewServerHealth as a, selectReviewInstance as b, reviewServerStateDir as c, ReviewInstanceUnavailableError as d, clearDefaultReviewInstance as f, requireHealthyReviewDesktop as g, readDefaultReviewInstance as h, readReviewServerDiscovery as i, serverNotReady as l, healthyReviewInstance as m, desktopOwnsHome as n, reviewServerDiscoveryPath as o, desktopUsesHome as p, headlessServerLockPath as r, reviewServerIsHealthy as s, DESKTOP_TAKEOVER_SIGNAL as t, REVIEW_INSTANCE_ENV as u, reviewInstanceStartHint as v, writeDefaultReviewInstance as x, reviewInstanceUnavailable as y };
@@ -242,8 +242,10 @@ interface ReviewApiSummary {
242
242
  origin?: {
243
243
  branch?: string;
244
244
  baseRef?: string;
245
+ baseBranch?: string;
245
246
  pullRequestNumber?: number;
246
247
  pullRequestUrl?: string;
248
+ pullRequestTitle?: string;
247
249
  revision?: string;
248
250
  };
249
251
  /** Shared imports have separate local checkouts but retain their remote identity. */
@@ -266,6 +268,13 @@ interface ReviewApiSummary {
266
268
  trashedAt: string | null;
267
269
  /** An agent holds a live lease; absent on shared reviews. */
268
270
  working?: boolean;
271
+ host?: string;
272
+ hostState?: Exclude<ReviewGatewayHostState["state"], "duplicate">;
273
+ available?: {
274
+ sourceWindows: boolean;
275
+ /** Why they are off: the host's sentence, or its language features detail. */
276
+ reason?: string;
277
+ };
269
278
  }
270
279
  interface ReviewSourceEntry {
271
280
  path: string;
@@ -281,6 +290,57 @@ interface ReviewLanguageEnvironment {
281
290
  /** Present only when the language checkout is unavailable, not while preparing. */
282
291
  readonly issue?: string;
283
292
  }
293
+ declare const ReviewGatewayHostSchema: z.ZodObject<{
294
+ alias: z.ZodString;
295
+ endpoint: z.ZodOptional<z.ZodObject<{
296
+ url: z.ZodString;
297
+ token: z.ZodString;
298
+ }, z.core.$strict>>;
299
+ problem: z.ZodOptional<z.ZodObject<{
300
+ state: z.ZodEnum<{
301
+ unreachable: "unreachable";
302
+ "not-installed": "not-installed";
303
+ "auth-failed": "auth-failed";
304
+ unsupported: "unsupported";
305
+ incompatible: "incompatible";
306
+ }>;
307
+ detail: z.ZodString;
308
+ }, z.core.$strict>>;
309
+ declined: z.ZodOptional<z.ZodLiteral<true>>;
310
+ asking: z.ZodOptional<z.ZodString>;
311
+ installFailure: z.ZodOptional<z.ZodString>;
312
+ target: z.ZodOptional<z.ZodEnum<{
313
+ "linux-x64": "linux-x64";
314
+ "linux-arm64": "linux-arm64";
315
+ }>>;
316
+ installing: z.ZodOptional<z.ZodObject<{
317
+ step: z.ZodEnum<{
318
+ preparing: "preparing";
319
+ "waiting-for-lock": "waiting-for-lock";
320
+ package: "package";
321
+ done: "done";
322
+ }>;
323
+ }, z.core.$strict>>;
324
+ languageFeatures: z.ZodOptional<z.ZodBoolean>;
325
+ languageFeaturesDetail: z.ZodOptional<z.ZodString>;
326
+ languageGroups: z.ZodOptional<z.ZodArray<z.ZodObject<{
327
+ group: z.ZodString;
328
+ installed: z.ZodBoolean;
329
+ detail: z.ZodOptional<z.ZodString>;
330
+ }, z.core.$strict>>>;
331
+ }, z.core.$strict>;
332
+ type ReviewGatewayHost = z.infer<typeof ReviewGatewayHostSchema>;
333
+ interface ReviewGatewayHostState {
334
+ alias: string;
335
+ serverId?: string;
336
+ state: "connecting" | "online" | "offline" | "incompatible" | "duplicate" | "unreachable" | "not-installed" | "auth-failed" | "unsupported" | "installing";
337
+ detail?: string;
338
+ installCommand?: string;
339
+ languageFeatures?: boolean;
340
+ languageFeaturesDetail?: string;
341
+ languageGroups?: ReviewGatewayHost["languageGroups"];
342
+ declined?: true;
343
+ }
284
344
  //#endregion
285
345
  //#region src/source.d.ts
286
346
  /** The repository and commits one source reference was read from. A
@@ -425,19 +485,6 @@ interface SectionBlock {
425
485
  children: Block[];
426
486
  }
427
487
  //#endregion
428
- //#region src/review-api/blocks/tutorial.d.ts
429
- type TutorialContent = {
430
- kind: "keymap";
431
- } | {
432
- kind: "view";
433
- view: "diff";
434
- label: string;
435
- };
436
- type TutorialBlock = {
437
- id?: string;
438
- type: "tutorial";
439
- } & TutorialContent;
440
- //#endregion
441
488
  //#region src/review-api/blocks/index.d.ts
442
489
  /** Leaf kinds share one discriminated union so unknown types read as they always have. */
443
490
  declare const leafSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
@@ -635,7 +682,7 @@ declare const leafSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
635
682
  pins: any;
636
683
  }, z.core.$strict>], "type">;
637
684
  type LeafBlock = z.infer<typeof leafSchema>;
638
- type Block = LeafBlock | SectionBlock | CalloutBlock | TutorialBlock;
685
+ type Block = LeafBlock | SectionBlock | CalloutBlock;
639
686
  //#endregion
640
687
  //#region src/review-api/diff-lenses.d.ts
641
688
  declare const lensSchema: z.ZodObject<{
@@ -891,17 +938,7 @@ declare const editSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
891
938
  danger: "danger";
892
939
  }>>;
893
940
  children: z.ZodArray<z.ZodLazy<z.ZodType<Block, unknown, z.core.$ZodTypeInternals<Block, unknown>>>>;
894
- }, z.core.$strict>, z.ZodDiscriminatedUnion<[z.ZodObject<{
895
- type: z.ZodLiteral<"tutorial">;
896
- id: z.ZodOptional<z.ZodString>;
897
- kind: z.ZodLiteral<"keymap">;
898
941
  }, z.core.$strict>, z.ZodObject<{
899
- type: z.ZodLiteral<"tutorial">;
900
- id: z.ZodOptional<z.ZodString>;
901
- kind: z.ZodLiteral<"view">;
902
- view: z.ZodLiteral<"diff">;
903
- label: z.ZodString;
904
- }, z.core.$strict>], "kind">, z.ZodObject<{
905
942
  type: z.ZodDefault<z.ZodLiteral<"step">>;
906
943
  from: z.ZodString;
907
944
  to: z.ZodString;
@@ -1171,6 +1208,14 @@ declare const askOfferSchema: z.ZodObject<{
1171
1208
  }, z.core.$strip>>;
1172
1209
  }, z.core.$strip>;
1173
1210
  type AskOffer = z.infer<typeof askOfferSchema>;
1211
+ /** What an agent may do without asking: nothing that changes the checkout,
1212
+ * what its own judgment calls safe, or everything. */
1213
+ declare const askPermissionsSchema: z.ZodEnum<{
1214
+ "read-only": "read-only";
1215
+ auto: "auto";
1216
+ bypass: "bypass";
1217
+ }>;
1218
+ type AskPermissions = z.infer<typeof askPermissionsSchema>;
1174
1219
  type AskEntry = z.infer<typeof askEntrySchema>;
1175
1220
  /** A saved conversation, as the history list shows it. The transcript stays
1176
1221
  * with the agent; Whiteboard keeps what it needs to find and reopen it. */
@@ -1206,8 +1251,8 @@ declare class AskHistory {
1206
1251
  private assertWritable;
1207
1252
  private assertThreadWritable;
1208
1253
  save(record: AskRecord): void;
1209
- /** Whether reopening the conversation bypasses permissions. */
1210
- setBypass(id: string, bypass: boolean): void;
1254
+ /** What the agent may do without asking when the conversation reopens. */
1255
+ setPermissions(id: string, permissions: AskPermissions): void;
1211
1256
  /** Points a conversation at a new session, when its agent could not
1212
1257
  * reopen the one it had. */
1213
1258
  updateSession(id: string, sessionId: string): void;
@@ -1232,12 +1277,14 @@ declare class AskHistory {
1232
1277
  //#region src/review-api/store.d.ts
1233
1278
  /** Source identity displayed in the review header and Home, alongside immutable pins. */
1234
1279
  interface SnapshotOrigin {
1235
- /** Managed tutorial; readable by ID but excluded from the user catalog. */
1236
- tutorial?: boolean;
1237
1280
  branch?: string;
1238
1281
  baseRef?: string;
1282
+ /** The base the review's target names, such as the branch below it. */
1283
+ baseBranch?: string;
1239
1284
  pullRequestNumber?: number;
1240
1285
  pullRequestUrl?: string;
1286
+ /** As the forge reported it when the PR was attached. */
1287
+ pullRequestTitle?: string;
1241
1288
  /** The legacy review revision this version was imported from. */
1242
1289
  revision?: string;
1243
1290
  }
@@ -1299,6 +1346,7 @@ interface Result {
1299
1346
  interface ResolvedPullRequest {
1300
1347
  target: ReviewTarget;
1301
1348
  pins: Pins;
1349
+ /** Empty when the PR has none. */
1302
1350
  title: string;
1303
1351
  }
1304
1352
  interface ReviewProviders {
@@ -1310,6 +1358,8 @@ interface ReviewProviders {
1310
1358
  id?: string;
1311
1359
  preferred?: string;
1312
1360
  }): Promise<ResolvedPullRequest>;
1361
+ /** Undefined when the forge can't be read. */
1362
+ pullRequestTitle?(url: string): Promise<string | undefined>;
1313
1363
  resolveTarget?(target: ReviewTarget, pinned?: Pins): Promise<{
1314
1364
  target: ReviewTarget;
1315
1365
  pins: Pins;
@@ -1413,8 +1463,8 @@ declare class ReviewStore {
1413
1463
  private purgeExpired;
1414
1464
  read(id: string, version?: number): Snapshot;
1415
1465
  setDiffStats(pins: Pins, stats: NonNullable<ReviewApiSummary["diffStats"]>, mode?: "structural" | "textual"): void;
1416
- /** Managed records are discoverable even if their preparation stamp was lost. */
1417
- tutorialIds(): string[];
1466
+ /** Reviews of the tutorial earlier versions shipped, for their removal. */
1467
+ legacyTutorialIds(): string[];
1418
1468
  list(mode?: "structural" | "textual"): ReviewApiSummary[];
1419
1469
  /** One review's catalog entry, as review_list shows it. */
1420
1470
  summary(id: string): ReviewApiSummary | undefined;
@@ -1433,16 +1483,17 @@ declare class ReviewStore {
1433
1483
  }[];
1434
1484
  inspect(id: string, targetId?: string, version?: number): Element | {
1435
1485
  id: string | undefined;
1436
- type: "image" | "code" | "markdown" | "divider" | "code_peek" | "sequence" | "step" | "call_stack_diff" | "database_lens" | "trace_quote" | "software_map" | "flow_diagram" | "flow_node" | "flow_edge" | "section" | "callout" | "tutorial";
1486
+ type: "image" | "code" | "markdown" | "divider" | "code_peek" | "sequence" | "step" | "call_stack_diff" | "database_lens" | "trace_quote" | "software_map" | "flow_diagram" | "flow_node" | "flow_edge" | "section" | "callout";
1437
1487
  label: any;
1438
1488
  }[];
1439
- /** The host can seed a managed document; transport callers only supply a command. */
1489
+ /** The host can seed a document; transport callers only supply a command. */
1440
1490
  execute(input: unknown, initial?: {
1441
1491
  document: Block[];
1442
1492
  origin: SnapshotOrigin;
1443
1493
  }): Promise<Result>;
1444
1494
  /** Resolve a target-less PR create before it queues, when it will need it. */
1445
1495
  private startPullRequest;
1496
+ private startPullRequestTitle;
1446
1497
  /** Reviews whose PR is this one, newest version first. Summaries only. */
1447
1498
  private reviewsForPullRequest;
1448
1499
  private assertReusableReview;
@@ -1492,6 +1543,8 @@ declare class ReviewWorkspaces {
1492
1543
  * the Desktop's canvas tabs and source windows may still show them.
1493
1544
  */
1494
1545
  private releaseTrashed;
1546
+ /** Restored since the move to Trash: its checkouts may be in use. */
1547
+ private stillTrashed;
1495
1548
  private repositoryDirs;
1496
1549
  /** Resolves once no release of the review is pending, so an ensure cannot race it. */
1497
1550
  released(reviewId: string): Promise<void>;
@@ -1644,6 +1697,7 @@ declare class LocalReviewData {
1644
1697
  id?: string;
1645
1698
  preferred?: string;
1646
1699
  }): Promise<ResolvedPullRequest>;
1700
+ pullRequestTitle(url: string): Promise<string | undefined>;
1647
1701
  /** A registered checkout with a remote for host/owner/repo, and that remote. */
1648
1702
  private pullRequestCheckout;
1649
1703
  resolvePins(repositoryId: string, base: string, head: string): Promise<Pins>;
@@ -1803,6 +1857,7 @@ declare function validateShareBundle(bundle: ShareBundle): {
1803
1857
  baseRef?: string | undefined;
1804
1858
  pullRequestNumber?: number | undefined;
1805
1859
  pullRequestUrl?: string | undefined;
1860
+ pullRequestTitle?: string | undefined;
1806
1861
  revision?: string | undefined;
1807
1862
  } | undefined;
1808
1863
  };
@@ -1892,6 +1947,7 @@ declare class SharedReviewStore {
1892
1947
  baseRef?: string | undefined;
1893
1948
  pullRequestNumber?: number | undefined;
1894
1949
  pullRequestUrl?: string | undefined;
1950
+ pullRequestTitle?: string | undefined;
1895
1951
  revision?: string | undefined;
1896
1952
  } | undefined;
1897
1953
  };
@@ -1,2 +1,2 @@
1
- import { a as sharedReviewId, c as verifyShareRepository, i as SharedReviewStore, l as exportShare, n as ShareClient, o as validateShareBundle } from "../client-DzcSt6pV.js";
1
+ import { a as sharedReviewId, c as verifyShareRepository, i as SharedReviewStore, l as exportShare, n as ShareClient, o as validateShareBundle } from "../client-DhZzPePs.js";
2
2
  export { ShareClient, SharedReviewStore, exportShare, sharedReviewId, validateShareBundle, verifyShareRepository };
@@ -29,16 +29,6 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
29
29
  }) : target, mod));
30
30
  var __require = /* #__PURE__ */ (() => createRequire(import.meta.url))();
31
31
  //#endregion
32
- //#region src/review-api/input-error.ts
33
- /** Deliberately safe to show to API clients, unlike filesystem/provider errors. */
34
- var ReviewInputError = class extends Error {
35
- status;
36
- constructor(message, status = 400) {
37
- super(message);
38
- this.status = status;
39
- }
40
- };
41
- //#endregion
42
32
  //#region ../../node_modules/protocols/lib/index.js
43
33
  var require_lib$4 = /* @__PURE__ */ __commonJSMin(((exports, module) => {
44
34
  /**
@@ -1332,6 +1322,11 @@ async function resolveRevision(rootPath, revision) {
1332
1322
  if (!vcs) return null;
1333
1323
  return vcs.resolveRevision(revision);
1334
1324
  }
1325
+ async function defaultBranch(rootPath) {
1326
+ const vcs = await detectLocalVcs(rootPath);
1327
+ if (!vcs) return null;
1328
+ return vcs.defaultBranch();
1329
+ }
1335
1330
  async function currentHeadForKind(rootPath, kind) {
1336
1331
  return resolveRevisionForKind(rootPath, kind === "jj" ? "@" : "HEAD", kind);
1337
1332
  }
@@ -3683,4 +3678,4 @@ var require_adapter = /* @__PURE__ */ __commonJSMin(((exports, module) => {
3683
3678
  module.exports.checkSync = checkSync;
3684
3679
  })))();
3685
3680
  //#endregion
3686
- export { ReviewInputError as C, __toESM as E, setLocalVcsCommandObserver as S, __require as T, readFileAtCommit as _, diffFileSummaries as a, resolveRevision as b, diffTrees as c, gitAt as d, gitCommonDir as f, parseGitRemote as g, listTrackedFilesAtCommit as h, diff as i, diffWorkingTree as l, listCommitRange as m, createBlobBatchReader as n, diffFileSummariesTrees as o, jjRevisionIsConflicted as p, detectLocalVcs as r, diffFileSummariesWorkingTree as s, LocalVcsToolsMissingError as t, git as u, resolveRepoContext as v, __commonJSMin as w, splitGitPatchFiles as x, resolveRepoContextSync as y };
3681
+ export { setLocalVcsCommandObserver as C, __toESM as E, splitGitPatchFiles as S, __require as T, parseGitRemote as _, diff as a, resolveRepoContextSync as b, diffFileSummariesWorkingTree as c, git as d, gitAt as f, listTrackedFilesAtCommit as g, listCommitRange as h, detectLocalVcs as i, diffTrees as l, jjRevisionIsConflicted as m, createBlobBatchReader as n, diffFileSummaries as o, gitCommonDir as p, defaultBranch as r, diffFileSummariesTrees as s, LocalVcsToolsMissingError as t, diffWorkingTree as u, readFileAtCommit as v, __commonJSMin as w, resolveRevision as x, resolveRepoContext as y };
@@ -176,6 +176,7 @@ const ReviewStructuralDiffErrorSchema = z.object({
176
176
  z.union([StructuralDiffEventSchema, ReviewStructuralDiffErrorSchema]);
177
177
  const REVIEW_CLIENT_HEADER = "x-review-client";
178
178
  const REVIEW_CLIENT_REMOTE = "remote";
179
+ const REVIEW_HOST_HEADER = "x-review-host";
179
180
  const requiredString$1 = z.string({ error: "must be a string" }).refine((value) => value.trim().length > 0, "must be a string");
180
181
  const stringAllowEmpty = z.string({ error: "must be a string" });
181
182
  const positiveInteger = z.number({ error: "must be a positive integer" }).int("must be a positive integer").positive("must be a positive integer");
@@ -225,6 +226,7 @@ z.strictObject({
225
226
  });
226
227
  /** How embedded diffs lay out: base and head side by side, or one column. */
227
228
  const REVIEW_DIFF_LAYOUTS = ["split", "unified"];
229
+ const REVIEW_DIFF_LAYOUT_SCOPES = ["diff", "peek"];
228
230
  const REVIEW_THEME_CHOICES = [
229
231
  "dark",
230
232
  "light",
@@ -259,6 +261,16 @@ z.object({
259
261
  changed: z.boolean().optional(),
260
262
  error: z.string().optional()
261
263
  });
264
+ const REVIEW_REMOTE_WRAPPER_MARK = "# Written by Whiteboard Desktop, which replaces it with each install.";
265
+ const REVIEW_REMOTE_ATTACH_BEGIN = "WHITEBOARD-REMOTE-BEGIN";
266
+ const REVIEW_REMOTE_ATTACH_END = "WHITEBOARD-REMOTE-END";
267
+ const REVIEW_REMOTE_INSTALL_LOCK = "install.lock";
268
+ const REVIEW_REMOTE_AGENT_IDS = [
269
+ "claude",
270
+ "codex",
271
+ "opencode",
272
+ "pi"
273
+ ];
262
274
  const ReviewDesktopDiscoverySchema = z.object({
263
275
  version: z.literal(3, { error: "Unsupported Whiteboard Desktop discovery version" }),
264
276
  instanceId: requiredString$1,
@@ -280,6 +292,40 @@ const ReviewDesktopDiscoverySchema = z.object({
280
292
  appPath: requiredString$1.optional(),
281
293
  appVersion: requiredString$1.optional()
282
294
  });
295
+ const ReviewGatewayHostSchema = z.strictObject({
296
+ alias: requiredString$1,
297
+ endpoint: z.strictObject({
298
+ url: requiredString$1,
299
+ token: requiredString$1
300
+ }).optional(),
301
+ problem: z.strictObject({
302
+ state: z.enum([
303
+ "unreachable",
304
+ "not-installed",
305
+ "auth-failed",
306
+ "unsupported",
307
+ "incompatible"
308
+ ]),
309
+ detail: stringAllowEmpty
310
+ }).optional(),
311
+ declined: z.literal(true).optional(),
312
+ asking: requiredString$1.optional(),
313
+ installFailure: stringAllowEmpty.optional(),
314
+ target: z.enum(["linux-x64", "linux-arm64"]).optional(),
315
+ installing: z.strictObject({ step: z.enum([
316
+ "preparing",
317
+ "waiting-for-lock",
318
+ "package",
319
+ "done"
320
+ ]) }).optional(),
321
+ languageFeatures: z.boolean().optional(),
322
+ languageFeaturesDetail: stringAllowEmpty.optional(),
323
+ languageGroups: z.array(z.strictObject({
324
+ group: requiredString$1,
325
+ installed: z.boolean(),
326
+ detail: stringAllowEmpty.optional()
327
+ })).optional()
328
+ });
283
329
  z.strictObject({
284
330
  kind: z.enum([
285
331
  "git",
@@ -343,15 +389,14 @@ const ReviewErrorResponseSchema = z.strictObject({
343
389
  code: requiredString$1.optional(),
344
390
  retryable: z.boolean().optional()
345
391
  });
346
- z.strictObject({
347
- kind: z.literal("api"),
348
- reviewUuid: z.uuid({ error: "must be a UUID" }),
349
- title: stringAllowEmpty
350
- });
351
392
  const ReviewStackLayerSchema = z.strictObject({
352
393
  branch: requiredString$1,
353
- pullRequestNumber: positiveInteger,
394
+ pullRequestNumber: positiveInteger.nullable(),
354
395
  pullRequestUrl: absoluteUrlSchema.nullable(),
396
+ /** Where a new review of an unreviewed local branch starts. */
397
+ base: requiredString$1.nullable(),
398
+ /** A local branch's oldest commit subject. */
399
+ subject: requiredString$1.nullable(),
355
400
  reviewUuid: z.uuid({ error: "must be a UUID" }).nullable(),
356
401
  reviewTitle: stringAllowEmpty.nullable(),
357
402
  relation: z.enum([
@@ -370,6 +415,15 @@ const ReviewCliInstallTargetSchema = z.enum([
370
415
  "omp",
371
416
  "copilot"
372
417
  ], { error: "must be claude, codex, cursor, opencode, pi, omp, or copilot" });
418
+ const REVIEW_CLI_INSTALL_TARGET_LABELS = {
419
+ claude: "Claude Code",
420
+ codex: "Codex",
421
+ cursor: "Cursor",
422
+ opencode: "OpenCode",
423
+ pi: "Pi",
424
+ omp: "oh-my-pi",
425
+ copilot: "Copilot CLI"
426
+ };
373
427
  const ReviewCliInstallStampSchema = z.object({
374
428
  consent: z.enum([
375
429
  "granted",
@@ -461,6 +515,69 @@ z.strictObject({
461
515
  output: stringAllowEmpty,
462
516
  shimPath: requiredString$1.optional()
463
517
  });
518
+ /** An agent harness Whiteboard can connect to, and whether this machine has it. */
519
+ const ReviewConnectAgentSchema = z.strictObject({
520
+ target: ReviewCliInstallTargetSchema,
521
+ name: requiredString$1,
522
+ found: z.boolean(),
523
+ /** Whether it already has Whiteboard's plugin or MCP server. */
524
+ connected: z.boolean()
525
+ });
526
+ z.strictObject({ agents: z.array(ReviewConnectAgentSchema) });
527
+ const ReviewConnectRequestSchema = z.strictObject({ target: ReviewCliInstallTargetSchema });
528
+ z.strictObject({
529
+ target: ReviewCliInstallTargetSchema,
530
+ state: z.enum([
531
+ "connected",
532
+ "approve",
533
+ "failed"
534
+ ]),
535
+ output: stringAllowEmpty
536
+ });
537
+ /** A repository the first-run Run step can draw from. */
538
+ const ReviewOnboardingProjectSchema = z.strictObject({
539
+ path: requiredString$1,
540
+ name: requiredString$1
541
+ });
542
+ /** Something in a repository to draw: the checkout's branch, another
543
+ * worktree, or an open pull request. */
544
+ const ReviewOnboardingTargetSchema = z.discriminatedUnion("kind", [
545
+ z.strictObject({
546
+ kind: z.literal("branch"),
547
+ path: requiredString$1,
548
+ branch: requiredString$1
549
+ }),
550
+ z.strictObject({
551
+ kind: z.literal("worktree"),
552
+ path: requiredString$1,
553
+ branch: requiredString$1
554
+ }),
555
+ z.strictObject({
556
+ kind: z.literal("pr"),
557
+ number: z.number().int().positive(),
558
+ title: requiredString$1,
559
+ url: requiredString$1,
560
+ author: stringAllowEmpty,
561
+ branch: stringAllowEmpty,
562
+ additions: z.number().int().nonnegative(),
563
+ deletions: z.number().int().nonnegative()
564
+ })
565
+ ]);
566
+ z.strictObject({
567
+ project: ReviewOnboardingProjectSchema,
568
+ base: stringAllowEmpty,
569
+ targets: z.array(ReviewOnboardingTargetSchema)
570
+ });
571
+ /** Make a whiteboard of `target`, open it, and start `agent` drawing it. */
572
+ const ReviewOnboardingRunRequestSchema = z.strictObject({
573
+ /** The repository the target came from. */
574
+ projectPath: requiredString$1,
575
+ /** What a branch is compared to, when it has no pull request. */
576
+ base: stringAllowEmpty,
577
+ target: ReviewOnboardingTargetSchema,
578
+ agent: requiredString$1,
579
+ prompt: z.string().trim().min(1).max(8e3)
580
+ });
464
581
  const ReviewDiffFileSchema = z.strictObject({
465
582
  path: requiredString$1,
466
583
  previousPath: requiredString$1.optional(),
@@ -623,7 +740,7 @@ const ReviewVerbResponseSchema = z.discriminatedUnion("ok", [z.strictObject({
623
740
  ok: z.literal(true),
624
741
  result: z.unknown().optional()
625
742
  }), ReviewErrorResponseSchema]);
626
- z.strictObject({
743
+ const ReviewDesktopVerbFrameSchema = z.strictObject({
627
744
  event: z.literal("desktop-verb"),
628
745
  id: requiredString$1,
629
746
  request: ReviewVerbRequestSchema
@@ -832,6 +949,9 @@ function parseReviewDesktopDiscovery(value) {
832
949
  function parseReviewCliInstallApplyRequest(value) {
833
950
  return parseZod(ReviewCliInstallApplyRequestSchema, value);
834
951
  }
952
+ function parseReviewDesktopVerbFrame(value) {
953
+ return parseZod(ReviewDesktopVerbFrameSchema, value);
954
+ }
835
955
  function parseReviewDesktopVerbResult(value) {
836
956
  return parseZod(ReviewDesktopVerbResultSchema, value);
837
957
  }
@@ -856,4 +976,4 @@ function formatIssuePath(path) {
856
976
  return output;
857
977
  }
858
978
  //#endregion
859
- export { SCRATCHPAD_REVIEW_ID as _, parseReviewVerbResponse as a, structuralChangeCounts as b, ReviewBugReportRequestSchema as c, REVIEW_CLIENT_REMOTE as d, REVIEW_DIFF_LAYOUTS as f, ReviewSelectedDiffSchema as g, ReviewCliInstallStampSchema as h, parseReviewVerbRequest as i, parseReviewBugReportResponse as l, ReviewApiSelectionSourceSchema as m, parseReviewDesktopDiscovery as n, parseZod as o, REVIEW_THEME_CHOICES as p, parseReviewDesktopVerbResult as r, ReviewBugReportMetaV2Schema as s, parseReviewCliInstallApplyRequest as t, REVIEW_CLIENT_HEADER as u, reviewDiffrSummarizerInputSchema as v, decodeStructuralDiffEvent as y };
979
+ export { reviewDiffrSummarizerInputSchema as A, ReviewApiSelectionSourceSchema as C, ReviewOnboardingRunRequestSchema as D, ReviewGatewayHostSchema as E, structuralChangeCounts as M, ReviewSelectedDiffSchema as O, REVIEW_THEME_CHOICES as S, ReviewConnectRequestSchema as T, REVIEW_REMOTE_AGENT_IDS as _, parseReviewVerbRequest as a, REVIEW_REMOTE_INSTALL_LOCK as b, ReviewBugReportMetaV2Schema as c, REVIEW_CLIENT_HEADER as d, REVIEW_CLIENT_REMOTE as f, REVIEW_HOST_HEADER as g, REVIEW_DIFF_LAYOUT_SCOPES as h, parseReviewDesktopVerbResult as i, decodeStructuralDiffEvent as j, SCRATCHPAD_REVIEW_ID as k, ReviewBugReportRequestSchema as l, REVIEW_DIFF_LAYOUTS as m, parseReviewDesktopDiscovery as n, parseReviewVerbResponse as o, REVIEW_CLI_INSTALL_TARGET_LABELS as p, parseReviewDesktopVerbFrame as r, parseZod as s, parseReviewCliInstallApplyRequest as t, parseReviewBugReportResponse as u, REVIEW_REMOTE_ATTACH_BEGIN as v, ReviewCliInstallStampSchema as w, REVIEW_REMOTE_WRAPPER_MARK as x, REVIEW_REMOTE_ATTACH_END as y };
@@ -1,6 +1,6 @@
1
- import { C as ReviewInputError } from "./src-DiHP11l3.js";
2
1
  import { f as isNumberValue, l as jsonValueSchema, m as isStringValue, n as isJsonObject } from "./src-X9phtB2j.js";
3
- import "./src-WCbabvdS.js";
2
+ import "./src-Bgvf1sEN.js";
3
+ import { t as ReviewInputError } from "./input-error-OLgB_h31.js";
4
4
  import { z } from "zod";
5
5
  import { fromMarkdown } from "mdast-util-from-markdown";
6
6
  import { gfmFromMarkdown } from "mdast-util-gfm";
@@ -1448,17 +1448,6 @@ const trace_quote = {
1448
1448
  })
1449
1449
  };
1450
1450
  //#endregion
1451
- //#region src/review-api/blocks/tutorial.ts
1452
- const tutorialSchema = z.discriminatedUnion("kind", [defineBlock("tutorial", { kind: z.literal("keymap") }), defineBlock("tutorial", {
1453
- kind: z.literal("view"),
1454
- view: z.literal("diff"),
1455
- label: z.string()
1456
- })]);
1457
- const tutorial = {
1458
- type: "tutorial",
1459
- schema: tutorialSchema
1460
- };
1461
- //#endregion
1462
1451
  //#region src/review-api/blocks/index.ts
1463
1452
  /** Leaf kinds share one discriminated union so unknown types read as they always have. */
1464
1453
  const leafSchema = z.discriminatedUnion("type", [
@@ -1488,8 +1477,7 @@ const blocks = {
1488
1477
  software_map,
1489
1478
  flow_diagram,
1490
1479
  section,
1491
- callout,
1492
- tutorial
1480
+ callout
1493
1481
  };
1494
1482
  /** Discriminated by type, so a malformed block reports its own kind's
1495
1483
  * issues rather than every kind's. */
@@ -1499,8 +1487,7 @@ const blockSchema = z.lazy(() => z.discriminatedUnion("type", blockKindSchemas()
1499
1487
  const blockKindSchemas = () => [
1500
1488
  leafSchema,
1501
1489
  sectionSchema,
1502
- calloutSchema,
1503
- tutorialSchema
1490
+ calloutSchema
1504
1491
  ];
1505
1492
  function checkBlock(type, block) {
1506
1493
  blocks[type].check?.(block);
@@ -1752,7 +1739,7 @@ function edits(content, replacement, value) {
1752
1739
  ]);
1753
1740
  }
1754
1741
  const editSchema = edits(contentSchema, blockSchema, z.json());
1755
- const writableBlockTypes = Object.keys(blocks).filter((type) => type !== "tutorial");
1742
+ const writableBlockTypes = Object.keys(blocks);
1756
1743
  const namedByType = (types) => z.looseObject({ type: z.enum(types) }).describe("Fields depend on type; see the tool description.");
1757
1744
  /** What agents are shown: content names only its type. The host parses
1758
1745
  * editSchema, reporting the named kind's issues. */
@@ -1,6 +1,6 @@
1
- import { C as ReviewInputError } from "./src-DiHP11l3.js";
2
1
  import { m as isStringValue, n as isJsonObject } from "./src-X9phtB2j.js";
3
- import { n as callAuthoringTool, s as ReviewApiError, t as ToolText } from "./agent-client-P3J4y8Zn.js";
2
+ import { n as callAuthoringTool, s as ReviewApiError, t as ToolText } from "./agent-client-DVd-prfp.js";
3
+ import { t as ReviewInputError } from "./input-error-OLgB_h31.js";
4
4
  //#region src/review-api/public-tools.ts
5
5
  function publicDescription(text) {
6
6
  return text.replace(/\breview_(\w+)/g, "session_$1").replace(/\breviewId\b/g, "sessionId").replace(/\bReview\b/g, "Whiteboard").replace("The result carries review,", "The result carries session,");