@dev.fast/whiteboard 0.2.1-preview.20261006.99 → 0.2.1-preview.20261007.102

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 (37) hide show
  1. package/dist/{account-alias-DOHN1RSH.js → account-alias-DLE9XZhi.js} +1 -1
  2. package/dist/agent-cli-DdfuiyOF.js +2 -0
  3. package/dist/{agent-cli-ChOq0ZuO.js → agent-cli-d0YJQNrR.js} +6 -6
  4. package/dist/{agent-client-CPQo7iTI.js → agent-client-DoaODH6I.js} +2 -2
  5. package/dist/{authoring-tools-Cr6Kpjsx.js → authoring-tools-Bvre8hN2.js} +2 -2
  6. package/dist/build-info.json +1 -1
  7. package/dist/{cli-hVgHcdsv.js → cli-DtolttUF.js} +1 -1
  8. package/dist/{cli-runner-CMmd6o_m.js → cli-runner-CSEH_mQN.js} +11 -11
  9. package/dist/cli.js +2 -2
  10. package/dist/{client-DFoh50W3.js → client-D01H2KOV.js} +2 -2
  11. package/dist/{desktop-discovery-DBO-PM2V.js → desktop-discovery-DK2HoBy5.js} +1 -1
  12. package/dist/{headless-host-CVMDNmLU.js → headless-host-YxYKMcI5.js} +3 -2
  13. package/dist/{local-data-CxLk75rx.js → local-data-Dy8AngNb.js} +37 -84
  14. package/dist/{mcp-QeF8vkdf.js → mcp-DGKhDPMs.js} +3 -3
  15. package/dist/{process-error-telemetry-CkiiadgB.js → process-error-telemetry-DN5BpZnc.js} +345 -13
  16. package/dist/{profile-4ry2f3AM.js → profile-B6jrb2QN.js} +1 -1
  17. package/dist/profile-D8XSrJYI.js +2 -0
  18. package/dist/{request-origin-D_4QIdoN.js → request-origin-BB2TENZ5.js} +1 -1
  19. package/dist/{review-telemetry-DEFnQsQ2.js → review-telemetry-2UlsdE4D.js} +2 -2
  20. package/dist/server/desktop-host.js +14 -303
  21. package/dist/sharing/index.d.ts +6 -10
  22. package/dist/sharing/index.js +1 -1
  23. package/dist/{src-DnwdaQ2r.js → src-DHiDJQDy.js} +7 -14
  24. package/dist/{stored-document-migration-DHeMpLMb.js → stored-document-migration-T9Uj5MkN.js} +1 -1
  25. package/dist/{tool-failure-C8zB73HV.js → tool-failure-Djeeax6q.js} +1 -1
  26. package/package.json +7 -8
  27. package/src/review-api/comparison-coverage.ts +6 -11
  28. package/src/review-api/http.ts +31 -4
  29. package/src/review-api/store.ts +0 -5
  30. package/src/server/desktop-server.ts +2 -13
  31. package/src/server/diffr-config.ts +170 -128
  32. package/src/server/headless-host.ts +3 -0
  33. package/src/server/structural-diff.ts +12 -39
  34. package/src/server/test-fixtures/diffr-ui-overrides.toml +38 -0
  35. package/dist/agent-cli-DzWfZr6G.js +0 -2
  36. package/dist/profile-Bh5FIrmf.js +0 -2
  37. package/src/server/diffr-languages.ts +0 -97
@@ -1,28 +1,25 @@
1
1
  #!/usr/bin/env node
2
2
  import { a as diffFileSummaries, b as resolveRevision, i as diff, x as splitGitPatchFiles } from "../src-CmdiBL20.js";
3
- import { c as jsonString, i as jsonBoolean, m as isStringValue, n as isJsonObject, p as isObjectValue, s as jsonProperty, u as parseJsonText } from "../src-X9phtB2j.js";
4
- import { i as SharedReviewStore } from "../client-DFoh50W3.js";
5
- import { c as ReviewBugReportRequestSchema, l as parseReviewBugReportResponse, o as parseZod, s as ReviewBugReportMetaV2Schema, t as parseReviewCliInstallApplyRequest, v as reviewDiffrSummarizerInputSchema, y as decodeStructuralDiffEvent } from "../src-DnwdaQ2r.js";
6
- import { _ as invalidateStructuralComparisons, b as installFullDiffr, v as diffrExecutable, y as diffrMissingError } from "../local-data-CxLk75rx.js";
7
- import { C as resourceReferences, p as documentSchema } from "../stored-document-migration-DHeMpLMb.js";
3
+ import { c as jsonString, i as jsonBoolean, n as isJsonObject, p as isObjectValue, s as jsonProperty, u as parseJsonText } from "../src-X9phtB2j.js";
4
+ import { i as SharedReviewStore } from "../client-D01H2KOV.js";
5
+ import { c as ReviewBugReportRequestSchema, l as parseReviewBugReportResponse, o as parseZod, s as ReviewBugReportMetaV2Schema, t as parseReviewCliInstallApplyRequest, v as reviewDiffrSummarizerInputSchema } from "../src-DHiDJQDy.js";
6
+ import { C as resourceReferences, p as documentSchema } from "../stored-document-migration-T9Uj5MkN.js";
8
7
  import { t as ReviewInputError } from "../input-error-OLgB_h31.js";
