@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
@@ -0,0 +1,153 @@
1
+ import { b as REVIEW_REMOTE_INSTALL_LOCK, x as REVIEW_REMOTE_WRAPPER_MARK } from "./src-Bgvf1sEN.js";
2
+ import "./review-home-paths-zwevIq4I.js";
3
+ import { j as DEV_REVIEW_HOME_ENV } from "./s3-config-DDWWMh5x.js";
4
+ import { c as stopBackgroundServer, o as recordedBackgroundServer } from "./background-server-Cw7CcfaZ.js";
5
+ import { i as whiteboardRemoteHome } from "./remote-extensions-BiNx6NQK.js";
6
+ import { n as remoteLanguageServerGroups, r as stopProcessGroup } from "./remote-language-server-DQgWUNeV.js";
7
+ import { existsSync } from "node:fs";
8
+ import { lstat, mkdir, readFile, readdir, rm, stat } from "node:fs/promises";
9
+ import path from "node:path";
10
+ import { execFile } from "node:child_process";
11
+ import { promisify } from "node:util";
12
+ //#region src/remote-uninstall.ts
13
+ const LOCK_STALE_MS = 9e5;
14
+ const REVIEW_STORE = [
15
+ "review-api.db",
16
+ "review-api.db-wal",
17
+ "review-api.db-shm",
18
+ "review-api.db.workspaces",
19
+ "review-api.db.workspaces-wal",
20
+ "review-api.db.workspaces-shm"
21
+ ];
22
+ async function remoteUninstall(input) {
23
+ const refuse = (reason) => ({
24
+ event: "remote.uninstall",
25
+ ok: false,
26
+ reason
27
+ });
28
+ if (!path.isAbsolute(input.home)) return refuse(`HOME is ${input.home ? JSON.stringify(input.home) : "not set"}; Whiteboard removes nothing without an absolute home.`);
29
+ if (!path.isAbsolute(input.stateDir)) return refuse(`The review home ${JSON.stringify(input.stateDir)} is not an absolute path.`);
30
+ const override = input.env[DEV_REVIEW_HOME_ENV]?.trim();
31
+ if (override && (/[\x00-\x1f\x7f-\x9f]/.test(override) || override.includes("//") || path.resolve(override) !== (override.replace(/\/$/, "") || "/"))) return refuse(`DEV_REVIEW_HOME is ${JSON.stringify(override)}; Whiteboard removes nothing under a review home that is not an absolute, normalised path.`);
32
+ const install = whiteboardRemoteHome(input.env);
33
+ const wrapper = path.join(input.home, ".local", "bin", "whiteboard");
34
+ const lock = path.join(install, REVIEW_REMOTE_INSTALL_LOCK);
35
+ const busy = async () => refuse(`Desktop on ${await readText(path.join(lock, "owner")) || "another computer"} is installing Whiteboard here. Try again when it is done.`);
36
+ if (existsSync(lock)) {
37
+ if (!await stale(lock)) return busy();
38
+ await rm(lock, {
39
+ recursive: true,
40
+ force: true
41
+ });
42
+ }
43
+ const held = existsSync(install);
44
+ if (held && !await mkdir(lock).then(() => true, () => false)) return busy();
45
+ try {
46
+ return await removeInstall(input, install, wrapper, refuse);
47
+ } finally {
48
+ if (held) await rm(lock, {
49
+ recursive: true,
50
+ force: true
51
+ });
52
+ }
53
+ }
54
+ async function removeInstall(input, install, wrapper, refuse) {
55
+ const server = await recordedBackgroundServer(input.stateDir);
56
+ const recorded = server && {
57
+ ...server.discovery,
58
+ version: server.health.version ?? null
59
+ };
60
+ const stoppable = recorded && recorded.startedBy !== "user" ? recorded.serverPid : void 0;
61
+ const groups = await remoteLanguageServerGroups(input.env);
62
+ const running = await processesFrom(`${install}/`);
63
+ if (running === void 0) return refuse(`Cannot list this host's processes to check that none runs from ${install}.`);
64
+ const others = running.filter(({ pid, pgid }) => pid !== stoppable && !groups.includes(pgid)).map(({ pid }) => pid);
65
+ if (others.length) return refuse(`${others.map((pid) => pid === recorded?.serverPid ? `A Whiteboard server you started (process ${pid})` : `Process ${pid}`).join(", ")} ${others.length > 1 ? "run" : "runs"} from ${install}. Stop ${others.length > 1 ? "them" : "it"}, then run whiteboard remote uninstall again.`);
66
+ if (recorded?.startedBy === "user" && input.deleteReviews) return refuse(`A Whiteboard server you started (process ${recorded.serverPid}) uses the reviews in ${input.stateDir}. Stop it, then run whiteboard remote uninstall again.`);
67
+ await Promise.all(groups.map(stopProcessGroup));
68
+ let stoppedServer;
69
+ if (recorded && stoppable !== void 0) {
70
+ try {
71
+ await stopBackgroundServer({ serverPid: stoppable });
72
+ } catch (error) {
73
+ return refuse(error instanceof Error ? error.message : String(error));
74
+ }
75
+ stoppedServer = {
76
+ pid: stoppable,
77
+ version: recorded.version
78
+ };
79
+ }
80
+ const left = await processesFrom(`${install}/`);
81
+ if (left?.length !== 0) return refuse(`${left ? `Process ${left.map(({ pid }) => pid).join(", ")}` : "A process"} still runs from ${install}. Stop it, then run whiteboard remote uninstall again.`);
82
+ const removed = [];
83
+ if (existsSync(install)) {
84
+ await rm(install, {
85
+ recursive: true,
86
+ force: true
87
+ });
88
+ removed.push(install);
89
+ }
90
+ if (await desktopWrote(wrapper)) {
91
+ await rm(wrapper, { force: true });
92
+ removed.push(wrapper);
93
+ }
94
+ if (input.deleteReviews) for (const name of REVIEW_STORE) {
95
+ const file = path.join(input.stateDir, name);
96
+ if (existsSync(file)) {
97
+ await rm(file, { force: true });
98
+ removed.push(file);
99
+ }
100
+ }
101
+ const result = {
102
+ event: "remote.uninstall",
103
+ ok: true,
104
+ removed,
105
+ keptReviews: !input.deleteReviews
106
+ };
107
+ if (stoppedServer) result.stoppedServer = stoppedServer;
108
+ return result;
109
+ }
110
+ async function stale(lock) {
111
+ const started = await readText(path.join(lock, "started"));
112
+ if (started !== "") return /^\d+$/.test(started) && Date.now() - Number(started) * 1e3 >= LOCK_STALE_MS;
113
+ if (existsSync(path.join(lock, "started"))) return false;
114
+ const since = (await stat(lock).catch(() => void 0))?.mtimeMs;
115
+ return since !== void 0 && Date.now() - since >= LOCK_STALE_MS;
116
+ }
117
+ const readText = (file) => readFile(file, "utf8").then((text) => text.trim(), () => "");
118
+ async function processesFrom(prefix) {
119
+ let lines;
120
+ try {
121
+ const pids = (await readdir("/proc")).filter((name) => /^\d+$/.test(name));
122
+ lines = await Promise.all(pids.map(async (pid) => {
123
+ const [args = "", stat = ""] = await Promise.all([`/proc/${pid}/cmdline`, `/proc/${pid}/stat`].map((file) => readFile(file, "utf8").catch(() => "")));
124
+ const pgid = Number(stat.slice(stat.lastIndexOf(")") + 2).split(" ")[2]);
125
+ return {
126
+ pid: Number(pid),
127
+ pgid,
128
+ args: args.replaceAll("\0", " ")
129
+ };
130
+ }));
131
+ } catch {
132
+ try {
133
+ const { stdout } = await promisify(execFile)("ps", ["-eo", "pid=,pgid=,args="], { maxBuffer: 16 << 20 });
134
+ lines = stdout.split("\n").map((line) => {
135
+ const [, pid = "", pgid = "", args = ""] = /^\s*(\d+)\s+(\d+)\s(.*)$/.exec(line) ?? [];
136
+ return {
137
+ pid: Number(pid),
138
+ pgid: Number(pgid),
139
+ args
140
+ };
141
+ });
142
+ } catch {
143
+ return;
144
+ }
145
+ }
146
+ return lines.filter(({ pid, args }) => pid && pid !== process.pid && args.includes(prefix));
147
+ }
148
+ async function desktopWrote(file) {
149
+ if (!(await lstat(file).catch(() => void 0))?.isFile()) return false;
150
+ return (await readFile(file, "utf8")).split("\n").includes(REVIEW_REMOTE_WRAPPER_MARK);
151
+ }
152
+ //#endregion
153
+ export { remoteUninstall };
@@ -1,5 +1,5 @@
1
1
  import { a as jsonNumber, c as jsonString, i as jsonBoolean, n as isJsonObject } from "./src-X9phtB2j.js";
