@dev.fast/whiteboard 0.2.4 → 0.2.5-preview.20261013.109

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 (77) hide show
  1. package/dist/{account-alias-_1_7vROx.js → account-alias-D0hF0sKk.js} +2 -3
  2. package/dist/{agent-cli-BrFqhnzM.js → agent-cli-BBPxwUOd.js} +6 -6
  3. package/dist/agent-cli-YSuhE5X8.js +2 -0
  4. package/dist/{agent-client-B_b1GtUc.js → agent-client-P3J4y8Zn.js} +3 -3
  5. package/dist/{authoring-tools-DvIO906l.js → authoring-tools-D8WXsm_g.js} +11 -10
  6. package/dist/build-info.json +1 -1
  7. package/dist/{cli-gRGxkwc0.js → cli-C0ziP6hg.js} +1 -1
  8. package/dist/{cli-runner-Cjl8h376.js → cli-runner-C1gY-HAI.js} +16 -17
  9. package/dist/cli.js +1 -1
  10. package/dist/{client-BFClfQLO.js → client-DzcSt6pV.js} +63 -13
  11. package/dist/{desktop-discovery-C-Qz54wO.js → desktop-discovery-Ba3dD5Cn.js} +1 -1
  12. package/dist/{headless-host-DcWaxx9P.js → headless-host-BHD_Vg5F.js} +4 -4
  13. package/dist/{local-data-Bm84Xdck.js → local-data-BlERtVm2.js} +281 -75
  14. package/dist/{mcp-BDy9Aybd.js → mcp-t7VN9R3k.js} +4 -4
  15. package/dist/{package-paths-B6-zxvIO.js → package-paths-B9PDoAl6.js} +1 -1
  16. package/dist/{process-error-telemetry-Dzh5a3s_.js → process-error-telemetry-C0swByfp.js} +158 -57
  17. package/dist/{profile-RDEioF8b.js → profile-BsYDBr7t.js} +2 -2
  18. package/dist/profile-j97aRTS7.js +2 -0
  19. package/dist/{review-agent-traces-DNJFYzc8.js → review-home-paths-Y9mYWkCI.js} +2600 -62
  20. package/dist/{review-telemetry-DBy3eVCo.js → review-telemetry-CmM6tCbj.js} +2 -2
  21. package/dist/server/desktop-host.js +35 -46
  22. package/dist/{server-discovery-Dbzn3w6Z.js → server-discovery-ZVi8Ay8J.js} +1 -1
  23. package/dist/sharing/index.d.ts +37 -49
  24. package/dist/sharing/index.js +1 -1
  25. package/dist/{src-CmdiBL20.js → src-DiHP11l3.js} +11 -1
  26. package/dist/{stored-document-migration-BxZWQQjv.js → stored-document-migration-u4L_1VZa.js} +15 -36
  27. package/dist/{tool-failure-DJmTieh5.js → tool-failure-CJHBCHMh.js} +2 -2
  28. package/instructions/scratchpad.md +1 -1
  29. package/package.json +5 -4
  30. package/src/ask/agents.ts +6 -0
  31. package/src/ask/thread-state.ts +17 -1
  32. package/src/ask/thread.ts +116 -22
  33. package/src/ask/threads.ts +8 -2
  34. package/src/error-telemetry.ts +13 -9
  35. package/src/fixtures/blocks/tutorial.json +3 -40
  36. package/src/review-api/README.md +10 -7
  37. package/src/review-api/ask-history.ts +37 -0
  38. package/src/review-api/authoring-tools.ts +8 -5
  39. package/src/review-api/blocks/database_lens.ts +5 -0
  40. package/src/review-api/blocks/tutorial.ts +2 -18
  41. package/src/review-api/comparison-coverage.ts +16 -9
  42. package/src/review-api/component-reference.ts +1 -1
  43. package/src/review-api/document.ts +24 -10
  44. package/src/review-api/http.ts +267 -189
  45. package/src/review-api/local-data.ts +126 -37
  46. package/src/review-api/store-schema.ts +25 -1
  47. package/src/review-api/store.ts +165 -33
  48. package/src/review-api/workspaces.ts +14 -14
  49. package/src/review-api/worktree-events.ts +46 -0
  50. package/src/review-app.ts +1 -1
  51. package/src/review-info.ts +1 -1
  52. package/src/review-stack.ts +2 -2
  53. package/src/server/review-server-core.ts +25 -1
  54. package/src/server/tutorial-service.ts +22 -46
  55. package/src/sharing/export.ts +1 -0
  56. package/src/sharing/import.ts +111 -26
  57. package/tutorial/document.json +27 -86
  58. package/tutorial/lenses.json +10 -0
  59. package/tutorial/runtime-manifest.json +2 -3
  60. package/tutorial/sample-service/src/database/drizzle-orm.d.ts +16 -0
  61. package/tutorial/sample-service/src/database/schema.ts +9 -10
  62. package/tutorial/sample-service/src/inventory/forklift-mood.ts +4 -0
  63. package/tutorial/sample-service/src/inventory/inventory-service.ts +18 -1
  64. package/tutorial/sample-service/src/inventory/warehouse-lore.ts +11 -0
  65. package/tutorial/sample-service/src/orders/order-service.ts +3 -2
  66. package/tutorial/sample-service/src/orders/order.ts +27 -5
  67. package/tutorial/sample-service/src/payments/fraud-vibes.ts +4 -0
  68. package/tutorial/sample-service/src/payments/payment-gateway.ts +19 -2
  69. package/tutorial/sample-service/src/payments/raccoon-ledger.ts +9 -0
  70. package/dist/agent-cli-BR2j-2n5.js +0 -2
  71. package/dist/input-error-OLgB_h31.js +0 -11
  72. package/dist/profile-BQGaX8aX.js +0 -2
  73. package/dist/review-home-paths-6zZH-1c9.js +0 -2560
  74. package/dist/tutorial-trace-DrBvkaeL.js +0 -43
  75. package/src/tutorial-conversation.ts +0 -18
  76. package/tutorial/software-map.json +0 -139
  77. package/tutorial/trace.json +0 -20