9
8
  import { A as devReviewHome, P as writePrivateJsonAtomic } from "../s3-config-CAxhO9u_.js";
10
9
  import { U as shellQuote, i as reviewInstanceDiscoveryPath, o as reviewLegacyDiscoveryPath } from "../review-home-paths-6zZH-1c9.js";
11
10
  import { i as readReviewPackageVersion, t as findReviewPackageRoot } from "../package-paths-B6-zxvIO.js";
12
- import { c as reviewInstanceIdentity, t as REVIEW_INSTANCE_ENV } from "../desktop-discovery-DBO-PM2V.js";
13
- import { t as openReviewProfile } from "../profile-4ry2f3AM.js";
14
- import { r as reviewTelemetryChannel, t as ReviewTelemetry } from "../review-telemetry-DEFnQsQ2.js";
15
- import { o as REVIEW_TELEMETRY_TABS } from "../request-origin-D_4QIdoN.js";
16
- import { c as removeLegacyReviewSkills, d as skipCliInstall, i as finishCliInstallUpdate, l as resetCliInstall, n as applyCliInstall, r as declineCliInstall, s as removeCliInstall, t as aliasInstallationToAccount, u as resolveCliInstallStatus } from "../account-alias-DOHN1RSH.js";
17
- import { a as serverJson, c as recordClientError, d as HttpJsonError, f as ReviewServerError, i as createWhiteboardCore, l as createNodeRequestListener, n as installProcessErrorTelemetry, o as captureSanitizedUiTelemetry, p as GlobalReviewDesktopVerbRelay, r as serverCrashReportPath, s as clientErrorsForSession, t as drainServerCrashReport, u as readBoundedRequestJson } from "../process-error-telemetry-CkiiadgB.js";
18
- import { c as readQuerySchemas } from "../authoring-tools-Cr6Kpjsx.js";
11
+ import { c as reviewInstanceIdentity, t as REVIEW_INSTANCE_ENV } from "../desktop-discovery-DK2HoBy5.js";
12
+ import { t as openReviewProfile } from "../profile-B6jrb2QN.js";
13
+ import { r as reviewTelemetryChannel, t as ReviewTelemetry } from "../review-telemetry-2UlsdE4D.js";
14
+ import { o as REVIEW_TELEMETRY_TABS } from "../request-origin-BB2TENZ5.js";
15
+ import { c as removeLegacyReviewSkills, d as skipCliInstall, i as finishCliInstallUpdate, l as resetCliInstall, n as applyCliInstall, r as declineCliInstall, s as removeCliInstall, t as aliasInstallationToAccount, u as resolveCliInstallStatus } from "../account-alias-DLE9XZhi.js";
16
+ import { _ as setDiffrConfigValue, a as serverJson, c as recordClientError, d as HttpJsonError, f as ReviewServerError, g as saveDiffrSummarizer, h as readDiffrConfig, i as createWhiteboardCore, l as createNodeRequestListener, m as migrateDiffrConfig, n as installProcessErrorTelemetry, o as captureSanitizedUiTelemetry, p as GlobalReviewDesktopVerbRelay, r as serverCrashReportPath, s as clientErrorsForSession, t as drainServerCrashReport, u as readBoundedRequestJson, v as testDiffrSummarizer } from "../process-error-telemetry-DN5BpZnc.js";
17
+ import { c as readQuerySchemas } from "../authoring-tools-Bvre8hN2.js";
19
18
  import crypto, { createHash, randomBytes, randomUUID } from "node:crypto";
20
19
  import { z } from "zod";
21
20
  import fs, { existsSync } from "node:fs";
22
- import { chmod, copyFile, cp, mkdir, mkdtemp, readFile, realpath, rename, rm, stat, writeFile } from "node:fs/promises";
23
- import path, { join } from "node:path";
24
- import { tmpdir } from "node:os";
25
- import { execFile } from "node:child_process";
21
+ import { chmod, copyFile, cp, mkdir, readFile, realpath, rename, rm, stat, writeFile } from "node:fs/promises";
22
+ import path from "node:path";
26
23
  import { promisify } from "node:util";
27
24
  import { setTimeout as setTimeout$1 } from "node:timers/promises";
28
25
  import { pathToFileURL } from "node:url";
@@ -30,7 +27,6 @@ import { Writable } from "node:stream";
30
27
  import { pipeline } from "node:stream/promises";
31
28
  import { gzip, gzipSync } from "node:zlib";
32
29
  import { createServer } from "node:http";
33
- import { stringify } from "smol-toml";
34
30
  import { Hono } from "hono";
35
31
  //#region src/review-bundled-tools.ts
36
32
  function reviewToolsRoot(env = process.env) {
@@ -426,283 +422,6 @@ async function dumpInside(file, directory) {
426
422
  }
427
423
  }
428
424
  //#endregion