2
- import { f as REVIEW_DIFF_LAYOUTS, p as REVIEW_THEME_CHOICES } from "./src-WCbabvdS.js";
2
+ import { S as REVIEW_THEME_CHOICES, h as REVIEW_DIFF_LAYOUT_SCOPES, m as REVIEW_DIFF_LAYOUTS } from "./src-Bgvf1sEN.js";
3
3
  import { z } from "zod";
4
4
  //#region src/telemetry-clean-text.ts
5
5
  const USER_DATA_REGEXES = [
@@ -442,7 +442,10 @@ const UI_TELEMETRY_EVENTS = {
442
442
  },
443
443
  diff_layout_changed: {
444
444
  event: "review_diff_layout_changed",
445
- properties: { layout: REVIEW_DIFF_LAYOUTS }
445
+ properties: {
446
+ layout: REVIEW_DIFF_LAYOUTS,
447
+ scope: REVIEW_DIFF_LAYOUT_SCOPES
448
+ }
446
449
  },
447
450
  review_dismissed: {
448
451
  event: "review_review_dismissed",
@@ -0,0 +1,222 @@
1
+ import { o as parseReviewVerbResponse } from "./src-Bgvf1sEN.js";
2
+ import { b as selectReviewInstance, m as healthyReviewInstance, v as reviewInstanceStartHint, y as reviewInstanceUnavailable } from "./server-discovery-CifQ3yQS.js";
3
+ import { closeSync, existsSync, fstatSync, mkdtempSync, openSync, readSync } from "node:fs";
4
+ import path from "node:path";
5
+ import { homedir, tmpdir } from "node:os";
6
+ import { execFileSync, spawn } from "node:child_process";
7
+ //#region src/review-app-launcher.ts
8
+ const RELEASE_APPS = {
9
+ stable: {
10
+ bundleId: "dev.fast.review",
11
+ macApp: "Whiteboard.app",
12
+ linuxLauncher: "/usr/bin/review-desktop"
13
+ },
14
+ preview: {
15
+ bundleId: "dev.fast.review.preview",
16
+ macApp: "Whiteboard Preview.app",
17
+ linuxLauncher: "/usr/bin/review-preview-desktop"
18
+ }
19
+ };
20
+ /** "1" on launches without --focus; Desktop opens inactive. */
21
+ const REVIEW_DESKTOP_BACKGROUND_ENV = "DEV_FAST_REVIEW_DESKTOP_BACKGROUND";
22
+ const DEFAULT_LAUNCH_TIMEOUT_MS = 9e4;
23
+ const POLL_INTERVAL_MS = 250;
24
+ const EARLY_EXIT_GRACE_MS = 5e3;
25
+ async function runReviewAppLaunch(input = {}, overrides = {}) {
26
+ const fetch = overrides.fetch ?? globalThis.fetch;
27
+ const runtime = {
28
+ selectInstance: () => selectReviewInstance({ fetch }),
29
+ fetch,
30
+ focusDesktop: (discovery) => focusReviewDesktop(discovery, fetch),
31
+ launchDesktop: launchDesktopApplication,
32
+ now: Date.now,
33
+ wait: (milliseconds) => new Promise((resolve) => setTimeout(resolve, milliseconds)),
34
+ ...overrides
35
+ };
36
+ const selection = await runtime.selectInstance();
37
+ const current = healthyReviewInstance(selection);
38
+ if (current) {
39
+ if (input.focus) await runtime.focusDesktop(current);
40
+ return launchEvent("running", current.instanceId);
41
+ }
42
+ const running = selection.instances.filter((instance) => instance.healthy);
43
+ if (selection.key !== "stable" && selection.key !== "preview") throw new Error(`${reviewInstanceStartHint(selection)}; \`whiteboard app launch\` starts only installed apps.`);
44
+ if (selection.source === "fallback" && running.length > 1) throw reviewInstanceUnavailable(selection);
45
+ const launch = {
46
+ focus: input.focus,
47
+ noSandbox: input.noSandbox
48
+ };
49
+ if (selection.source !== "fallback") launch.instance = {
50
+ key: selection.key,
51
+ appPath: selection.instance?.discovery.appPath
52
+ };
53
+ const attempt = runtime.launchDesktop(launch);
54
+ let completion = observedCompletion(attempt);
55
+ completion.catch(() => void 0);
56
+ const deadline = runtime.now() + (input.timeoutMs ?? DEFAULT_LAUNCH_TIMEOUT_MS);
57
+ let unexpectedSuccessfulExitAt;
58
+ while (runtime.now() < deadline) {
59
+ const ready = healthyReviewInstance(await runtime.selectInstance());
60
+ if (ready) return launchEvent("launched", ready.instanceId);
61
+ if (unexpectedSuccessfulExitAt !== void 0 && runtime.now() - unexpectedSuccessfulExitAt >= EARLY_EXIT_GRACE_MS) throw launchFailure(attempt, /* @__PURE__ */ new Error("the launch process exited before Desktop became ready"));
62
+ const remaining = Math.max(0, deadline - runtime.now());
63
+ const outcome = completion ? await Promise.race([completion.then((result) => ({ completion: result })), runtime.wait(Math.min(POLL_INTERVAL_MS, remaining)).then(() => ({ completion: null }))]) : await runtime.wait(Math.min(POLL_INTERVAL_MS, remaining)).then(() => ({ completion: null }));
64
+ if (outcome.completion) {
65
+ assertSuccessfulLaunchCompletion(attempt, outcome.completion);
66
+ unexpectedSuccessfulExitAt = runtime.now();
67
+ completion = void 0;
68
+ }
69
+ }
70
+ if (unexpectedSuccessfulExitAt !== void 0) throw launchFailure(attempt, /* @__PURE__ */ new Error("the launch process exited before Desktop became ready"));
71
+ throw new Error(`Whiteboard Desktop did not become ready within ${Math.ceil((input.timeoutMs ?? DEFAULT_LAUNCH_TIMEOUT_MS) / 1e3)} seconds after ${attempt.method}. Open Whiteboard Desktop once, then run \`whiteboard app launch\` again.${launchDiagnostics(attempt.logPath)}`);
72
+ }
73
+ async function focusReviewDesktop(discovery, fetch) {
74
+ const response = await fetch(`${discovery.url}/app/focus`, {
75
+ method: "POST",
76
+ headers: { "x-review-token": discovery.token },
77
+ signal: AbortSignal.timeout(5e3)
78
+ });
79
+ const result = parseReviewVerbResponse(await response.json());
80
+ if (!response.ok || !result.ok) throw new Error(result.ok ? `Whiteboard Desktop focus returned ${response.status}.` : result.error);
81
+ }
82
+ function desktopApplicationInstalled(input = {}) {
83
+ const env = input.env ?? process.env;
84
+ const platform = input.platform ?? process.platform;
85
+ const exists = input.exists ?? existsSync;
86
+ const apps = Object.values(RELEASE_APPS);
87
+ if (platform !== "linux" && platform !== "darwin" || (input.electron ?? Boolean(process.versions.electron)) || env.DEV_FAST_REVIEW_DESKTOP_COMMAND?.trim()) return true;
88
+ if (platform === "linux") return apps.some(({ linuxLauncher }) => exists(linuxLauncher));
89
+ return apps.some(({ macApp }) => ["/Applications", path.join(homedir(), "Applications")].some((dir) => exists(path.join(dir, macApp)))) || apps.some(({ bundleId }) => (input.spotlight ?? spotlightFinds)(bundleId));
90
+ }
91
+ function spotlightFinds(bundleId) {
92
+ try {
93
+ return execFileSync("/usr/bin/mdfind", [`kMDItemCFBundleIdentifier == '${bundleId}'`], {
94
+ encoding: "utf8",
95
+ timeout: 2e3
96
+ }).trim() !== "";
97
+ } catch {
98
+ return false;
99
+ }
100
+ }
101
+ function launchDesktopApplication(input = {}) {
102
+ const platform = input.platform ?? process.platform;
103
+ if (platform !== "darwin" && platform !== "linux") return {
104
+ method: `the ${platform} application launcher`,
105
+ completion: Promise.reject(/* @__PURE__ */ new Error("automatic launch is available only on macOS and Linux"))
106
+ };
107
+ if (input.noSandbox && platform !== "linux") return {
108
+ method: `the ${platform} application launcher`,
109
+ completion: Promise.reject(/* @__PURE__ */ new Error("--no-sandbox is only supported for Linux Desktop launches."))
110
+ };
111
+ const electron = input.electron ?? Boolean(process.versions.electron);
112
+ const env = { ...input.env ?? process.env };
113
+ const execPath = input.execPath ?? process.execPath;
114
+ const focus = input.focus === true;
115
+ if (focus) delete env[REVIEW_DESKTOP_BACKGROUND_ENV];
116
+ else env[REVIEW_DESKTOP_BACKGROUND_ENV] = "1";
117
+ delete env.ELECTRON_RUN_AS_NODE;
118
+ delete env.DEV_FAST_REVIEW_CHECKOUT;
119
+ const release = RELEASE_APPS[input.instance?.key ?? "stable"];
120
+ const stateRoot = env.DEV_FAST_REVIEW_DESKTOP_STATE_ROOT?.trim();
121
+ const profileArgs = stateRoot ? [`--user-data-dir=${path.resolve(stateRoot, "user-data")}`, `--extensions-dir=${path.resolve(stateRoot, "extensions")}`] : [];
122
+ let command = "/usr/bin/open";
123
+ let method;
124
+ let args;
125
+ if (platform === "linux") {
126
+ delete env.VSCODE_DEV;
127
+ delete env.VSCODE_CLI;
128
+ command = (input.instance ? "" : env.DEV_FAST_REVIEW_DESKTOP_COMMAND?.trim()) || release.linuxLauncher;
129
+ method = `the installed Linux launcher at "${command}"`;
130
+ if (electron && !env.DEV_FAST_REVIEW_DESKTOP_COMMAND?.trim()) {
131
+ command = execPath;
132
+ method = `the Desktop-managed bundle at "${command}"`;
133
+ }
134
+ args = profileArgs;
135
+ if (input.noSandbox) args.push("--no-sandbox");
136
+ } else {
137
+ const appPath = input.instance ? input.instance.appPath : electron ? execPath.match(/^(.*?\.app)\/Contents\/MacOS\//)?.[1] : void 0;
138
+ const target = appPath?.endsWith(".app") ? ["-a", appPath] : ["-b", release.bundleId];
139
+ method = appPath?.endsWith(".app") ? `the macOS application at "${appPath}"` : `the macOS bundle identifier "${release.bundleId}"`;
140
+ args = [
141
+ "-n",
142
+ "-W",
143
+ ...focus ? [] : ["-g"],
144
+ ...target
145
+ ];
146
+ for (const [key, value] of Object.entries(env)) if (value !== void 0 && /^DEV_(REVIEW|FAST)_/.test(key)) args.push("--env", `${key}=${value}`);
147
+ if (profileArgs.length > 0) args.push("--args", ...profileArgs);
148
+ }
149
+ const { promise: completion, resolve, reject } = Promise.withResolvers();
150
+ let logPath;
151
+ let logFd;
152
+ try {
153
+ if (platform === "linux") {
154
+ logPath = path.join(mkdtempSync(path.join(tmpdir(), "whiteboard-launch-")), "stderr.log");
155
+ logFd = openSync(logPath, "wx", 384);
156
+ }
157
+ const child = (input.spawn ?? spawn)(command, args, {
158
+ detached: true,
159
+ env,
160
+ stdio: logFd === void 0 ? "ignore" : [
161
+ "ignore",
162
+ "ignore",
163
+ logFd
164
+ ]
165
+ });
166
+ child.once("error", (error) => reject(error));
167
+ child.once("exit", (code, signal) => {
168
+ resolve({
169
+ code,
170
+ signal
171
+ });
172
+ });
173
+ child.unref();
174
+ } catch (error) {
175
+ reject(error instanceof Error ? error : new Error(String(error)));
176
+ } finally {
177
+ if (logFd !== void 0) closeSync(logFd);
178
+ }
179
+ return {
180
+ method,
181
+ logPath,
182
+ completion
183
+ };
184
+ }
185
+ function launchEvent(state, instanceId) {
186
+ return {
187
+ event: "app",
188
+ action: "launch",
189
+ state,
190
+ instanceId
191
+ };
192
+ }
193
+ function launchFailure(attempt, error) {
194
+ return /* @__PURE__ */ new Error(`Could not launch Whiteboard with ${attempt.method}: ${error.message}. Open Whiteboard once, then run \`whiteboard app launch\` again. A sandboxed agent must run it outside the sandbox.${launchDiagnostics(attempt.logPath)}`);
195
+ }
196
+ function assertSuccessfulLaunchCompletion(attempt, completion) {
197
+ if (completion.code === 0 && !completion.signal) return;
198
+ throw launchFailure(attempt, /* @__PURE__ */ new Error(completion.signal ? `the launch process exited on ${completion.signal}` : `the launch process exited with code ${completion.code ?? "unknown"}`));
199
+ }
200
+ function observedCompletion(attempt) {
201
+ return attempt.completion.catch((error) => {
202
+ throw launchFailure(attempt, error instanceof Error ? error : new Error(String(error)));
203
+ });
204
+ }
205
+ function launchDiagnostics(logPath) {
206
+ if (!logPath) return "";
207
+ let tail = "";
208
+ try {
209
+ const fd = openSync(logPath, "r");
210
+ try {
211
+ const size = fstatSync(fd).size;
212
+ const buffer = Buffer.alloc(Math.min(size, 16384));
213
+ const read = readSync(fd, buffer, 0, buffer.length, size - buffer.length);
214
+ tail = buffer.subarray(0, read).toString("utf8").trim().split("\n").slice(-20).join("\n");
215
+ } finally {
216
+ closeSync(fd);
217
+ }
218
+ } catch {}
219
+ return `\nDesktop log: ${logPath}${tail ? `\n${tail}` : ""}`;
220
+ }
221
+ //#endregion
222
+ export { focusReviewDesktop as n, runReviewAppLaunch as r, desktopApplicationInstalled as t };
@@ -1,7 +1,7 @@
1
- import { d as gitAt, u as git, v as resolveRepoContext } from "./src-DiHP11l3.js";
1
+ import { d as git, f as gitAt, y as resolveRepoContext } from "./src-BMPuxWv4.js";
2
2
  import { a as jsonNumber, c as jsonString, i as jsonBoolean, m as isStringValue, n as isJsonObject, o as jsonObject, r as jsonArray, t as isJsonArray, u as parseJsonText } from "./src-X9phtB2j.js";
3
3
  import { a as TRACE_STORE_CLIENT_ID, b as commitShaSchema, c as deleteStoreResponseSchema, d as listUploadsResponseSchema, f as storeErrorCodeSchema, g as uploadManifestMismatch, h as traceObjectNameSchema, i as storeRoutes, l as listSessionsResponseSchema, m as storeResponseSchema, n as DEVICE_TOKEN_PATH, o as beginUploadResponseSchema, p as storeErrorEnvelopeSchema, r as SESSION_PATH, s as completeUploadResponseSchema, t as DEVICE_CODE_PATH, u as listUploadsQuerySchema, x as sessionIdSchema } from "./src-CGP5ytbV.js";
4
- import { A as devReviewHome, E as writeTraceConfigFile, M as writeFileAtomic, N as writeFileAtomicAsync, P as writePrivateJsonAtomic, S as readTraceConfigFile, _ as emptyTraceConfig, b as hostedCaptureEnabled, c as resolveS3Setup, d as traceEnvPath, f as traceEnvValue$1, g as currentStore, h as TraceConfigurationError, i as isS3MockMode, j as normalizeStoreOrigin, l as s3CacheIdentity, m as DEFAULT_HOSTED_ORIGIN, n as clearTraceEnvCache$1, p as traceSettingsPath, u as s3MockRoot, v as enabledOriginsOf, w as s3Store, x as hostedOrigin, y as hasHostedStore } from "./s3-config-CAxhO9u_.js";
4
+ import { E as writeTraceConfigFile, F as writeFileAtomicAsync, I as writePrivateJsonAtomic, M as devReviewHome, N as normalizeStoreOrigin, P as writeFileAtomic, S as readTraceConfigFile, _ as emptyTraceConfig, b as hostedCaptureEnabled, c as resolveS3Setup, d as traceEnvPath, f as traceEnvValue$1, g as currentStore, h as TraceConfigurationError, i as isS3MockMode, l as s3CacheIdentity, m as DEFAULT_HOSTED_ORIGIN, n as clearTraceEnvCache$1, p as traceSettingsPath, u as s3MockRoot, v as enabledOriginsOf, w as s3Store, x as hostedOrigin, y as hasHostedStore } from "./s3-config-DDWWMh5x.js";
5
5
  import { t as errorMessage } from "./error-message-OtiDonty.js";
6
6
  import crypto, { createHash, randomBytes } from "node:crypto";
7
7
  import { z } from "zod";
@@ -2537,7 +2537,7 @@ function isTraceStorageConfigured(scope = {}) {
2537
2537
  }
2538
2538
  /** Loads the direct-store implementation on demand, without opening a store. */
2539
2539
  async function loadS3TraceStorage() {
2540
- return (await import("./s3-SzLpeCG6.js")).S3TraceStorage;
2540
+ return (await import("./s3-77-qkEhs.js")).S3TraceStorage;
2541
2541
  }
2542
2542
  //#endregion
2543
2543
  //#region ../trace-core/src/trace-upload-status.ts
@@ -4684,7 +4684,7 @@ async function describeTraceSession(ref, storage) {
4684
4684
  commits: ref.commits
4685
4685
  };
4686
4686
  }
4687
- /** Loads the offline tutorial or a store-compatible normalized trace, preserving freshness and offline fallback status; store refusals propagate. */
4687
+ /** Loads a store-compatible normalized trace, preserving freshness and offline fallback status; store refusals propagate. */
4688
4688
  async function loadReviewAgentTrace(input) {
4689
4689
  const { sessionId, trace } = input;
4690
4690
  if (!sessionIdSchema.safeParse(sessionId).success) return null;
@@ -5263,7 +5263,7 @@ async function configureTraceMachine(input) {
5263
5263
  if (input.verify !== false) {
5264
5264
  if (env.TRACE_R2_MODE === "mock") verifiedAt = (/* @__PURE__ */ new Date()).toISOString();
5265
5265
  else {
5266
- const { S3TraceStorage } = await import("./s3-SzLpeCG6.js");
5266
+ const { S3TraceStorage } = await import("./s3-77-qkEhs.js");
5267
5267
  const readiness = await S3TraceStorage.fromCredentials({
5268
5268
  endpoint: credentials.endpoint,
5269
5269
  bucket: credentials.bucket,
@@ -5734,7 +5734,7 @@ async function runTraceStatus(input) {
5734
5734
  input.stderr.write("Upload status filters require hosted storage.\n");
5735
5735
  return 1;
5736
5736
  }
5737
- const [{ S3TraceStorage }, { describeS3Setup, noTraceConfigurationMessage }] = await Promise.all([import("./s3-SzLpeCG6.js"), import("./s3-config-BgxkSoOy.js")]);
5737
+ const [{ S3TraceStorage }, { describeS3Setup, noTraceConfigurationMessage }] = await Promise.all([import("./s3-77-qkEhs.js"), import("./s3-config-0xuCh-F_.js")]);
5738
5738
  const setup = describeS3Setup(input.scope.env);
5739
5739
  input.stdout.write(`Checking trace configuration (${setup.envPath})…\n`);
5740
5740
  for (const failure of await listTraceSyncFailures(input.scope.devHome)) input.stdout.write(describeTraceSyncFailure(failure));
@@ -6401,4 +6401,4 @@ function reviewDesktopStateDir(env = process.env) {
6401
6401
  return path.join(reviewDesktopRoot(env), "state");
6402
6402
  }
6403
6403
  //#endregion
6404
- export { resolveTraceCommand as $, runTraceDeny as A, TraceStorageDeniedError as B, disableTraceMachine as C, listReviewTraceSessions as D, traceMachineStatus as E, describeSelection as F, listTraceRepositoryRoots as G, resolveTraceRepositoryTarget as H, isTraceStorageConfigured as I, HOSTED_CAPTURE_SCOPE_DESCRIPTION as J, traceRepositoryStatus as K, loadS3TraceStorage as L, runTraceSessions as M, runTraceStoreDelete as N, loadReviewAgentTrace as O, runTraceStoreInfo as P, removeAgentTraceHook as Q, resolveTraceStorage as R, configureTraceMachine as S, traceMachineEnabled as T, disableAllTraceRepositories as U, requireTraceConsent as V, enableTraceRepository as W, describeTraceHookOwners as X, AGENT_TRACE_HOOK_AGENTS as Y, installHarnessHooks as Z, runTraceRepair as _, reviewInstancesDir as a, openUrlInBrowser as at, runTraceGitHook as b, registerTraceCommands as c, runStoreLogout as ct, runTraceList as d, StoreClient as dt, shellQuote as et, runTracePull as f, withStoreAuthorization as ft, runTraceInstallMachine as g, jsonRequestedInArgv as gt, runTraceEnable as h, humanStream as ht, reviewInstanceDiscoveryPath as i, clearStoreAuth as it, runTraceOnboard as j, runTraceAllow as k, resolveTraceReadStorage as l, runStoreWhoami as lt, runTraceDisable as m, failWithJsonError as mt, reviewDesktopStateDir as n, traceScope as nt, reviewLegacyDiscoveryPath as o, readStoreAuth as ot, runTraceShow as p, emitJsonEvent as pt, readTraceUserConfig as q, reviewDevInstanceKey as r, DEFAULT_STORE_ORIGIN as rt, safeStorageSegment as s, runStoreLogin as st, reviewDefaultInstancePath as t, traceHomeDir as tt, runTraceBlame as u, StoreApiError as ut, runTraceStatus as v, readLegacyCaptureSettings as w, runTraceHook as x, runTraceSync as y, selectTraceStorage as z };
6404
+ export { removeAgentTraceHook as $, runTraceDeny as A, TraceStorageDeniedError as B, disableTraceMachine as C, listReviewTraceSessions as D, traceMachineStatus as E, describeSelection as F, listTraceRepositoryRoots as G, resolveTraceRepositoryTarget as H, isTraceStorageConfigured as I, HOSTED_CAPTURE_SCOPE_DESCRIPTION as J, traceRepositoryStatus as K, loadS3TraceStorage as L, runTraceSessions as M, runTraceStoreDelete as N, loadReviewAgentTrace as O, runTraceStoreInfo as P, installHarnessHooks as Q, resolveTraceStorage as R, configureTraceMachine as S, traceMachineEnabled as T, disableAllTraceRepositories as U, requireTraceConsent as V, enableTraceRepository as W, agentTraceHomeDirectory as X, AGENT_TRACE_HOOK_AGENTS as Y, describeTraceHookOwners as Z, runTraceRepair as _, jsonRequestedInArgv as _t, reviewInstancesDir as a, clearStoreAuth as at, runTraceGitHook as b, registerTraceCommands as c, runStoreLogin as ct, runTraceList as d, StoreApiError as dt, resolveTraceCommand as et, runTracePull as f, StoreClient as ft, runTraceInstallMachine as g, humanStream as gt, runTraceEnable as h, failWithJsonError as ht, reviewInstanceDiscoveryPath as i, DEFAULT_STORE_ORIGIN as it, runTraceOnboard as j, runTraceAllow as k, resolveTraceReadStorage as l, runStoreLogout as lt, runTraceDisable as m, emitJsonEvent as mt, reviewDesktopStateDir as n, traceHomeDir as nt, reviewLegacyDiscoveryPath as o, openUrlInBrowser as ot, runTraceShow as p, withStoreAuthorization as pt, readTraceUserConfig as q, reviewDevInstanceKey as r, traceScope as rt, safeStorageSegment as s, readStoreAuth as st, reviewDefaultInstancePath as t, shellQuote as tt, runTraceBlame as u, runStoreWhoami as ut, runTraceStatus as v, readLegacyCaptureSettings as w, runTraceHook as x, runTraceSync as y, selectTraceStorage as z };
@@ -1,9 +1,9 @@
1
1
  import { c as jsonString, l as jsonValueSchema, o as jsonObject, u as parseJsonText } from "./src-X9phtB2j.js";
2
- import "./src-WCbabvdS.js";
3
- import "./review-home-paths-Y9mYWkCI.js";
4
- import { A as devReviewHome, D as processIsAlive, M as writeFileAtomic, O as withFileLock } from "./s3-config-CAxhO9u_.js";
5
- import { i as readReviewPackageVersion, t as findReviewPackageRoot } from "./package-paths-B9PDoAl6.js";
6
- import "./request-origin-DA4K1qWY.js";
2
+ import "./src-Bgvf1sEN.js";
3
+ import "./review-home-paths-zwevIq4I.js";
4
+ import { A as withFileLock, M as devReviewHome, O as processIsAlive, P as writeFileAtomic } from "./s3-config-DDWWMh5x.js";
5
+ import { i as readReviewPackageVersion, t as findReviewPackageRoot } from "./package-paths-BsuI4WUf.js";
6
+ import "./request-origin-DaRIV3cB.js";
7
7
  import { createHmac, randomUUID } from "node:crypto";
8
8
  import { z } from "zod";
9
9
  import { existsSync, readFileSync, rmSync } from "node:fs";
@@ -1,6 +1,6 @@
1
1
  import { a as jsonNumber, c as jsonString, o as jsonObject, r as jsonArray, u as parseJsonText } from "./src-X9phtB2j.js";
2
2
  import { S as sessionMetaSchema, b as commitShaSchema, y as byCommitSchema } from "./src-CGP5ytbV.js";
3
- import { i as isS3MockMode, l as s3CacheIdentity, s as resolveS3Credentials, u as s3MockRoot } from "./s3-config-CAxhO9u_.js";
3
+ import { i as isS3MockMode, l as s3CacheIdentity, s as resolveS3Credentials, u as s3MockRoot } from "./s3-config-DDWWMh5x.js";
4
4
  import { t as errorMessage } from "./error-message-OtiDonty.js";
5
5
  import { existsSync, mkdirSync, readFileSync, readdirSync, rmSync, statSync, writeFileSync } from "node:fs";
6
6
  import path from "node:path";
@@ -1,2 +1,2 @@
1
- import { a as noTraceConfigurationMessage, c as resolveS3Setup, d as traceEnvPath, f as traceEnvValue, i as isS3MockMode, l as s3CacheIdentity, n as clearTraceEnvCache, o as readTraceEnvFile, p as traceSettingsPath, r as describeS3Setup, s as resolveS3Credentials, t as S3_DEFAULT_REGION, u as s3MockRoot } from "./s3-config-CAxhO9u_.js";
1
+ import { a as noTraceConfigurationMessage, c as resolveS3Setup, d as traceEnvPath, f as traceEnvValue, i as isS3MockMode, l as s3CacheIdentity, n as clearTraceEnvCache, o as readTraceEnvFile, p as traceSettingsPath, r as describeS3Setup, s as resolveS3Credentials, t as S3_DEFAULT_REGION, u as s3MockRoot } from "./s3-config-DDWWMh5x.js";
2
2
  export { S3_DEFAULT_REGION, clearTraceEnvCache, describeS3Setup, isS3MockMode, noTraceConfigurationMessage, readTraceEnvFile, resolveS3Credentials, resolveS3Setup, s3CacheIdentity, s3MockRoot, traceEnvPath, traceEnvValue, traceSettingsPath };
@@ -229,6 +229,10 @@ async function withFileLock(lockPath, options, operation) {
229
229
  });
230
230
  }
231
231
  }
232
+ async function liveLockOwner(lockPath) {
233
+ const owner = await readLockOwner(lockPath);
234
+ return owner && processIsAlive(owner.pid) && !pidReused(owner.pid, owner.started) ? owner.pid : void 0;
235
+ }
232
236
  function processIsAlive(pid) {
233
237
  try {
234
238
  process.kill(pid, 0);
@@ -673,4 +677,4 @@ function normalizeS3Endpoint(endpoint) {
673
677
  }
674
678
  }
675
679
  //#endregion
676
- export { devReviewHome as A, s3ProfileSchema as C, processIsAlive as D, writeTraceConfigFile as E, writeFileAtomic as M, writeFileAtomicAsync as N, withFileLock as O, writePrivateJsonAtomic as P, readTraceConfigFile as S, sameS3Profile as T, emptyTraceConfig as _, noTraceConfigurationMessage as a, hostedCaptureEnabled as b, resolveS3Setup as c, traceEnvPath as d, traceEnvValue as f, currentStore as g, TraceConfigurationError as h, isS3MockMode as i, normalizeStoreOrigin as j, DEV_REVIEW_HOME_ENV as k, s3CacheIdentity as l, DEFAULT_HOSTED_ORIGIN as m, clearTraceEnvCache as n, readTraceEnvFile as o, traceSettingsPath as p, describeS3Setup as r, resolveS3Credentials as s, S3_DEFAULT_REGION as t, s3MockRoot as u, enabledOriginsOf as v, s3Store as w, hostedOrigin as x, hasHostedStore as y };
680
+ export { withFileLock as A, s3ProfileSchema as C, liveLockOwner as D, writeTraceConfigFile as E, writeFileAtomicAsync as F, writePrivateJsonAtomic as I, devReviewHome as M, normalizeStoreOrigin as N, processIsAlive as O, writeFileAtomic as P, readTraceConfigFile as S, sameS3Profile as T, emptyTraceConfig as _, noTraceConfigurationMessage as a, hostedCaptureEnabled as b, resolveS3Setup as c, traceEnvPath as d, traceEnvValue as f, currentStore as g, TraceConfigurationError as h, isS3MockMode as i, DEV_REVIEW_HOME_ENV as j, processStartIdentity as k, s3CacheIdentity as l, DEFAULT_HOSTED_ORIGIN as m, clearTraceEnvCache as n, readTraceEnvFile as o, traceSettingsPath as p, describeS3Setup as r, resolveS3Credentials as s, S3_DEFAULT_REGION as t, s3MockRoot as u, enabledOriginsOf as v, s3Store as w, hostedOrigin as x, hasHostedStore as y };