@dev.fast/whiteboard 0.2.5-preview.20261013.112 → 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.
- package/THIRD_PARTY_NOTICES.md +29 -0
- package/dist/{account-alias-D0hF0sKk.js → account-alias-ChZR3RIU.js} +94 -8
- package/dist/{agent-cli-BBPxwUOd.js → agent-cli-CR2PnA9G.js} +9 -12
- package/dist/agent-cli-CRPPmS3l.js +2 -0
- package/dist/{agent-client-P3J4y8Zn.js → agent-client-DVd-prfp.js} +31 -14
- package/dist/{authoring-tools-D8WXsm_g.js → authoring-tools-BXKEwhfi.js} +5 -5
- package/dist/background-server-Cw7CcfaZ.js +156 -0
- package/dist/background-server-D2Rrr_e2.js +2 -0
- package/dist/build-info.json +1 -1
- package/dist/{cli-C0ziP6hg.js → cli-CSrz_wGk.js} +1 -1
- package/dist/{cli-runner-C1gY-HAI.js → cli-runner-PlGqBeMw.js} +368 -252
- package/dist/cli.js +3 -2
- package/dist/{client-DzcSt6pV.js → client-DhZzPePs.js} +5 -3
- package/dist/{process-error-telemetry-C0swByfp.js → headless-host-CVXTSIlx.js} +3285 -2585
- package/dist/headless-host-Cp37C-v0.js +2 -0
- package/dist/input-error-OLgB_h31.js +11 -0
- package/dist/{local-data-BlERtVm2.js → local-data-yfLp5PiJ.js} +237 -157
- package/dist/{mcp-t7VN9R3k.js → mcp-DBbDwG4y.js} +4 -4
- package/dist/{package-paths-B9PDoAl6.js → package-paths-BsuI4WUf.js} +1 -1
- package/dist/profile-BSOkA6A-.js +2 -0
- package/dist/{profile-BsYDBr7t.js → profile-pAzg7fxC.js} +3 -3
- package/dist/remote-attach-BAt2eZx3.js +137 -0
- package/dist/remote-extensions-2Cxf3hMo.js +2 -0
- package/dist/remote-extensions-BiNx6NQK.js +323 -0
- package/dist/remote-language-server-DQgWUNeV.js +252 -0
- package/dist/remote-uninstall-DLtDG0o7.js +153 -0
- package/dist/{request-origin-DA4K1qWY.js → request-origin-DaRIV3cB.js} +5 -2
- package/dist/review-app-launcher-D7I4DGoH.js +222 -0
- package/dist/{review-home-paths-Y9mYWkCI.js → review-home-paths-zwevIq4I.js} +7 -7
- package/dist/{review-telemetry-CmM6tCbj.js → review-telemetry-BqB4CiTD.js} +5 -5
- package/dist/{s3-SzLpeCG6.js → s3-77-qkEhs.js} +1 -1
- package/dist/{s3-config-BgxkSoOy.js → s3-config-0xuCh-F_.js} +1 -1
- package/dist/{s3-config-CAxhO9u_.js → s3-config-DDWWMh5x.js} +5 -1
- package/dist/server/desktop-host.js +1663 -266
- package/dist/{desktop-discovery-Ba3dD5Cn.js → server-discovery-CifQ3yQS.js} +88 -3
- package/dist/sharing/index.d.ts +88 -32
- package/dist/sharing/index.js +1 -1
- package/dist/{src-DiHP11l3.js → src-BMPuxWv4.js} +6 -11
- package/dist/{src-WCbabvdS.js → src-Bgvf1sEN.js} +128 -8
- package/dist/{stored-document-migration-u4L_1VZa.js → stored-document-migration-CFOvjRwW.js} +5 -18
- package/dist/{tool-failure-CJHBCHMh.js → tool-failure-BXWzQG8T.js} +2 -2
- package/instructions/authoring.md +16 -12
- package/instructions/file-lenses.md +4 -2
- package/package.json +9 -14
- package/src/agent-connect.ts +507 -0
- package/src/agent-selection.ts +4 -0
- package/src/ask/agents.ts +59 -13
- package/src/ask/pi-mcp.ts +7 -3
- package/src/ask/thread-state.ts +9 -4
- package/src/ask/thread.ts +88 -50
- package/src/ask/threads.ts +53 -7
- package/src/cli-install.ts +3 -1
- package/src/cli-runner.ts +472 -81
- package/src/cli.ts +1 -1
- package/src/connect-agents.ts +285 -0
- package/src/connect-prompts.ts +60 -6
- package/src/desktop-discovery.ts +24 -0
- package/src/pinned-download.ts +118 -0
- package/src/remote-attach.ts +130 -0
- package/src/remote-extensions.ts +359 -0
- package/src/remote-language-server.ts +428 -0
- package/src/remote-toolchains.ts +107 -0
- package/src/remote-uninstall.ts +280 -0
- package/src/review-api/README.md +1 -3
- package/src/review-api/agent-cli.ts +4 -4
- package/src/review-api/agent-client.ts +23 -0
- package/src/review-api/ask-history.ts +31 -16
- package/src/review-api/authoring-tools.ts +1 -1
- package/src/review-api/blocks/index.ts +2 -4
- package/src/review-api/document.ts +1 -4
- package/src/review-api/github-provider.ts +154 -0
- package/src/review-api/http.ts +265 -237
- package/src/review-api/local-data.ts +34 -17
- package/src/review-api/onboarding.ts +478 -0
- package/src/review-api/origin.ts +10 -6
- package/src/review-api/provider.ts +44 -0
- package/src/review-api/pull-request.ts +15 -146
- package/src/review-api/recent-folders.ts +173 -0
- package/src/review-api/store.ts +56 -10
- package/src/review-api/workspaces.ts +56 -11
- package/src/review-app-launcher.ts +61 -3
- package/src/review-stack.ts +162 -36
- package/src/server/background-server.ts +250 -0
- package/src/server/desktop-host-shutdown.ts +24 -0
- package/src/server/desktop-host.ts +39 -24
- package/src/server/desktop-server.ts +68 -118
- package/src/server/global-verb-relay.ts +16 -0
- package/src/server/headless-host.ts +128 -3
- package/src/server/hono-http.ts +6 -1
- package/src/server/legacy-tutorial-cleanup.ts +38 -0
- package/src/server/review-gateway-hosts.ts +379 -0
- package/src/server/review-gateway-list.ts +109 -0
- package/src/server/review-gateway-memory.ts +130 -0
- package/src/server/review-gateway-pushes.ts +142 -0
- package/src/server/review-gateway-streams.ts +415 -0
- package/src/server/review-gateway-transport.ts +204 -0
- package/src/server/review-gateway.ts +433 -0
- package/src/server/review-server-core.ts +36 -6
- package/src/server-discovery.ts +11 -0
- package/src/sharing/import.ts +1 -0
- package/src/ui-telemetry-events.ts +5 -1
- package/src/vsix.ts +122 -0
- package/dist/agent-cli-YSuhE5X8.js +0 -2
- package/dist/headless-host-BHD_Vg5F.js +0 -95
- package/dist/profile-j97aRTS7.js +0 -2
- package/dist/server-discovery-ZVi8Ay8J.js +0 -66
- package/src/fixtures/blocks/tutorial.json +0 -14
- package/src/review-api/blocks/tutorial.ts +0 -23
- package/src/server/tutorial-service.ts +0 -217
- package/tutorial/document.json +0 -216
- package/tutorial/lenses.json +0 -10
- package/tutorial/runtime-manifest.json +0 -11
- package/tutorial/sample-service/package.json +0 -9
- package/tutorial/sample-service/src/api/checkout-api.ts +0 -17
- package/tutorial/sample-service/src/app.ts +0 -26
- package/tutorial/sample-service/src/database/drizzle-orm.d.ts +0 -16
- package/tutorial/sample-service/src/database/schema.ts +0 -9
- package/tutorial/sample-service/src/fulfillment/fulfillment-queue.ts +0 -15
- package/tutorial/sample-service/src/fulfillment/fulfillment-worker.ts +0 -24
- package/tutorial/sample-service/src/inventory/forklift-mood.ts +0 -4
- package/tutorial/sample-service/src/inventory/inventory-service.ts +0 -28
- package/tutorial/sample-service/src/inventory/warehouse-lore.ts +0 -11
- package/tutorial/sample-service/src/orders/order-service.ts +0 -35
- package/tutorial/sample-service/src/orders/order.ts +0 -43
- package/tutorial/sample-service/src/orders/orders-repository.ts +0 -24
- package/tutorial/sample-service/src/payments/fraud-vibes.ts +0 -4
- package/tutorial/sample-service/src/payments/payment-gateway.ts +0 -29
- package/tutorial/sample-service/src/payments/raccoon-ledger.ts +0 -9
- package/tutorial/sample-service/src/shipping/shipping-gateway.ts +0 -15
- package/tutorial/sample-service/tsconfig.json +0 -12
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
import "./review-home-paths-zwevIq4I.js";
|
|
2
|
+
import { A as withFileLock, k as processStartIdentity } from "./s3-config-DDWWMh5x.js";
|
|
3
|
+
import { t as findReviewPackageRoot } from "./package-paths-BsuI4WUf.js";
|
|
4
|
+
import { n as cliSpawn, s as spawnDetached } from "./background-server-Cw7CcfaZ.js";
|
|
5
|
+
import { r as remoteServerPaths, t as ensureRemoteExtensions } from "./remote-extensions-BiNx6NQK.js";
|
|
6
|
+
import { randomBytes } from "node:crypto";
|
|
7
|
+
import { z } from "zod";
|
|
8
|
+
import { mkdir, readFile, rename, stat, writeFile } from "node:fs/promises";
|
|
9
|
+
import path from "node:path";
|
|
10
|
+
import { setTimeout as setTimeout$1 } from "node:timers/promises";
|
|
11
|
+
//#region src/remote-language-server.ts
|
|
12
|
+
const START_TIMEOUT_MS = 15e3;
|
|
13
|
+
const VERSION_TIMEOUT_MS = 3e3;
|
|
14
|
+
const INSTALL_TIMEOUT_MS = 35e3;
|
|
15
|
+
const SHUTDOWN_WITHOUT_DELAY_ENV = "DEV_FAST_REVIEW_REMOTE_SHUTDOWN_WITHOUT_DELAY";
|
|
16
|
+
const GRACE_ENV = "DEV_FAST_REVIEW_REMOTE_RECONNECTION_GRACE_SECONDS";
|
|
17
|
+
const PENDING = {
|
|
18
|
+
languageServer: null,
|
|
19
|
+
languageServerDetail: "Installing the language extensions on this host; they will be available on the next connection.",
|
|
20
|
+
languageServerPending: true
|
|
21
|
+
};
|
|
22
|
+
function idleArgs(env) {
|
|
23
|
+
const grace = env[GRACE_ENV] ?? "";
|
|
24
|
+
return [
|
|
25
|
+
"--enable-remote-auto-shutdown",
|
|
26
|
+
"--reconnection-grace-time",
|
|
27
|
+
/^[1-9]\d{0,3}$/.test(grace) ? grace : "600"
|
|
28
|
+
];
|
|
29
|
+
}
|
|
30
|
+
const LISTENING = /Extension host agent listening on (\d+)/;
|
|
31
|
+
const LOCK = {
|
|
32
|
+
retryMs: 100,
|
|
33
|
+
staleMs: 6e4,
|
|
34
|
+
unownedGraceMs: 5e3,
|
|
35
|
+
identifyOwner: true
|
|
36
|
+
};
|
|
37
|
+
const installingSchema = z.object({
|
|
38
|
+
pid: z.number(),
|
|
39
|
+
started: z.string().nullable()
|
|
40
|
+
});
|
|
41
|
+
const runningSchema = z.object({
|
|
42
|
+
pid: z.number(),
|
|
43
|
+
started: z.string().nullable(),
|
|
44
|
+
port: z.number()
|
|
45
|
+
});
|
|
46
|
+
function remoteLanguageServerFiles(env) {
|
|
47
|
+
const { serverDataDir } = remoteServerPaths(env);
|
|
48
|
+
return {
|
|
49
|
+
serverDataDir,
|
|
50
|
+
tokenFile: path.join(serverDataDir, "connection-token"),
|
|
51
|
+
runningFile: path.join(serverDataDir, "server.json"),
|
|
52
|
+
logFile: path.join(serverDataDir, "server.log"),
|
|
53
|
+
lock: path.join(serverDataDir, "start.lock"),
|
|
54
|
+
installingFile: path.join(serverDataDir, "install.json"),
|
|
55
|
+
installLog: path.join(serverDataDir, "install.log"),
|
|
56
|
+
installLock: path.join(serverDataDir, "install.lock")
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
async function ensureRemoteLanguageServer(input) {
|
|
60
|
+
const root = path.join(input.packageRoot ?? findReviewPackageRoot(import.meta.url), "vscode-server");
|
|
61
|
+
const files = remoteLanguageServerFiles(input.env);
|
|
62
|
+
const capped = new AbortController();
|
|
63
|
+
let languageGroups = (input.groups ?? []).map((group) => ({
|
|
64
|
+
group,
|
|
65
|
+
installed: false
|
|
66
|
+
}));
|
|
67
|
+
const cap = setTimeout(() => capped.abort(), input.installTimeoutMs ?? INSTALL_TIMEOUT_MS);
|
|
68
|
+
try {
|
|
69
|
+
const commit = await readCommit(root);
|
|
70
|
+
const ensure = input.ensure ?? ensureRemoteExtensions;
|
|
71
|
+
await mkdir(files.serverDataDir, {
|
|
72
|
+
recursive: true,
|
|
73
|
+
mode: 448
|
|
74
|
+
});
|
|
75
|
+
if (await installing(files)) return {
|
|
76
|
+
...PENDING,
|
|
77
|
+
languageGroups
|
|
78
|
+
};
|
|
79
|
+
const { failed, groups } = await ensure({
|
|
80
|
+
env: input.env,
|
|
81
|
+
groups: input.groups,
|
|
82
|
+
signal: AbortSignal.any([capped.signal, ...input.signal ? [input.signal] : []])
|
|
83
|
+
});
|
|
84
|
+
if (failed.length > 0 && capped.signal.aborted && !input.signal?.aborted && await installDetached(files, input)) return {
|
|
85
|
+
...PENDING,
|
|
86
|
+
languageGroups
|
|
87
|
+
};
|
|
88
|
+
if (groups) languageGroups = groups;
|
|
89
|
+
if (failed.length > 0) throw new Error(`Could not install the language extensions: ${failed.map(({ id, error }) => `${id}: ${error}`).join("; ")}`);
|
|
90
|
+
const outcome = await withFileLock(files.lock, {
|
|
91
|
+
...LOCK,
|
|
92
|
+
timeoutMs: (input.timeoutMs ?? START_TIMEOUT_MS) * 2
|
|
93
|
+
}, async () => {
|
|
94
|
+
return {
|
|
95
|
+
port: (await healthy(files.runningFile, commit))?.port ?? await startServer(root, files, input.env, input.timeoutMs),
|
|
96
|
+
connectionToken: (await readFile(files.tokenFile, "utf8")).trim(),
|
|
97
|
+
commit
|
|
98
|
+
};
|
|
99
|
+
});
|
|
100
|
+
if (!outcome.acquired) throw new Error("Another start of the VS Code server did not finish.");
|
|
101
|
+
return {
|
|
102
|
+
languageServer: outcome.result,
|
|
103
|
+
languageGroups
|
|
104
|
+
};
|
|
105
|
+
} catch (error) {
|
|
106
|
+
return {
|
|
107
|
+
languageServer: null,
|
|
108
|
+
languageServerDetail: error instanceof Error ? error.message : String(error),
|
|
109
|
+
languageGroups
|
|
110
|
+
};
|
|
111
|
+
} finally {
|
|
112
|
+
clearTimeout(cap);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
async function readInstalling(files) {
|
|
116
|
+
return readFile(files.installingFile, "utf8").then((text) => installingSchema.safeParse(JSON.parse(text)).data).catch(() => void 0);
|
|
117
|
+
}
|
|
118
|
+
async function installing(files) {
|
|
119
|
+
const running = await readInstalling(files);
|
|
120
|
+
return running !== void 0 && ours(running);
|
|
121
|
+
}
|
|
122
|
+
async function remoteLanguageServerGroups(env) {
|
|
123
|
+
const files = remoteLanguageServerFiles(env);
|
|
124
|
+
return (await Promise.all([readRunning(files.runningFile), readInstalling(files)])).flatMap((entry) => entry && ours(entry) ? [entry.pid] : []);
|
|
125
|
+
}
|
|
126
|
+
async function stopProcessGroup(leader) {
|
|
127
|
+
for (const signal of ["SIGTERM", "SIGKILL"]) {
|
|
128
|
+
try {
|
|
129
|
+
process.kill(-leader, signal);
|
|
130
|
+
} catch {
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
for (let i = 0; i < 50; i++) {
|
|
134
|
+
if (!groupAlive(leader)) return;
|
|
135
|
+
await setTimeout$1(100);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
function groupAlive(leader) {
|
|
140
|
+
try {
|
|
141
|
+
process.kill(-leader, 0);
|
|
142
|
+
return true;
|
|
143
|
+
} catch (error) {
|
|
144
|
+
return error instanceof Error && "code" in error && error.code === "EPERM";
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
async function installDetached(files, input) {
|
|
148
|
+
return (await withFileLock(files.installLock, {
|
|
149
|
+
...LOCK,
|
|
150
|
+
timeoutMs: 1e4
|
|
151
|
+
}, async () => {
|
|
152
|
+
if (await installing(files)) return;
|
|
153
|
+
const { command, args, env } = cliSpawn(input.cli, input.env);
|
|
154
|
+
const child = await spawnDetached({
|
|
155
|
+
command,
|
|
156
|
+
args: [
|
|
157
|
+
...args,
|
|
158
|
+
"remote",
|
|
159
|
+
"extensions",
|
|
160
|
+
"ensure",
|
|
161
|
+
"--json",
|
|
162
|
+
...input.groups?.length ? ["--groups", input.groups.join(",")] : []
|
|
163
|
+
],
|
|
164
|
+
cwd: files.serverDataDir,
|
|
165
|
+
env,
|
|
166
|
+
log: files.installLog
|
|
167
|
+
});
|
|
168
|
+
if (child.pid !== void 0) await writeFile(files.installingFile, `${JSON.stringify({
|
|
169
|
+
pid: child.pid,
|
|
170
|
+
started: processStartIdentity(child.pid)
|
|
171
|
+
})}\n`, { mode: 384 });
|
|
172
|
+
})).acquired;
|
|
173
|
+
}
|
|
174
|
+
async function readCommit(root) {
|
|
175
|
+
const file = path.join(root, "product.json");
|
|
176
|
+
const text = await readFile(file, "utf8").catch(() => {
|
|
177
|
+
throw new Error(`This Whiteboard install has no VS Code server (${file} is missing).`);
|
|
178
|
+
});
|
|
179
|
+
const { commit } = z.object({ commit: z.string().regex(/^[0-9a-f]{40}$/) }).parse(JSON.parse(text));
|
|
180
|
+
return commit;
|
|
181
|
+
}
|
|
182
|
+
async function readRunning(file) {
|
|
183
|
+
return readFile(file, "utf8").then((text) => runningSchema.safeParse(JSON.parse(text)).data).catch(() => void 0);
|
|
184
|
+
}
|
|
185
|
+
const ours = (running) => running.started !== null && processStartIdentity(running.pid) === running.started;
|
|
186
|
+
async function healthy(file, commit) {
|
|
187
|
+
const running = await readRunning(file);
|
|
188
|
+
if (!running) return void 0;
|
|
189
|
+
if (ours(running) && await version(running.port) === commit) return running;
|
|
190
|
+
await stop(running);
|
|
191
|
+
}
|
|
192
|
+
async function version(port) {
|
|
193
|
+
return fetch(`http://127.0.0.1:${port}/version`, { signal: AbortSignal.timeout(VERSION_TIMEOUT_MS) }).then((response) => response.ok ? response.text() : void 0).catch(() => void 0);
|
|
194
|
+
}
|
|
195
|
+
async function stop(running) {
|
|
196
|
+
if (!ours(running)) return;
|
|
197
|
+
try {
|
|
198
|
+
process.kill(running.pid, "SIGTERM");
|
|
199
|
+
} catch {
|
|
200
|
+
return;
|
|
201
|
+
}
|
|
202
|
+
for (let i = 0; i < 50 && processStartIdentity(running.pid) !== null; i++) await setTimeout$1(100);
|
|
203
|
+
}
|
|
204
|
+
async function startServer(root, files, env, timeoutMs = START_TIMEOUT_MS) {
|
|
205
|
+
const { extensionsDir, serverDataDir } = remoteServerPaths(env);
|
|
206
|
+
const temporary = `${files.tokenFile}.${process.pid}.tmp`;
|
|
207
|
+
await writeFile(temporary, randomBytes(32).toString("hex"), { mode: 384 });
|
|
208
|
+
await rename(temporary, files.tokenFile);
|
|
209
|
+
const logStart = (await stat(files.logFile).catch(() => null))?.size ?? 0;
|
|
210
|
+
const child = await spawnDetached({
|
|
211
|
+
command: process.execPath,
|
|
212
|
+
args: [
|
|
213
|
+
path.join(root, "out", "server-main.js"),
|
|
214
|
+
"--host",
|
|
215
|
+
"127.0.0.1",
|
|
216
|
+
"--port",
|
|
217
|
+
"0",
|
|
218
|
+
"--connection-token-file",
|
|
219
|
+
files.tokenFile,
|
|
220
|
+
"--server-data-dir",
|
|
221
|
+
serverDataDir,
|
|
222
|
+
"--extensions-dir",
|
|
223
|
+
extensionsDir,
|
|
224
|
+
"--accept-server-license-terms",
|
|
225
|
+
...idleArgs(env),
|
|
226
|
+
...env[SHUTDOWN_WITHOUT_DELAY_ENV] === "1" ? ["--remote-auto-shutdown-without-delay"] : []
|
|
227
|
+
],
|
|
228
|
+
cwd: serverDataDir,
|
|
229
|
+
env,
|
|
230
|
+
log: files.logFile
|
|
231
|
+
});
|
|
232
|
+
const deadline = Date.now() + timeoutMs;
|
|
233
|
+
while (Date.now() < deadline && !child.exited) {
|
|
234
|
+
const output = (await readFile(files.logFile)).subarray(logStart).toString("utf8");
|
|
235
|
+
const port = Number(LISTENING.exec(output)?.[1]);
|
|
236
|
+
if (port && child.pid !== void 0) {
|
|
237
|
+
const running = {
|
|
238
|
+
pid: child.pid,
|
|
239
|
+
started: processStartIdentity(child.pid),
|
|
240
|
+
port
|
|
241
|
+
};
|
|
242
|
+
await writeFile(files.runningFile, `${JSON.stringify(running)}\n`, { mode: 384 });
|
|
243
|
+
return port;
|
|
244
|
+
}
|
|
245
|
+
await setTimeout$1(100);
|
|
246
|
+
}
|
|
247
|
+
if (!child.exited && child.pid !== void 0) process.kill(child.pid, "SIGTERM");
|
|
248
|
+
const tail = (await readFile(files.logFile)).subarray(logStart).toString("utf8").trimEnd().split("\n").slice(-10).join("\n");
|
|
249
|
+
throw new Error(`The VS Code server did not start${child.error ? `: ${child.error.message}` : child.exited ? "" : ` within ${timeoutMs / 1e3} s`}. The end of ${files.logFile}:\n${tail}`);
|
|
250
|
+
}
|
|
251
|
+
//#endregion
|
|
252
|
+
export { remoteLanguageServerGroups as n, stopProcessGroup as r, ensureRemoteLanguageServer as t };
|
|
@@ -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 {
|
|
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: {
|
|
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 };
|