429
- //#region src/server/diffr-config.ts
430
- const execFileAsync = promisify(execFile);
431
- let writes = Promise.resolve();
432
- let testing = false;
433
- function serialized(operation) {
434
- const next = writes.then(operation, operation);
435
- writes = next.catch(() => {});
436
- return next;
437
- }
438
- async function diffr(args, rootPath, options = {}) {
439
- try {
440
- const { stdout } = await execFileAsync(diffrExecutable(), args, {
441
- cwd: rootPath,
442
- maxBuffer: 16777216,
443
- signal: options.signal ?? AbortSignal.timeout(3e4),
444
- env: options.env ?? process.env
445
- });
446
- return stdout;
447
- } catch (error) {
448
- const failure = error;
449
- if (failure.code === "ENOENT") throw diffrMissingError();
450
- if (failure.name === "AbortError") throw new Error("diffr timed out or was cancelled.");
451
- throw new Error("diffr could not complete the operation. Check its configuration and credentials.");
452
- }
453
- }
454
- async function values(rootPath, reveal = false, signal) {
455
- return json(await diffr([
456
- "config",
457
- "show",
458
- "--json",
459
- ...reveal ? ["--reveal"] : []
460
- ], rootPath, { signal }));
461
- }
462
- function json(output) {
463
- try {
464
- const parsed = parseJsonText(output);
465
- if (isJsonObject(parsed)) return parsed;
466
- } catch {}
467
- throw new Error("diffr configuration response is malformed.");
468
- }
469
- function valueAt(object, key) {
470
- let value = object;
471
- for (const segment of key.split(".")) value = isJsonObject(value) ? value[segment] : void 0;
472
- return value;
473
- }
474
- const prefix = "plugins.bundled.summarize";
475
- function savedProvider(config) {
476
- const value = valueAt(config, `${prefix}.provider`);
477
- return isStringValue(value) ? value : "";
478
- }
479
- function savedEndpoint(config) {
480
- const value = valueAt(config, `${prefix}.endpoint`);
481
- return isStringValue(value) ? value : "";
482
- }
483
- /** Whether a saved key belongs to a different destination than the draft's. */
484
- function movesKey(config, draft) {
485
- return draft.provider !== savedProvider(config) || draft.endpoint !== savedEndpoint(config);
486
- }
487
- function environmentKey(providers, id) {
488
- return providers.find((provider) => provider.id === id)?.keyVariables.map((name) => process.env[name]).find((key) => !!key);
489
- }
490
- function keyOptional(providers, id, endpoint) {
491
- return endpoint !== "" && !!providers.find((provider) => provider.id === id)?.keylessCustomEndpoint;
492
- }
493
- function strings(value) {
494
- return Array.isArray(value) ? value.filter(isStringValue) : [];
495
- }
496
- /**
497
- * What diffr's schema says about summaries: each provider's defaults, and
498
- * the default prompt with the link its description gives. Nothing when
499
- * diffr cannot describe its schema: the rest of Settings still works.
500
- */
501
- async function summaryDefaults(rootPath) {
502
- let schema;
503
- try {
504
- schema = json(await diffr(["config", "schema"], rootPath));
505
- } catch {
506
- return {};
507
- }
508
- const options = valueAt(schema, "properties.plugins.properties.bundled.properties.summarize.properties");
509
- if (!isJsonObject(options)) return {};
510
- const option = (name) => isJsonObject(options[name]) ? options[name] : {};
511
- const provider = option("provider");
512
- const titles = strings(provider["x-enum-titles"]);
513
- const models = valueAt(option("model"), "x-default-by.values");
514
- const details = valueAt(option("provider_details"), "x-default-by.values");
515
- const providers = strings(provider.enum).map((id, index) => {
516
- const detail = isJsonObject(details) ? details[id] : void 0;
517
- const model = isJsonObject(models) ? models[id] : void 0;
518
- const endpoint = isJsonObject(detail) ? detail.endpoint : void 0;
519
- return {
520
- id,
521
- title: titles[index] ?? id,
522
- model: isStringValue(model) ? model : "",
523
- endpoint: isStringValue(endpoint) ? endpoint : "",
524
- keyVariables: isJsonObject(detail) ? strings(detail.key_variables) : [],
525
- keylessCustomEndpoint: isJsonObject(detail) && detail.keyless_custom_endpoint === true
526
- };
527
- });
528
- const prompt = option("system_prompt");
529
- return {
530
- providers,
531
- defaultPrompt: isStringValue(prompt.default) ? prompt.default : void 0
532
- };
533
- }
534
- async function read(rootPath) {
535
- const config = await values(rootPath);
536
- const defaults = await summaryDefaults(rootPath);
537
- const saved = valueAt(config, `${prefix}.api_key`);
538
- const credentialSource = isStringValue(saved) && saved.length > 0 ? "config" : environmentKey(defaults.providers ?? [], savedProvider(config)) ? "environment" : "missing";
539
- const plugins = config.plugins;
540
- if (isJsonObject(plugins)) for (const namespace of Object.values(plugins)) {
541
- if (!isJsonObject(namespace)) continue;
542
- for (const plugin of Object.values(namespace)) if (isJsonObject(plugin)) delete plugin.api_key;
543
- }
544
- return {
545
- values: config,
546
- credentialSource,
547
- ...defaults
548
- };
549
- }
550
- function readDiffrConfig(rootPath) {
551
- return serialized(() => read(rootPath));
552
- }
553
- function diffrConfigValueText(value) {
554
- return isStringValue(value) ? value : JSON.stringify(value);
555
- }
556
- async function writeSettings(entries, rootPath) {
557
- let changed = false;
558
- let error;
559
- let current = await values(rootPath);
560
- try {
561
- for (const [key, value] of entries) {
562
- if (valueAt(current, key) === value) continue;
563
- await diffr([
564
- "config",
565
- "set",
566
- key,
567
- diffrConfigValueText(value)
568
- ], rootPath);
569
- changed = true;
570
- current = await values(rootPath);
571
- }
572
- } catch {
573
- error = changed ? "Some settings were saved before diffr rejected a change. Check the current values and try again." : "diffr could not save the setting. Check its configuration and the entered value.";
574
- } finally {
575
- if (changed) invalidateStructuralComparisons();
576
- }
577
- return {
578
- ...await read(rootPath),
579
- changed,
580
- error
581
- };
582
- }
583
- function setDiffrConfigValue(key, value, rootPath) {
584
- if (!/^[A-Za-z0-9_][A-Za-z0-9_-]*(\.[A-Za-z0-9_][A-Za-z0-9_-]*)*$/.test(key)) throw new Error("Invalid diffr config key.");
585
- return serialized(() => writeSettings([[key, value]], rootPath));
586
- }
587
- function saveDiffrSummarizer(input, rootPath) {
588
- const parsed = reviewDiffrSummarizerInputSchema.safeParse(input);
589
- if (!parsed.success) throw new Error("Invalid summary settings.");
590
- const draft = parsed.data;
591
- return serialized(async () => {
592
- const current = await read(rootPath);
593
- const providers = current.providers ?? [];
594
- const moving = movesKey(current.values, draft);
595
- const savedKey = current.credentialSource === "config";
596
- if (draft.enabled && !draft.apiKey && !(savedKey && !moving) && !environmentKey(providers, draft.provider) && !keyOptional(providers, draft.provider, draft.endpoint)) return {
597
- ...current,
598
- changed: false,
599
- error: "Add an API key before enabling summaries."
600
- };
601
- const entries = [];
602
- if (!draft.enabled) entries.push([`${prefix}.enabled`, false]);
603
- if (moving && savedKey) entries.push([`${prefix}.api_key`, ""]);
604
- entries.push([`${prefix}.provider`, draft.provider]);
605
- if (draft.endpoint !== savedEndpoint(current.values) || draft.endpoint && draft.provider !== savedProvider(current.values)) entries.push([`${prefix}.endpoint`, draft.endpoint]);
606
- if (draft.apiKey) entries.push([`${prefix}.api_key`, draft.apiKey]);
607
- if (draft.systemPrompt.trim() && draft.systemPrompt !== valueAt(current.values, `${prefix}.system_prompt`)) entries.push([`${prefix}.system_prompt`, draft.systemPrompt]);
608
- entries.push([`${prefix}.model`, draft.model], [`${prefix}.tests`, draft.tests]);
609
- if (draft.enabled) entries.push([`${prefix}.enabled`, true]);
610
- return writeSettings(entries, rootPath);
611
- });
612
- }
613
- async function testDiffrSummarizer(input, rootPath, signal) {
614
- const parsed = reviewDiffrSummarizerInputSchema.safeParse(input);
615
- if (!parsed.success) throw new Error("Invalid summary settings.");
616
- if (testing) throw new Error("A summary test is already running.");
617
- testing = true;
618
- const timeout = AbortSignal.timeout(6e4);
619
- const deadline = signal ? AbortSignal.any([signal, timeout]) : timeout;
620
- let directory;
621
- try {
622
- const config = await serialized(() => values(rootPath, true, deadline));
623
- const saved = valueAt(config, prefix);
624
- if (!isJsonObject(saved)) throw new Error("The summarizer is not available in this configuration.");
625
- const { provider, endpoint, model, systemPrompt } = parsed.data;
626
- const providers = (await summaryDefaults(rootPath)).providers ?? [];
627
- const variables = providers.find((known) => known.id === provider)?.keyVariables ?? [];
628
- const apiKey = parsed.data.apiKey || (!movesKey(config, parsed.data) && isStringValue(saved.api_key) ? saved.api_key : "") || environmentKey(providers, provider);
629
- if (!apiKey && !keyOptional(providers, provider, endpoint)) throw new Error("Add an API key to test summaries.");
630
- directory = await mkdtemp(join(tmpdir(), "review-summary-"));
631
- const options = {
632
- ...saved,
633
- enabled: true,
634
- provider,
635
- model,
636
- ...systemPrompt.trim() && { system_prompt: systemPrompt },
637
- min_lines: 1,
638
- retries: 0,
639
- request_timeout_ms: 45e3
640
- };
641
- delete options.api_key;
642
- delete options.provider_details;
643
- if (endpoint) options.endpoint = endpoint;
644
- else delete options.endpoint;
645
- await mkdir(join(directory, "diffr"));
646
- await writeFile(join(directory, "diffr", "config.toml"), stringify({
647
- version: 1,
648
- plugins: {
649
- order: ["bundled.summarize"],
650
- bundled: { summarize: options }
651
- }
652
- }), { mode: 384 });
653
- const before = join(directory, "before.rs"), after = join(directory, "after.rs");
654
- await writeFile(before, "");
655
- await writeFile(after, SUMMARY_SAMPLE);
656
- const output = await diffr([
657
- "--no-index",
658
- "--format",
659
- "ndjson",
660
- "--stream-annotations",
661
- "--",
662
- before,
663
- after
664
- ], directory, {
665
- env: {
666
- ...process.env,
667
- ...Object.fromEntries(providers.flatMap((known) => known.keyVariables).map((name) => [name, ""])),
668
- ...apiKey && variables[0] && { [variables[0]]: apiKey },
669
- XDG_CONFIG_HOME: directory
670
- },
671
- signal: deadline
672
- });
673
- try {
674
- const events = output.trim().split("\n").map(decodeStructuralDiffEvent);
675
- const complete = events.at(-1);
676
- const annotation = events.flatMap((event) => event.type === "annotations" ? event.annotations : []).find((item) => item.label.trim());
677
- if (complete?.type === "complete" && complete.failed === 0 && !complete.aborted && !events.some((event) => (event.type === "annotations" || event.type === "file") && event.error) && annotation) return apiKey ? annotation.label.split(apiKey).join("[redacted]") : annotation.label;
678
- } catch {}
679
- throw new Error("No summary was produced. Check the API key, model, and network connection.");
680
- } finally {
681
- testing = false;
682
- if (directory) await rm(directory, {
683
- recursive: true,
684
- force: true
685
- });
686
- }
687
- }
688
- const SUMMARY_SAMPLE = `pub fn count_values(values: &[i32]) -> (i32, i32) {
689
- let mut total = 0;
690
- let mut count = 0;
691
- for value in values {
692
- if *value > 0 {
693
- total += value;
694
- count += 1;
695
- }
696
- }
697
- let average = if count > 0 {
698
- total / count
699
- } else {
700
- 0
701
- };
702
- (count, average)
703
- }
704
- `;
705
- //#endregion
706
425
  //#region src/review-diff-files.ts