@@ -1,8 +1,7 @@
1
1
  import { h as ReviewCliInstallStampSchema } from "./src-WCbabvdS.js";
2
- import { G as readStoreAuth, Z as StoreClient } from "./review-agent-traces-DNJFYzc8.js";
2
+ 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-Y9mYWkCI.js";
3
3
  import { A as devReviewHome, N as writeFileAtomicAsync, O as withFileLock, P as writePrivateJsonAtomic } from "./s3-config-CAxhO9u_.js";
4
- import { B as installHarnessHooks, C as disableTraceMachine, E as traceMachineStatus, F as listTraceRepositoryRoots, G as traceScope, I as traceRepositoryStatus, N as disableAllTraceRepositories, P as enableTraceRepository, R as AGENT_TRACE_HOOK_AGENTS, S as configureTraceMachine, T as traceMachineEnabled, V as removeAgentTraceHook, n as reviewDesktopStateDir, z as describeTraceHookOwners } from "./review-home-paths-6zZH-1c9.js";
5
- import { i as readReviewPackageVersion } from "./package-paths-B6-zxvIO.js";
4
+ import { i as readReviewPackageVersion } from "./package-paths-B9PDoAl6.js";
6
5
  import { n as isFile, t as isDirectory } from "./fs-utils-BMPLt0cr.js";
7
6
  import { createHash } from "node:crypto";
8
7
  import { constants } from "node:fs";
@@ -1,11 +1,11 @@
1
1
  import { n as isJsonObject, u as parseJsonText } from "./src-X9phtB2j.js";
2
- import { a as toolResultText, i as connectReviewInstance, s as ReviewApiError } from "./agent-client-B_b1GtUc.js";
2
+ import { T as traceMachineEnabled } from "./review-home-paths-Y9mYWkCI.js";
3
+ import { a as toolResultText, i as connectReviewInstance, s as ReviewApiError } from "./agent-client-P3J4y8Zn.js";
3
4
  import { A as devReviewHome } from "./s3-config-CAxhO9u_.js";
4
- import { T as traceMachineEnabled } from "./review-home-paths-6zZH-1c9.js";
5
- import { d as selectReviewInstance, t as REVIEW_INSTANCE_ENV } from "./desktop-discovery-C-Qz54wO.js";
6
- import { n as reviewSessionAgent } from "./review-telemetry-DBy3eVCo.js";
5
+ import { d as selectReviewInstance, t as REVIEW_INSTANCE_ENV } from "./desktop-discovery-Ba3dD5Cn.js";
6
+ import { n as reviewSessionAgent } from "./review-telemetry-CmM6tCbj.js";
7
7
  import { n as REVIEW_VIA_HEADER, t as REVIEW_AGENT_HEADER } from "./request-origin-DA4K1qWY.js";
8
- import { i as publicTool, n as RECOVERY, r as callPublicTool, t as toolFailure } from "./tool-failure-DJmTieh5.js";
8
+ import { i as publicTool, n as RECOVERY, r as callPublicTool, t as toolFailure } from "./tool-failure-CJHBCHMh.js";
9
9
  //#region src/review-api/agent-cli.ts
10
10
  const reviewAgentCliHelp = "whiteboard api tools (one line per tool)\nwhiteboard api tools <tool-name> (its description and input schema)\nwhiteboard api <tool-name> '<json>'\nwhiteboard api <tool-name> - (read JSON from stdin)\nwhiteboard mcp (stdio MCP adapter; Whiteboard Desktop or whiteboard server start must be running)\nSelect headless state with DEV_REVIEW_SERVER_DIR or whiteboard --state-dir <path> api/mcp.\n";