707
426
  async function resolveReviewDiffFiles(input) {
708
427
  const baseRef = input.baseRef?.trim();
@@ -1407,15 +1126,6 @@ function createGlobalReviewServer(input) {
1407
1126
  await withReviewLock(TUTORIAL_LIFECYCLE_LOCK_KEY, deleteTutorialLocked);
1408
1127
  return serverJson(200, { ok: true });
1409
1128
  });
1410
- app.post("/diffr-languages/install", async () => {
1411
- try {
1412
- await installFullDiffr();
1413
- invalidateStructuralComparisons();
1414
- return serverJson(200, { installed: true });
1415
- } catch (error) {
1416
- return serverJson(503, { error: error instanceof Error ? error.message : String(error) });
1417
- }
1418
- });
1419
1129
  app.get("/diffr-config", async () => serverJson(200, await readDiffrConfig()));
1420
1130
  app.put("/diffr-config", async (context) => {
1421
1131
  const body = await readBoundedRequestJson(context.req.raw);
@@ -1522,6 +1232,7 @@ function createGlobalReviewServer(input) {
1522
1232
  return urlForBoundPort();
1523
1233
  },
1524
1234
  listen: async () => {
1235
+ await migrateDiffrConfig();
1525
1236
  scratchpadEnabled = await readScratchpadEnabled();
1526
1237
  boundPort = await listen(httpServer, input.port);
1527
1238
  discovery.url = urlForBoundPort();
@@ -88,16 +88,7 @@ type StructuralDiffEvent = {
88
88
  } | ({
89
89
  type: "file";
90
90
  file: StructuralPairing<StructuralFileRef>;
91
- visibility?: StructuralVisibility;
92
91
  } & StructuralOutcome) | {
93
- type: "annotations";
94
- file: StructuralPairing<StructuralFileRef>;
95
- annotations: {
96
- region_id: number;
97
- label: string;
98
- }[];
99
- error?: StructuralProblem;
100
- } | {
101
92
  type: "complete";
102
93
  succeeded: number;
103
94
  failed: number;
@@ -158,7 +149,8 @@ type StructuralDiff = ({
158
149
  type StructuralSource = {
159
150
  text: string;
160
151
  syntax?: StructuralSyntaxSpan[];
161
- regions?: StructuralRegion[];
152
+ /** The whole file, as one fold. Both sides' roots share a fold_state_id; its visibility is the file's. */
153
+ root: StructuralRegion;
162
154
  };
163
155
  type StructuralBinaryRef = {
164
156
  size: number;
@@ -183,6 +175,10 @@ type StructuralNode = {
183
175
  } | {
184
176
  kind: "fold";
185
177
  children: StructuralRegion[];
178
+ /** Where the fold's content starts. Collapsed rows sit at this column. */
179
+ indent: StructuralPos;
180
+ /** From the opener to the closer, before rounding to lines. Absent without an opener. */
181
+ syntax?: StructuralSourceRange;
186
182
  };
187
183
  type StructuralSpan = {
188
184
  line: number;
@@ -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-DFoh50W3.js";
1
+ import { a as sharedReviewId, c as verifyShareRepository, i as SharedReviewStore, l as exportShare, n as ShareClient, o as validateShareBundle } from "../client-D01H2KOV.js";
2
2
  export { ShareClient, SharedReviewStore, exportShare, sharedReviewId, validateShareBundle, verifyShareRepository };
@@ -3,7 +3,7 @@ import { _ as ReviewAgentTraceEventSchema, v as ReviewAgentTraceSessionSchema }
3
3
  import { z } from "zod";
4
4
  //#region ../../node_modules/@dev.fast/diffr/dist/contract.js
5
5
  /**
6
- * Mirrors src/protocol/mod.rs and src/pairing.rs. See ../README.md for sync checks.
6
+ * Mirrors crates/diffr-core/src/protocol/mod.rs and crates/diffr-core/src/pairing.rs. See ../README.md for sync checks.
7
7
  * Import only zod: Review embeds this file in generated source.
8
8
  * Defaults are omitted, not null. Lines are zero-based, columns are UTF-8 bytes,
9
9
  * and ranges are half-open.
@@ -35,18 +35,8 @@ const StructuralDiffEventSchema = z.lazy(() => z.union([
35
35
  }),
36
36
  z.object({
37
37
  type: z.literal("file"),
38
- file: structuralPairingSchema(StructuralFileRefSchema),
39
- visibility: StructuralVisibilitySchema.optional()
38
+ file: structuralPairingSchema(StructuralFileRefSchema)
40
39
  }).and(StructuralOutcomeSchema),
41
- z.object({
42
- type: z.literal("annotations"),
43
- file: structuralPairingSchema(StructuralFileRefSchema),
44
- annotations: z.array(z.object({
45
- region_id: structuralU32,
46
- label: z.string()
47
- })),
48
- error: StructuralProblemSchema.optional()
49
- }),
50
40
  z.object({
51
41
  type: z.literal("complete"),
52
42
  succeeded: structuralU32,
@@ -120,7 +110,7 @@ const StructuralDiffSchema = z.lazy(() => z.union([z.object({
120
110
  const StructuralSourceSchema = z.lazy(() => z.object({
121
111
  text: z.string(),
122
112
  syntax: z.array(StructuralSyntaxSpanSchema).optional(),
123
- regions: z.array(StructuralRegionSchema).optional()
113
+ root: StructuralRegionSchema
124
114
  }));
125
115
  const StructuralBinaryRefSchema = z.lazy(() => z.object({ size: z.number().int().nonnegative() }));
126
116
  const StructuralSyntaxSpanSchema = z.lazy(() => z.object({
@@ -141,7 +131,9 @@ const StructuralNodeSchema = z.lazy(() => z.union([z.object({
141
131
  changed: z.array(StructuralSpanSchema).optional()
142
132
  }), z.object({
143
133
  kind: z.literal("fold"),
144
- children: z.array(StructuralRegionSchema)
134
+ children: z.array(StructuralRegionSchema),
135
+ indent: StructuralPosSchema,
136
+ syntax: StructuralSourceRangeSchema.optional()
145
137
  })]));
146
138
  const StructuralSpanSchema = z.lazy(() => z.object({
147
139
  line: structuralU32,
@@ -262,6 +254,7 @@ z.object({
262
254
  keylessCustomEndpoint: z.boolean()
263
255
  })).optional(),
264
256
  defaultPrompt: z.string().optional(),
257
+ defaultHiddenTags: z.array(z.string()).optional(),
265
258
  changed: z.boolean().optional(),
266
259
  error: z.string().optional()
267
260
  });
@@ -1,5 +1,5 @@
1
1
  import { f as isNumberValue, l as jsonValueSchema, m as isStringValue, n as isJsonObject } from "./src-X9phtB2j.js";
2
- import "./src-DnwdaQ2r.js";
2
+ import "./src-DHiDJQDy.js";
3
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";
@@ -1,5 +1,5 @@
1
1
  import { m as isStringValue, n as isJsonObject } from "./src-X9phtB2j.js";
2
- import { n as callAuthoringTool, s as ReviewApiError, t as ToolText } from "./agent-client-CPQo7iTI.js";
2
+ import { n as callAuthoringTool, s as ReviewApiError, t as ToolText } from "./agent-client-DoaODH6I.js";
3
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) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dev.fast/whiteboard",
3
- "version": "0.2.1-preview.20261006.99",
3
+ "version": "0.2.1-preview.20261007.102",
4
4
  "private": false,
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -43,6 +43,7 @@
43
43
  "@agentclientprotocol/claude-agent-acp": "0.84.0",
44
44
  "@agentclientprotocol/codex-acp": "2.0.0",
45
45
  "@agentclientprotocol/sdk": "1.5.1",
46
+ "@dev.fast/diffr": "0.1.14",
46
47
  "@hono/node-server": "2.0.12",
47
48
  "@img/sharp-wasm32": "0.35.4",
48
49
  "@modelcontextprotocol/sdk": "1.30.0",
@@ -61,7 +62,6 @@
61
62
  "zod": "4.6.5"
62
63
  },
63
64
  "devDependencies": {
64
- "@dev.fast/diffr": "0.1.10",
65
65
  "@dev.fast/local-vcs": "^0.1.0",
66
66
  "@types/node": "24.12.2",
67
67
  "@types/semver": "7.7.1",
@@ -72,10 +72,10 @@
72
72
  "typescript": "6.0.3",
73
73
  "vitest": "4.1.11",
74
74
  "@dev.fast/json": "0.0.0",
75
- "@dev.fast/review-share-protocol": "0.1.2",
76
- "@dev.fast/review-protocol": "0.0.0",
75
+ "@dev.fast/trace-core": "0.0.0",
77
76
  "@dev.fast/trace-protocol": "0.5.0",
78
- "@dev.fast/trace-core": "0.0.0"
77
+ "@dev.fast/review-share-protocol": "0.1.2",
78
+ "@dev.fast/review-protocol": "0.0.0"
79
79
  },
80
80
  "engines": {
81
81
  "node": ">=24 <25"
@@ -85,10 +85,9 @@
85
85
  "repo": "devdotfast/review",
86
86
  "docs": "packages/review/instructions/authoring.md"
87
87
  },
88
- "gitHead": "b8634e9e5ae077e753c5bdd743c9702d31fb9efe",
88
+ "gitHead": "912c9427199c112f1f66e09b6a9ca86cc6797968",
89
89
  "scripts": {
90
90
  "prebuild": "pnpm run build:workspace-deps",
91
- "ensure:diffr": "diffr-fetch --into bin",
92
91
  "build": "node --max-old-space-size=8192 node_modules/tsdown/dist/run.mjs",
93
92
  "build:tutorial-assets": "tsx scripts/build-tutorial-assets.ts",
94
93
  "ensure:tutorial-assets": "tsx scripts/ensure-tutorial-assets.ts",
@@ -103,7 +102,7 @@
103
102
  "test:node": "vitest run --config vitest.config.ts --project shared-module-graph",
104
103
  "test:telemetry-privacy": "vitest run --config vitest.config.ts src/ui-telemetry-events.test.ts src/error-telemetry.test.ts",
105
104
  "typecheck": "pnpm -w exec tsc --noEmit -p packages/review",
106
- "test:integration:diffr": "pnpm run build:workspace-deps && pnpm run ensure:diffr --required && vitest run --config vitest.integration.config.ts",
105
+ "test:integration:diffr": "pnpm run build:workspace-deps && vitest run --config vitest.integration.config.ts",
107
106
  "whiteboard": "tsx src/cli.ts"
108
107
  }
109
108
  }
@@ -4,7 +4,6 @@ import {
4
4
  type StructuralDiff,
5
5
  type StructuralRegion,
6
6
  type StructuralSource,
7
- type StructuralVisibility,
8
7
  structuralRows,
9
8
  } from "@dev.fast/review-protocol";
10
9
  import type { AlignmentRow } from "@review/lens-selection.js";
@@ -217,7 +216,7 @@ export async function comparisonCoverage(
217
216
  fingerprint,
218
217
  changed:
219
218
  diff.type === "text" ? diff.structural_changes : emptyCoverage(),
220
- folded: foldedChanges(event.visibility, diff),
219
+ folded: foldedChanges(diff),
221
220
  viewed: emptyCoverage(),
222
221
  });
223
222
  publish?.({
@@ -244,21 +243,17 @@ export async function comparisonCoverage(
244
243
  * the lines, not just the counts, are known. A paired leaf contributes its
245
244
  * changed spans' lines; an unpaired leaf, all of its lines.
246
245
  */
247
- export function foldedChanges(
248
- visibility: StructuralVisibility | undefined,
249
- diff: StructuralDiff,
250
- ): Coverage {
246
+ export function foldedChanges(diff: StructuralDiff): Coverage {
251
247
  if (diff.type !== "text") return emptyCoverage();
252
248
  const all = diff.structural_changes;
253
249
 
254
- if (visibility?.collapsed)
255
- return { base: unionIntervals(all.base), head: unionIntervals(all.head) };
256
-
257
250
  const side = (
258
251
  changed: readonly LineInterval[],
259
252
  source: StructuralSource | undefined,
260
253
  other: StructuralSource | undefined,
261
254
  ): LineInterval[] => {
255
+ if (source?.root.visibility?.collapsed) return unionIntervals(changed);
256
+
262
257
  const paired = new Set<number>();
263
258
 
264
259
  const pair = (region: StructuralRegion) => {
@@ -266,7 +261,7 @@ export function foldedChanges(
266
261
  else region.children.forEach(pair);
267
262
  };
268
263
 
269
- other?.regions?.forEach(pair);
264
+ if (other) pair(other.root);
270
265
 
271
266
  const hidden: LineInterval[] = [],
272
267
  visible: LineInterval[] = [];
@@ -292,7 +287,7 @@ export function foldedChanges(
292
287
  ]);
293
288
  };
294
289
 
295
- for (const region of source?.regions ?? []) collect(region, false);
290
+ if (source) collect(source.root, false);
296
291
 
297
292
  return subtractIntervals(intersectIntervals(changed, hidden), visible);
298
293
  };
@@ -12,11 +12,12 @@ import {
12
12
  AgentSelectionSchema,
13
13
  selectionMarkdown,
14
14
  } from "@review/agent-selection.js";
15
- import type { AskAgentStatus } from "@review/ask/agents.js";
15
+ import { type AskAgentStatus, askAgents } from "@review/ask/agents.js";
16
16
  import { checkoutFiles, mentionableFiles } from "@review/ask/checkout-files.js";
17
17
  import { parseFileRef, resolveFileRefs } from "@review/ask/file-refs.js";
18
18
  import {
19
19
  type AskAgentId,
20
+ type AskThreadState,
20
21
  askAgentIds,
21
22
  askChoiceKinds,
22
23
  askPicksSchema,
@@ -1154,15 +1155,15 @@ export function createReviewApi(
1154
1155
  checkout.live
1155
1156
  ? "Your working directory is the repository the review describes."
1156
1157
  : `Your working directory is a checkout of the review's head commit, ${checkout.head}. Answer from this code, not from other branches.`,
1157
- "Answer the question.",
1158
+ "Answer the question for a staff engineer: lead with the answer and keep it short. Explain at the level of components and data flow before functions, and check each claim about the code against code you have read.",
1158
1159
  "Name files by their path from the checkout root, with a line where it helps, as in `src/app.ts:42`; the reviewer can open them from your answer.",
1159
1160
  ...(reach?.kind === "mcp"
1160
1161
  ? [
1161
- `The whiteboard MCP tools read and change this review: its sessionId is "${reviewId}". Read it with session_get. If the reviewer asks you to change the review, edit it with session_edit; do not write files to do it.`,
1162
+ `The whiteboard MCP tools read and change this review: its sessionId is "${reviewId}". Read it with session_get. If the reviewer asks you to change the review, first read session_get_instructions({}) for its guidelines and each component's fields (the review exists, so skip creating one), then edit it with session_edit; do not write files to do it.`,
1162
1163
  ]
1163
1164
  : reach?.kind === "cli"
1164
1165
  ? [
1165
- `Whiteboard's CLI reads and changes this review from your shell: its sessionId is "${reviewId}". Read it with \`${reach.command} api session_get '{"sessionId":"${reviewId}"}'\`. If the reviewer asks you to change the review, edit it with \`${reach.command} api session_edit '<json>'\`; do not write files to do it. \`${reach.command} api tools\` lists each tool's input.`,
1166
+ `Whiteboard's CLI reads and changes this review from your shell: its sessionId is "${reviewId}". Read it with \`${reach.command} api session_get '{"sessionId":"${reviewId}"}'\`. If the reviewer asks you to change the review, first read \`${reach.command} api session_get_instructions '{}'\` for its guidelines and each component's fields (the review exists, so skip creating one), then edit it with \`${reach.command} api session_edit '<json>'\`; do not write files to do it. \`${reach.command} api tools\` lists each tool's input.`,
1166
1167
  ]
1167
1168
  : []),
1168
1169
  "",
@@ -1309,6 +1310,32 @@ export function createReviewApi(
1309
1310
  return context.json({ threads: store.askHistory.list(reviewId) });
1310
1311
  });
1311
1312
 
1313
+ // A saved conversation, without starting its agent.
1314
+ app.get("/:id/ask/:threadId", (context) => {
1315
+ const record = store.askHistory.get(context.req.param("threadId"));
1316
+
1317
+ if (record?.reviewId !== context.req.param("id"))
1318
+ throw new ReviewInputError("This conversation was not found.", 404);
1319
+ const target = record.selection.target;
1320
+
1321
+ return context.json({
1322
+ id: record.id,
1323
+ agent: record.agent,
1324
+ agentName: askAgents[record.agent].name,
1325
+ status: "idle",
1326
+ readOnly: true,
1327
+ bypass: false,
1328
+ head: record.head,
1329
+ cwd: record.cwd,
1330
+ title: record.title,
1331
+ selection: {
1332
+ title: record.selection.title,
1333
+ quote: target.kind === "text" ? target.quote : undefined,
1334
+ },
1335
+ entries: record.entries ?? [],
1336
+ } satisfies AskThreadState);
1337
+ });
1338
+
1312
1339
  // A saved conversation: attach to it if it is still running, else start
1313
1340
  // the agent and load it, at the commit it was asked about.
1314
1341
  app.post("/:id/ask/:threadId/open", async (context) => {
@@ -1237,11 +1237,6 @@ export class ReviewStore {
1237
1237
  ...(applied?.children && { children: applied.children }),
1238
1238
  };
1239
1239
 
1240
- if (snapshot.staleSources?.length)
1241
- warnings.push(
1242
- "Some authored source ranges changed. Update their references before presenting this review.",
1243
- );
1244
-
1245
1240
  if (warnings.length) result.warnings = warnings;
1246
1241
 
1247
1242
  this.commitCommand(