11
11
  async function runReviewAgentCli(input) {
@@ -23,7 +23,7 @@ async function runReviewAgentCli(input) {
23
23
  [REVIEW_AGENT_HEADER]: reviewSessionAgent(env)
24
24
  };
25
25
  if (mode === "mcp") {
26
- const { serveReviewMcp } = await import("./mcp-BDy9Aybd.js");
26
+ const { serveReviewMcp } = await import("./mcp-t7VN9R3k.js");
27
27
  await serveReviewMcp(async (key, agentKind) => {
28
28
  const connected = await connectReviewInstance(key ? {
29
29
  ...env,
@@ -0,0 +1,2 @@
1
+ import { n as runReviewAgentCli } from "./agent-cli-BBPxwUOd.js";
2
+ export { runReviewAgentCli };
@@ -1,8 +1,8 @@
1
+ import { C as ReviewInputError } from "./src-DiHP11l3.js";
1
2
  import { d as isBooleanValue, f as isNumberValue, m as isStringValue, u as parseJsonText } from "./src-X9phtB2j.js";
2
3
  import "./src-WCbabvdS.js";
3
- import { t as ReviewInputError } from "./input-error-OLgB_h31.js";
4
- import { d as selectReviewInstance, i as healthyReviewInstance, n as ReviewInstanceUnavailableError, u as reviewInstanceUnavailable } from "./desktop-discovery-C-Qz54wO.js";
5
- import { a as reviewServerIsHealthy, n as readReviewServerDiscovery, o as reviewServerStateDir, s as serverNotReady } from "./server-discovery-Dbzn3w6Z.js";
4
+ import { d as selectReviewInstance, i as healthyReviewInstance, n as ReviewInstanceUnavailableError, u as reviewInstanceUnavailable } from "./desktop-discovery-Ba3dD5Cn.js";
5
+ import { a as reviewServerIsHealthy, n as readReviewServerDiscovery, o as reviewServerStateDir, s as serverNotReady } from "./server-discovery-ZVi8Ay8J.js";
6
6
  //#region ../review-protocol/src/review-api-client.ts
7
7
  const defaultRequest = (url, init) => fetch(url, init);
8
8
  /** A non-2xx reply; the status tells a caller whether retrying can help. */
@@ -1,7 +1,7 @@
1
- import { g as activityUpdateSchema, h as activityEndSchema, i as commandSchema, m as activityBeginSchema, n as uploadSchema } from "./local-data-Bm84Xdck.js";
2
- import { x as publishedEditSchema, y as pathTargetSchema } from "./stored-document-migration-BxZWQQjv.js";
3
- import { t as ReviewInputError } from "./input-error-OLgB_h31.js";
4
- import { t as findReviewPackageRoot } from "./package-paths-B6-zxvIO.js";
1
+ import { C as ReviewInputError } from "./src-DiHP11l3.js";
2
+ import { g as activityUpdateSchema, h as activityEndSchema, i as commandSchema, m as activityBeginSchema, n as uploadSchema } from "./local-data-BlERtVm2.js";
3
+ import { x as publishedEditSchema, y as pathTargetSchema } from "./stored-document-migration-u4L_1VZa.js";
4
+ import { t as findReviewPackageRoot } from "./package-paths-B9PDoAl6.js";
5
5
  import { z } from "zod";
6
6
  import { readFile } from "node:fs/promises";
7
7
  import path from "node:path";
@@ -92,7 +92,7 @@ const COMPONENT_REFERENCE = [
92
92
  "- call_stack_diff {title, base: frame[], head: frame[], pins?}; either side may be empty",
93
93
  "- frame {source: anchor, label?, key?, parentKey? (an earlier frame on that side), callSite?: anchor, contextSources?: anchor[], via?: {kind: call|queue|callback|rpc, reason}, pins?}",
94
94
  "- database_lens {title, actors: {key: label}, stores: {key: store}, useCases: useCase[] (1 or more), pins?}",
95
- "- store {label, storage: relational|document, collections: {key: {label, fields: {key: field}}}, dataStoreKind?: database|objectStore|bucket|artifactStore|fileStore}",
95
+ "- store {label, storage: relational|document, collections: {key: {label, fields: {key: field}, source?: anchor of its schema declaration}}, dataStoreKind?: database|objectStore|bucket|artifactStore|fileStore}",
96
96
  "- field {label, dataType, nullable?, primaryKey?, references?: {store, collection, field}, fields?: {key: field}}",
97
97
  "- useCase {label, summary?, operations: operation[] (1 or more)}; operation {kind: read|write, actor, store, collection, label, source: anchor, field?, detail?, pins?}, naming declared keys",
98
98
  "",
@@ -166,7 +166,7 @@ function authoringTools(scratchpadAvailable = false, traceEnabled = false) {
166
166
  kind: true
167
167
  });
168
168
  const descriptions = {
169
- 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 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 a 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.",
169
+ 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 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.",
170
170
  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.",
171
171
  edit: [
172
172
  "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).",
@@ -178,11 +178,12 @@ function authoringTools(scratchpadAvailable = false, traceEnabled = false) {
178
178
  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.",
179
179
  rename: "Change the review title.",
180
180
  restore: "Restore title, source pins, PR identity and content from a saved version.",
181
- attention: "Mark a review viewed, dismissed or restored without changing its content.",
182
- delete: "Permanently delete this review and its history."
181
+ attention: "Mark an active review viewed without changing its content or lifecycle. Restore a review from Trash before marking it viewed.",
182
+ 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.",
183
+ delete: "Permanently delete a review and its history. The review must be in Trash. This cannot be undone."
183
184
  };
184
185
  const tool = (name, description, schema, method, path, commandType) => ({
185
- name: `review_${name}`,
186
+ name: `review_${name === "restore" ? "restore_version" : name}`,
186
187
  description,
187
188
  inputSchema: {
188
189
  ...z.toJSONSchema(schema, { io: "input" }),
@@ -220,7 +221,7 @@ function authoringTools(scratchpadAvailable = false, traceEnabled = false) {
220
221
  }), "GET", "/:reviewId/inspect"),
221
222
  tool("lens_get", "Read the review's Diff-view lenses as authored (ids, titles, targets), each lens's resolved fileCount (and unavailable reason, if any), and uncategorized: the changed lines no lens selects yet, by file.", z.strictObject(review), "GET", "/:reviewId/lenses"),
222
223
  tool("history", "List saved document versions.", z.strictObject(review), "GET", "/:reviewId/history"),
223
- tool("open", "Show an existing review immediately and prepare current pinned checkouts in the background. Returns softwareMapEnabled and any already-recorded environmentIssues. Missing optional setup is not an issue; use review_environment to recheck.", z.strictObject(review), "POST", "/:reviewId/open"),
224
+ tool("open", "Show an active review immediately; restore it from Trash before opening it. Prepare current pinned checkouts in the background. Returns softwareMapEnabled and any already-recorded environmentIssues. Missing optional setup is not an issue; use review_environment to recheck.", z.strictObject(review), "POST", "/:reviewId/open"),
224
225
  tool("environment", "Acquire and recheck this review's current base/head language checkouts (not historical or selected commits). Returns acquisition issues, not full LSP health. Missing optional setup and failed setup with a usable checkout stay silent. Set retry:true to rerun failed preparation after an actual language-feature failure; preparation runs in the background.", z.strictObject({
225
226
  ...review,
226
227
  retry: z.boolean().optional()
@@ -1 +1 @@
1
- {"version":"0.2.4","commit":"5b00f0e66d034e187a29d270b3690a37b57ccf7b","dirty":true,"builtAt":"2026-10-09T06:23:07.418Z"}
1
+ {"version":"0.2.5-preview.20261013.109","commit":"a3d88565d0643e8a8f9e80f24a9b9d29b6a97e3d","dirty":true,"builtAt":"2026-10-13T01:39:18.933Z"}
@@ -1,4 +1,4 @@
1
- import { r as connectReviewApi } from "./agent-client-B_b1GtUc.js";
1
+ import { r as connectReviewApi } from "./agent-client-P3J4y8Zn.js";
2
2
  import { randomUUID } from "node:crypto";
3
3
  import { z } from "zod";
4
4
  //#region src/sharing/cli.ts
@@ -1,19 +1,18 @@
1
- import { C as __commonJSMin, S as setLocalVcsCommandObserver, T as __toESM, w as __require } from "./src-CmdiBL20.js";
1
+ import { E as __toESM, S as setLocalVcsCommandObserver, T as __require, w as __commonJSMin } from "./src-DiHP11l3.js";
2
2
  import { c as jsonString, n as isJsonObject, u as parseJsonText } from "./src-X9phtB2j.js";
3
3
  import { a as parseReviewVerbResponse } from "./src-WCbabvdS.js";
4
- import { $ as emitJsonEvent, G as readStoreAuth, H as DEFAULT_STORE_ORIGIN, J as runStoreLogout, N as requireTraceConsent, P as resolveTraceRepositoryTarget, Q as withStoreAuthorization, V as readTraceUserConfig, X as StoreApiError, Y as runStoreWhoami, Z as StoreClient, b as selectTraceStorage, et as failWithJsonError, g as describeSelection, nt as jsonRequestedInArgv, q as runStoreLogin, tt as humanStream, v as loadS3TraceStorage } from "./review-agent-traces-DNJFYzc8.js";
5
- import { o as ReviewApiClient, s as ReviewApiError } from "./agent-client-B_b1GtUc.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-Y9mYWkCI.js";
5
+ import { o as ReviewApiClient, s as ReviewApiError } from "./agent-client-P3J4y8Zn.js";
6
6
  import { t as resolveReviewRoot } from "./runtime-CKiWhGrf.js";
7
7
  import { A as devReviewHome, C as s3ProfileSchema, D as processIsAlive, E as writeTraceConfigFile, S as readTraceConfigFile, T as sameS3Profile, _ as emptyTraceConfig, c as resolveS3Setup, g as currentStore, h as TraceConfigurationError, i as isS3MockMode, j as normalizeStoreOrigin, k as DEV_REVIEW_HOME_ENV, n as clearTraceEnvCache, o as readTraceEnvFile, p as traceSettingsPath, w as s3Store } from "./s3-config-CAxhO9u_.js";
8
8
  import { t as errorMessage } from "./error-message-OtiDonty.js";
9
- import { A as runTraceSessions, D as runTraceAllow, E as traceMachineStatus, G as traceScope, H as resolveTraceCommand, I as traceRepositoryStatus, L as HOSTED_CAPTURE_SCOPE_DESCRIPTION, M as runTraceStoreInfo, O as runTraceDeny, T as traceMachineEnabled, W as traceHomeDir, _ as runTraceRepair, b as runTraceGitHook, c as registerTraceCommands, d as runTraceList$1, f as runTracePull$1, g as runTraceInstallMachine, h as runTraceEnable, j as runTraceStoreDelete, k as runTraceOnboard, l as resolveTraceReadStorage, m as runTraceDisable, p as runTraceShow, u as runTraceBlame, v as runTraceStatus, w as readLegacyCaptureSettings, x as runTraceHook, y as runTraceSync } from "./review-home-paths-6zZH-1c9.js";
10
- import { i as readReviewPackageVersion, r as readBuildInfo, t as findReviewPackageRoot } from "./package-paths-B6-zxvIO.js";
11
- import { a as readDefaultReviewInstance, d as selectReviewInstance, f as writeDefaultReviewInstance, i as healthyReviewInstance, l as reviewInstanceStartHint, n as ReviewInstanceUnavailableError, o as readReviewInstances, r as clearDefaultReviewInstance, s as requireHealthyReviewDesktop, t as REVIEW_INSTANCE_ENV, u as reviewInstanceUnavailable } from "./desktop-discovery-C-Qz54wO.js";
12
- import { t as ReviewTelemetry } from "./review-telemetry-DBy3eVCo.js";
13
- import { a as isOwnedShim, f as windowsInstallerCommand, m as connectPrompts, o as pathShimPath, p as scanLegacySkills, t as aliasInstallationToAccount } from "./account-alias-_1_7vROx.js";
9
+ import { i as readReviewPackageVersion, r as readBuildInfo, t as findReviewPackageRoot } from "./package-paths-B9PDoAl6.js";
10
+ import { a as readDefaultReviewInstance, d as selectReviewInstance, f as writeDefaultReviewInstance, i as healthyReviewInstance, l as reviewInstanceStartHint, n as ReviewInstanceUnavailableError, o as readReviewInstances, r as clearDefaultReviewInstance, s as requireHealthyReviewDesktop, t as REVIEW_INSTANCE_ENV, u as reviewInstanceUnavailable } from "./desktop-discovery-Ba3dD5Cn.js";
11
+ import { t as ReviewTelemetry } from "./review-telemetry-CmM6tCbj.js";
12
+ import { a as isOwnedShim, f as windowsInstallerCommand, m as connectPrompts, o as pathShimPath, p as scanLegacySkills, t as aliasInstallationToAccount } from "./account-alias-D0hF0sKk.js";
14
13
  import { t as fuzzyRank } from "./fuzzy-match-BoAmcyak.js";
15
- import { n as readReviewServerDiscovery, o as reviewServerStateDir, r as readReviewServerHealth, s as serverNotReady } from "./server-discovery-Dbzn3w6Z.js";
16
- import { t as reviewAgentCliHelp } from "./agent-cli-BrFqhnzM.js";
14
+ import { n as readReviewServerDiscovery, o as reviewServerStateDir, r as readReviewServerHealth, s as serverNotReady } from "./server-discovery-ZVi8Ay8J.js";
15
+ import { t as reviewAgentCliHelp } from "./agent-cli-BBPxwUOd.js";
17
16
  import { closeSync, existsSync, fstatSync, mkdirSync, mkdtempSync, openSync, readSync, renameSync, writeFileSync } from "node:fs";
18
17
  import { realpath } from "node:fs/promises";
19
18
  import path from "node:path";
@@ -394,7 +393,7 @@ async function runReviewAppPick(input, overrides = {}) {
394
393
  else {
395
394
  if (!input.stdin.isTTY) throw new ReviewAppUsageError("whiteboard app pick needs a terminal without --session. Pass --session <uuid> or run it in a terminal.");
396
395
  const root = await runtime.resolveReviewRoot(input.cwd);
397
- const reviews = (await client.read("")).filter((review) => review.repositoryPath === root && !review.dismissedAt).sort((a, b) => b.createdAt.localeCompare(a.createdAt));
396
+ const reviews = (await client.read("")).filter((review) => review.repositoryPath === root && !review.trashedAt).sort((a, b) => b.createdAt.localeCompare(a.createdAt));
398
397
  if (!reviews.length) throw new ReviewAppStateError("No review to show.");
399
398
  const picked = await runtime.pickReview(reviews.map((review) => ({
400
399
  uuid: review.reviewId,
@@ -440,7 +439,7 @@ async function runReviewInfo(input, runtime = {
440
439
  const root = await runtime.resolveReviewRoot(input.cwd);
441
440
  return {
442
441
  event: "info",
443
- reviews: reviews.filter((review) => review.repositoryPath === root && (input.all || !review.dismissedAt))
442
+ reviews: reviews.filter((review) => review.repositoryPath === root && (input.all || !review.trashedAt))
444
443
  };
445
444
  }
446
445
  //#endregion
@@ -4495,7 +4494,7 @@ async function runReviewCli(input) {
4495
4494
  process.once("SIGINT", stop);
4496
4495
  process.once("SIGTERM", stop);
4497
4496
  try {
4498
- const { runHeadlessServer } = await import("./headless-host-DcWaxx9P.js");
4497
+ const { runHeadlessServer } = await import("./headless-host-BHD_Vg5F.js");
4499
4498
  await runHeadlessServer({
4500
4499
  stateDir,
4501
4500
  port,
@@ -4546,7 +4545,7 @@ async function runReviewCli(input) {
4546
4545
  if (problem) throw new Error(`Cannot tell whether a Whiteboard Desktop is using ${stateDir}: ${problem.message}`);
4547
4546
  const inUse = () => /* @__PURE__ */ new Error(`A Whiteboard server is using ${stateDir}. Stop it first, then run whiteboard server reset-id again.`);
4548
4547
  if (desktops.instances.some(({ discovery }) => [discovery.appPid, discovery.serverPid].some((pid) => processIsAlive(pid)))) throw inUse();
4549
- const [{ openReviewProfile }, { withHeadlessServerLock }] = await Promise.all([import("./profile-BQGaX8aX.js"), import("./headless-host-DcWaxx9P.js")]);
4548
+ const [{ openReviewProfile }, { withHeadlessServerLock }] = await Promise.all([import("./profile-j97aRTS7.js"), import("./headless-host-BHD_Vg5F.js")]);
4550
4549
  const reset = await withHeadlessServerLock(await realpath(stateDir), async () => {
4551
4550
  const local = await openReviewProfile(stateDir, { manageWorkspaces: false });
4552
4551
  try {
@@ -4686,14 +4685,14 @@ async function runReviewCli(input) {
4686
4685
  command.showHelpAfterError(false).error("Markdown review migration is no longer supported. Ask your agent to migrate your old Whiteboard reviews.");
4687
4686
  });
4688
4687
  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) => {
4689
- const { runShareCli } = await import("./cli-gRGxkwc0.js");
4688
+ const { runShareCli } = await import("./cli-C0ziP6hg.js");
4690
4689
  state.exitCode = await runShareCli({
4691
4690
  ...input,
4692
4691
  ...options,
4693
4692
  env: authoringEnv()
4694
4693
  });
4695
4694
  }).command("revoke <share-id>").description("Revoke future downloads of a share"), "plain").action(async (shareId, options) => {
4696
- const { runShareCli } = await import("./cli-gRGxkwc0.js");
4695
+ const { runShareCli } = await import("./cli-C0ziP6hg.js");
4697
4696
  state.exitCode = await runShareCli({
4698
4697
  ...input,
4699
4698
  ...options,
@@ -4774,7 +4773,7 @@ async function runReviewCli(input) {
4774
4773
  return commands;
4775
4774
  } });
4776
4775
  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) => {
4777
- const { runReviewAgentCli } = await import("./agent-cli-BR2j-2n5.js");
4776
+ const { runReviewAgentCli } = await import("./agent-cli-YSuhE5X8.js");
4778
4777
  state.exitCode = await runReviewAgentCli({
4779
4778
  ...input,
4780
4779
  env: authoringEnv(),
package/dist/cli.js CHANGED
@@ -17,7 +17,7 @@ async function runCli(effectivePath) {
17
17
  `);
18
18
  return 1;
19
19
  }
20
- const { runReviewCli } = await import("./cli-runner-Cjl8h376.js");
20
+ const { runReviewCli } = await import("./cli-runner-C1gY-HAI.js");
21
21
  return runReviewCli({
22
22
  argv,
23
23
  cliPaths: {
@@ -1,8 +1,7 @@
1
- import { d as gitAt } from "./src-CmdiBL20.js";
1
+ import { C as ReviewInputError, d as gitAt } from "./src-DiHP11l3.js";
2
2
  import { n as isJsonObject, u as parseJsonText } from "./src-X9phtB2j.js";
3
3
  import "./src-WCbabvdS.js";
4
- import { B as parseMarkdown, C as resourceReferences, E as sourceReferences, K as sourceAnchors, M as lensSchema, N as lensSelections, O as checkReferences, S as resourceReference, _ as hasCodeReferences, b as pinsSchema, c as traceSchema, h as elements, l as textIncludesQuote, n as migrateStoredDocument, o as normalizedSoftwareElementSchema, p as documentSchema, r as migrateStoredLenses, s as normalizedSoftwareRelationshipSchema, t as liftFileLenses, u as anchorPins, z as markdownNodes } from "./stored-document-migration-BxZWQQjv.js";
5
- import { t as ReviewInputError } from "./input-error-OLgB_h31.js";
4
+ import { B as parseMarkdown, C as resourceReferences, E as sourceReferences, K as sourceAnchors, M as lensSchema, N as lensSelections, O as checkReferences, S as resourceReference, _ as hasCodeReferences, b as pinsSchema, c as traceSchema, h as elements, l as textIncludesQuote, n as migrateStoredDocument, o as normalizedSoftwareElementSchema, p as documentSchema, r as migrateStoredLenses, s as normalizedSoftwareRelationshipSchema, t as liftFileLenses, u as anchorPins, z as markdownNodes } from "./stored-document-migration-u4L_1VZa.js";
6
5
  import { createHash, randomUUID } from "node:crypto";
7
6
  import { z } from "zod";
8
7
  import { existsSync } from "node:fs";
@@ -127,7 +126,7 @@ function validateShareSources(snapshot) {
127
126
  }
128
127
  /** Freeze one stored version before any asynchronous source reads. */
129
128
  async function exportShare(input) {
130
- const { target, kind: _kind, staleSources: _staleSources, sourceUnavailable: _sourceUnavailable, lastEdit: _lastEdit, ...snapshot } = structuredClone(input.store.read(input.reviewId, input.version));
129
+ const { target, kind: _kind, staleSources: _staleSources, sourceUnavailable: _sourceUnavailable, lastEdit: _lastEdit, trashedAt: _trashedAt, ...snapshot } = structuredClone(input.store.read(input.reviewId, input.version));
131
130
  if (target?.kind === "worktree") throw new ReviewInputError("Pin this review to commits before sharing it.");
132
131
  documentSchema.parse(snapshot.document);
133
132
  checkReferences(snapshot.document);
@@ -439,12 +438,16 @@ var SharedReviewStore = class {
439
438
  attention = /* @__PURE__ */ new Map();
440
439
  listeners = /* @__PURE__ */ new Set();
441
440
  catalogLoaded = false;
441
+ purgeTimer;
442
+ purgePending;
442
443
  downloads = /* @__PURE__ */ new Set();
443
444
  trackImport(job) {
444
445
  this.downloads.add(job);
445
446
  job.finally(() => this.downloads.delete(job));
446
447
  }
447
448
  async close() {
449
+ clearInterval(this.purgeTimer);
450
+ await this.purgePending;
448
451
  await Promise.allSettled([...this.downloads, ...this.jobs.values()]);
449
452
  }
450
453
  local;
@@ -454,12 +457,28 @@ var SharedReviewStore = class {
454
457
  constructor(root, fetchRepository = fetchPinnedRepository) {
455
458
  this.root = root;
456
459
  this.fetchRepository = fetchRepository;
460
+ this.purgeTimer = setInterval(() => {
461
+ this.purgeExpired().catch((error) => console.error("Could not purge expired shared whiteboards:", error));
462
+ }, 6e4);
463
+ this.purgeTimer.unref();
464
+ }
465
+ purgeExpired() {
466
+ if (this.purgePending) return this.purgePending;
467
+ const expired = [...this.attention].filter(([, state]) => state.trashedAt && Date.parse(state.trashedAt) <= Date.now() - 2592e6);
468
+ const pending = (async () => {
469
+ for (const [id] of expired) await this.removeLocal(id);
470
+ })();
471
+ this.purgePending = pending.finally(() => {
472
+ this.purgePending = void 0;
473
+ });
474
+ return this.purgePending;
457
475
  }
458
476
  connect(store, data) {
459
477
  this.local = {
460
478
  store,
461
479
  data
462
480
  };
481
+ store.askHistory.setExternalWriteGuard((id) => this.assertWritable(id));
463
482
  data.workspaces.attachExternalReviews({
464
483
  has: (id) => this.has(id) || !this.catalogLoaded && id.startsWith("shared-"),
465
484
  subscribe: (listener) => this.subscribe(listener)
@@ -602,16 +621,32 @@ var SharedReviewStore = class {
602
621
  this.repositories.set(id, repository.id);
603
622
  this.setStatus(id, "ready");
604
623
  } else this.setStatus(id, "error", "Reopen the share link to fetch its repository.");
605
- try {
606
- this.attention.set(id, z.strictObject({
624
+ const attentionPath = path.join(this.root, id, "attention.json");
625
+ if (existsSync(attentionPath)) {
626
+ const raw = JSON.parse(await readFile(attentionPath, "utf8"));
627
+ const migrated = Object.hasOwn(raw, "dismissedAt") || Object.hasOwn(raw, "deletedAt");
628
+ const state = z.strictObject({
607
629
  viewedAt: z.string().nullable(),
608
- dismissedAt: z.string().nullable()
609
- }).parse(JSON.parse(await readFile(path.join(this.root, id, "attention.json"), "utf8"))));
610
- } catch {}
630
+ trashedAt: z.string().nullable()
631
+ }).parse(migrated ? {
632
+ viewedAt: raw.viewedAt,
633
+ trashedAt: Object.hasOwn(raw, "deletedAt") ? raw.deletedAt : raw.dismissedAt ? (/* @__PURE__ */ new Date()).toISOString() : null
634
+ } : raw);
635
+ this.attention.set(id, state);
636
+ if (migrated) await writeFile(attentionPath, JSON.stringify(state), { mode: 384 });
637
+ }
611
638
  }
639
+ await this.purgeExpired();
612
640
  this.catalogLoaded = true;
613
641
  for (const listener of this.listeners) listener();
614
642
  }
643
+ assertWritable(id) {
644
+ const trashedAt = this.attention.get(id)?.trashedAt;
645
+ if (trashedAt) throw new ReviewInputError(`This whiteboard was moved to Trash on ${new Date(trashedAt).toLocaleDateString("en-US", {
646
+ month: "short",
647
+ day: "numeric"
648
+ })}. Restore it with session_lifecycle({"sessionId":"${id}","state":"active"}) to continue.`, 409);
649
+ }
615
650
  get(id) {
616
651
  const bundle = this.loaded.get(id);
617
652
  if (!bundle) throw new ReviewInputError("Shared review is not available.", 404);
@@ -623,6 +658,7 @@ var SharedReviewStore = class {
623
658
  snapshot: {
624
659
  ...validated.snapshot,
625
660
  reviewId: id,
661
+ trashedAt: this.attention.get(id)?.trashedAt ?? null,
626
662
  pins: validated.snapshot.pins ? {
627
663
  ...validated.snapshot.pins,
628
664
  repositoryId
@@ -647,7 +683,7 @@ var SharedReviewStore = class {
647
683
  firstCreatedAt: snapshot.version === 0 ? snapshot.createdAt : void 0,
648
684
  repositoryName: "Shared review",
649
685
  viewedAt: this.attention.get(id)?.viewedAt ?? null,
650
- dismissedAt: this.attention.get(id)?.dismissedAt ?? null
686
+ trashedAt: this.attention.get(id)?.trashedAt ?? null
651
687
  };
652
688
  }).toArray();
653
689
  return this.local?.store.withDiffStats(reviews, mode) ?? reviews;
@@ -680,19 +716,32 @@ var SharedReviewStore = class {
680
716
  recursive: true,
681
717
  force: true
682
718
  });
719
+ this.local?.store.askHistory.removeReview(id);
683
720
  this.loaded.delete(id);
684
721
  this.validated.delete(id);
685
722
  this.attention.delete(id);
686
723
  for (const listener of this.listeners) listener();
687
724
  }
688
- async setAttention(id, action) {
725
+ async setAttention(id) {
726
+ await this.updateAttention(id, "view");
727
+ }
728
+ async setLifecycle(id, state) {
729
+ await this.updateAttention(id, state);
730
+ }
731
+ async updateAttention(id, action) {
689
732
  this.get(id);
733
+ const trashedAt = this.attention.get(id)?.trashedAt;
734
+ if (trashedAt && Date.parse(trashedAt) <= Date.now() - 2592e6) {
735
+ await this.removeLocal(id);
736
+ throw new ReviewInputError("The 30-day recovery period has ended. This whiteboard was permanently deleted.", 404);
737
+ }
738
+ if (action === "view") this.assertWritable(id);
690
739
  const state = this.attention.get(id) ?? {
691
740
  viewedAt: null,
692
- dismissedAt: null
741
+ trashedAt: null
693
742
  };
694
743
  if (action === "view") state.viewedAt = (/* @__PURE__ */ new Date()).toISOString();
695
- else state.dismissedAt = action === "dismiss" ? (/* @__PURE__ */ new Date()).toISOString() : null;
744
+ else state.trashedAt = action === "trash" ? state.trashedAt ?? (/* @__PURE__ */ new Date()).toISOString() : null;
696
745
  await writeFile(path.join(this.root, id, "attention.json"), JSON.stringify(state), { mode: 384 });
697
746
  this.attention.set(id, state);
698
747
  for (const listener of this.listeners) listener();
@@ -700,6 +749,7 @@ var SharedReviewStore = class {
700
749
  async import(origin, shareId, bundle) {
701
750
  const validated = validateShareBundle(bundle);
702
751
  const id = sharedReviewId(origin, shareId);
752
+ this.assertWritable(id);
703
753
  await mkdir(this.root, {
704
754
  recursive: true,
705
755
  mode: 448
@@ -1,6 +1,6 @@
1
1
  import { a as jsonNumber, n as isJsonObject, s as jsonProperty, u as parseJsonText } from "./src-X9phtB2j.js";
2
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-6zZH-1c9.js";
3
+ import { a as reviewInstancesDir, o as reviewLegacyDiscoveryPath, r as reviewDevInstanceKey, t as reviewDefaultInstancePath } from "./review-home-paths-Y9mYWkCI.js";
4
4
  import { mkdir, readFile, readdir, rm, writeFile } from "node:fs/promises";
5
5
  import path from "node:path";
6
6
  //#region src/desktop-discovery.ts
@@ -1,9 +1,9 @@
1
1
  import { p as isObjectValue } from "./src-X9phtB2j.js";
2
+ import "./review-home-paths-Y9mYWkCI.js";
2
3
  import { O as withFileLock, P as writePrivateJsonAtomic } from "./s3-config-CAxhO9u_.js";
3
- import "./review-home-paths-6zZH-1c9.js";
4
- import { t as openReviewProfile } from "./profile-RDEioF8b.js";
5
- import { i as createWhiteboardCore, l as createNodeRequestListener, m as migrateDiffrConfig, n as installProcessErrorTelemetry, p as GlobalReviewDesktopVerbRelay, t as drainServerCrashReport } from "./process-error-telemetry-Dzh5a3s_.js";
6
- import { i as reviewServerDiscoveryPath, t as headlessServerLockPath } from "./server-discovery-Dbzn3w6Z.js";
4
+ import { t as openReviewProfile } from "./profile-BsYDBr7t.js";
5
+ import { i as createWhiteboardCore, l as createNodeRequestListener, m as migrateDiffrConfig, n as installProcessErrorTelemetry, p as GlobalReviewDesktopVerbRelay, t as drainServerCrashReport } from "./process-error-telemetry-C0swByfp.js";
6
+ import { i as reviewServerDiscoveryPath, t as headlessServerLockPath } from "./server-discovery-ZVi8Ay8J.js";
7
7
  import { randomBytes, randomUUID } from "node:crypto";
8
8
  import { mkdir, realpath, rm } from "node:fs/promises";
9
9
  import { createServer } from "node:http";