mindwire 0.1.34 → 0.1.38
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/dist/binary-cache-CG52TXCZ.js +3 -0
- package/dist/{binary-cache-VM7VHCEU.js.map → binary-cache-CG52TXCZ.js.map} +1 -1
- package/dist/{chunk-3QFDSYKC.js → chunk-CKTVMUS4.js} +547 -177
- package/dist/chunk-CKTVMUS4.js.map +1 -0
- package/dist/{chunk-3FR7BFAW.js → chunk-PFYKJ6LX.js} +56 -4
- package/dist/chunk-PFYKJ6LX.js.map +1 -0
- package/dist/chunk-QODJKRFK.js +20 -0
- package/dist/chunk-QODJKRFK.js.map +1 -0
- package/dist/cli.js +763 -32
- package/dist/cli.js.map +1 -1
- package/dist/client.d.ts +6 -8
- package/dist/computer/connection-change.d.ts +13 -0
- package/dist/computer/connection-flow.d.ts +3 -3
- package/dist/computer/directory-http.d.ts +8 -0
- package/dist/computer/discovery.d.ts +13 -0
- package/dist/computer/lifecycle.d.ts +2 -1
- package/dist/computer/managed-relay.d.ts +27 -0
- package/dist/computer/oblien-auth.d.ts +25 -0
- package/dist/computer/provider-errors.d.ts +9 -0
- package/dist/computer/provider-info.d.ts +7 -0
- package/dist/computer/provider-setup.d.ts +20 -0
- package/dist/computer/relay-health.d.ts +2 -2
- package/dist/computer/relay.d.ts +11 -3
- package/dist/computer/setup-prompt.d.ts +9 -0
- package/dist/computer.d.ts +27 -1
- package/dist/connection-change-5F4WNCYV.js +41 -0
- package/dist/connection-change-5F4WNCYV.js.map +1 -0
- package/dist/{controller-AGT6MCJG.js → controller-3SZI7TBW.js} +199 -279
- package/dist/controller-3SZI7TBW.js.map +1 -0
- package/dist/{controller-handoff-3LAYBJUL.js → controller-handoff-KV7WRDAI.js} +4 -4
- package/dist/{controller-handoff-3LAYBJUL.js.map → controller-handoff-KV7WRDAI.js.map} +1 -1
- package/dist/conversations.d.ts +57 -0
- package/dist/index.cjs +37 -8
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.js +37 -9
- package/dist/index.js.map +1 -1
- package/dist/types.d.ts +16 -0
- package/dist/workspace.d.ts +2 -0
- package/package.json +5 -5
- package/dist/binary-cache-VM7VHCEU.js +0 -4
- package/dist/chunk-3FR7BFAW.js.map +0 -1
- package/dist/chunk-3QFDSYKC.js.map +0 -1
- package/dist/controller-AGT6MCJG.js.map +0 -1
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
import { MindwireError, TimeoutError, ApiError, RunFailedError } from './chunk-
|
|
1
|
+
import { MindwireError, binaryCacheDirectory, cachedExecutable, verifyReleaseBytes, cacheExecutable, executableChecksum, TimeoutError, ApiError, RunFailedError } from './chunk-PFYKJ6LX.js';
|
|
2
2
|
import { execFile, spawn } from 'child_process';
|
|
3
|
-
import * as
|
|
4
|
-
import { readFile } from 'fs/promises';
|
|
3
|
+
import * as fs2 from 'fs/promises';
|
|
4
|
+
import { readFile, open, rm, mkdtemp, writeFile } from 'fs/promises';
|
|
5
5
|
import { promisify } from 'util';
|
|
6
6
|
import { setTimeout as setTimeout$1 } from 'timers/promises';
|
|
7
7
|
import { randomUUID } from 'crypto';
|
|
8
|
-
import { homedir, networkInterfaces } from 'os';
|
|
9
|
-
import * as
|
|
8
|
+
import { tmpdir, homedir, networkInterfaces } from 'os';
|
|
9
|
+
import * as path4 from 'path';
|
|
10
10
|
|
|
11
11
|
// src/computer.ts
|
|
12
12
|
var ComputerApi = class {
|
|
@@ -17,8 +17,17 @@ var ComputerApi = class {
|
|
|
17
17
|
info() {
|
|
18
18
|
return this.client.http.request("GET", "/computer");
|
|
19
19
|
}
|
|
20
|
-
|
|
21
|
-
return this.client.http.request("
|
|
20
|
+
discovery() {
|
|
21
|
+
return this.client.http.request("GET", "/computer/discovery");
|
|
22
|
+
}
|
|
23
|
+
configureDiscovery(enabled, url) {
|
|
24
|
+
return this.client.http.request("PUT", "/computer/discovery", { body: { enabled, url } });
|
|
25
|
+
}
|
|
26
|
+
directoryEnrollment(url, challenge) {
|
|
27
|
+
return this.client.http.request("POST", "/computer/discovery/enrollment", { body: { url, challenge } });
|
|
28
|
+
}
|
|
29
|
+
setRoutes(routes, connection) {
|
|
30
|
+
return this.client.http.request("PUT", "/computer/routes", { body: { routes, connection } });
|
|
22
31
|
}
|
|
23
32
|
updateStatus() {
|
|
24
33
|
return this.client.http.request("GET", "/computer/update");
|
|
@@ -79,7 +88,7 @@ function computerPairingURI(invitation) {
|
|
|
79
88
|
}
|
|
80
89
|
|
|
81
90
|
// src/version.ts
|
|
82
|
-
var SDK_VERSION = "0.1.
|
|
91
|
+
var SDK_VERSION = "0.1.38" ;
|
|
83
92
|
function versionAtLeast(actual, desired) {
|
|
84
93
|
if (actual === desired) return true;
|
|
85
94
|
if (!actual || !/^\d+\.\d+\.\d+$/.test(actual) || !/^\d+\.\d+\.\d+$/.test(desired)) return false;
|
|
@@ -117,12 +126,12 @@ async function ensureDaemonBinary(opts = {}) {
|
|
|
117
126
|
if (!/^[0-9]+\.[0-9]+\.[0-9]+(?:-[0-9A-Za-z.-]+)?$/.test(version)) {
|
|
118
127
|
throw new MindwireError(`mindwire: cannot download daemon for non-release SDK version ${version}`);
|
|
119
128
|
}
|
|
120
|
-
const
|
|
121
|
-
const { binaryCacheDirectory, cachedExecutable, cacheExecutable } = await import('./binary-cache-
|
|
122
|
-
const dir =
|
|
129
|
+
const path5 = await import('path');
|
|
130
|
+
const { binaryCacheDirectory: binaryCacheDirectory2, cachedExecutable: cachedExecutable2, cacheExecutable: cacheExecutable2 } = await import('./binary-cache-CG52TXCZ.js');
|
|
131
|
+
const dir = path5.join(opts.cacheDir ?? await binaryCacheDirectory2(daemonPlatform), version, `${daemonPlatform}-${daemonArch}`);
|
|
123
132
|
const asset = assetName(version, daemonPlatform, daemonArch);
|
|
124
|
-
const bin =
|
|
125
|
-
const cached = await
|
|
133
|
+
const bin = path5.join(dir, asset);
|
|
134
|
+
const cached = await cachedExecutable2(bin);
|
|
126
135
|
if (cached) return cached;
|
|
127
136
|
const base = (opts.releaseBaseUrl ?? proc.process?.env?.MINDWIRE_RELEASE_BASE_URL ?? "https://github.com/oblien/mindwire/releases/download").replace(/\/$/, "");
|
|
128
137
|
const release = `${base}/v${version}`;
|
|
@@ -145,7 +154,7 @@ async function ensureDaemonBinary(opts = {}) {
|
|
|
145
154
|
const expected = checksumFor(await checksums.text(), asset);
|
|
146
155
|
if (!expected) throw new MindwireError(`mindwire: release v${version} has no checksum for ${asset}`);
|
|
147
156
|
const bytes = new Uint8Array(await binary.arrayBuffer());
|
|
148
|
-
return
|
|
157
|
+
return cacheExecutable2(bin, bytes, expected);
|
|
149
158
|
}
|
|
150
159
|
var execute = promisify(execFile);
|
|
151
160
|
function processAlive(pid) {
|
|
@@ -215,7 +224,7 @@ async function ownChild(child) {
|
|
|
215
224
|
const alive = () => child.exitCode === null && child.signalCode === null;
|
|
216
225
|
return { pid, identity, alive, async close() {
|
|
217
226
|
if (!alive()) return;
|
|
218
|
-
const exited = new Promise((
|
|
227
|
+
const exited = new Promise((resolve3) => child.once("exit", () => resolve3()));
|
|
219
228
|
child.kill("SIGTERM");
|
|
220
229
|
const timer = setTimeout(() => {
|
|
221
230
|
if (alive()) child.kill("SIGKILL");
|
|
@@ -240,6 +249,413 @@ async function adoptProcess(identity) {
|
|
|
240
249
|
if (await matches()) process.kill(identity.pid, "SIGKILL");
|
|
241
250
|
} };
|
|
242
251
|
}
|
|
252
|
+
var RelayOutput = class _RelayOutput {
|
|
253
|
+
constructor(file, handle) {
|
|
254
|
+
this.file = file;
|
|
255
|
+
this.handle = handle;
|
|
256
|
+
}
|
|
257
|
+
file;
|
|
258
|
+
handle;
|
|
259
|
+
position = 0;
|
|
260
|
+
timer;
|
|
261
|
+
pending = Promise.resolve();
|
|
262
|
+
closing;
|
|
263
|
+
static async create(directory = tmpdir()) {
|
|
264
|
+
const file = path4.join(directory, `.relay-output-${randomUUID()}.log`);
|
|
265
|
+
return new _RelayOutput(file, await open(file, "ax+", 384));
|
|
266
|
+
}
|
|
267
|
+
static async adopt(directory, file) {
|
|
268
|
+
if (path4.dirname(file) !== path4.resolve(directory) || !/^\.relay-output-[a-f0-9-]{36}\.log$/.test(path4.basename(file))) return;
|
|
269
|
+
try {
|
|
270
|
+
const output = new _RelayOutput(file, await open(file, "r+"));
|
|
271
|
+
output.discard();
|
|
272
|
+
return output;
|
|
273
|
+
} catch (error) {
|
|
274
|
+
if (error.code !== "ENOENT") throw error;
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
get fd() {
|
|
278
|
+
return this.handle.fd;
|
|
279
|
+
}
|
|
280
|
+
async read() {
|
|
281
|
+
const { size } = await this.handle.stat();
|
|
282
|
+
if (size > 2 * 1024 * 1024) throw new Error("The tunnel helper produced too much startup output. Check its configuration.");
|
|
283
|
+
const data = Buffer.alloc(Math.min(65536, Math.max(0, size - this.position)));
|
|
284
|
+
const { bytesRead } = await this.handle.read(data, 0, data.length, this.position);
|
|
285
|
+
this.position += bytesRead;
|
|
286
|
+
return data.subarray(0, bytesRead);
|
|
287
|
+
}
|
|
288
|
+
discard() {
|
|
289
|
+
if (this.timer || this.closing) return;
|
|
290
|
+
const drain = () => {
|
|
291
|
+
this.pending = this.pending.then(() => this.handle.truncate(0)).catch(() => {
|
|
292
|
+
});
|
|
293
|
+
};
|
|
294
|
+
drain();
|
|
295
|
+
this.timer = setInterval(drain, 2e3);
|
|
296
|
+
this.timer.unref();
|
|
297
|
+
}
|
|
298
|
+
close() {
|
|
299
|
+
return this.closing ??= (async () => {
|
|
300
|
+
clearInterval(this.timer);
|
|
301
|
+
await this.pending;
|
|
302
|
+
await this.handle.close();
|
|
303
|
+
await rm(this.file, { force: true });
|
|
304
|
+
})();
|
|
305
|
+
}
|
|
306
|
+
};
|
|
307
|
+
|
|
308
|
+
// src/computer/provider-errors.ts
|
|
309
|
+
var ProviderConnectionError = class extends Error {
|
|
310
|
+
constructor(code, message) {
|
|
311
|
+
super(message);
|
|
312
|
+
this.code = code;
|
|
313
|
+
this.name = "ProviderConnectionError";
|
|
314
|
+
}
|
|
315
|
+
code;
|
|
316
|
+
};
|
|
317
|
+
function providerNeedsAction(code) {
|
|
318
|
+
return ["provider_sign_in", "provider_limit", "provider_missing", "provider_certificate", "provider_configuration"].includes(code ?? "");
|
|
319
|
+
}
|
|
320
|
+
function hasCertificateError(error) {
|
|
321
|
+
const seen = /* @__PURE__ */ new Set();
|
|
322
|
+
for (let current = error; current && typeof current === "object" && seen.size < 8 && !seen.has(current); ) {
|
|
323
|
+
seen.add(current);
|
|
324
|
+
const value = current;
|
|
325
|
+
if ([
|
|
326
|
+
"CERT_HAS_EXPIRED",
|
|
327
|
+
"CERT_NOT_YET_VALID",
|
|
328
|
+
"ERR_TLS_CERT_ALTNAME_INVALID",
|
|
329
|
+
"DEPTH_ZERO_SELF_SIGNED_CERT",
|
|
330
|
+
"SELF_SIGNED_CERT_IN_CHAIN",
|
|
331
|
+
"UNABLE_TO_VERIFY_LEAF_SIGNATURE",
|
|
332
|
+
"UNABLE_TO_GET_ISSUER_CERT_LOCALLY",
|
|
333
|
+
"CERT_REVOKED"
|
|
334
|
+
].includes(value.code ?? "") || /certificate (?:has expired|expired|verify failed)|self[- ]signed certificate|CERT_HAS_EXPIRED/i.test(value.message ?? "")) return true;
|
|
335
|
+
current = value.cause;
|
|
336
|
+
}
|
|
337
|
+
return false;
|
|
338
|
+
}
|
|
339
|
+
function providerFailure(error, provider) {
|
|
340
|
+
if (error instanceof ProviderConnectionError) return error;
|
|
341
|
+
const value = error;
|
|
342
|
+
const setup = `mindwire connection ${provider.toLowerCase()}`;
|
|
343
|
+
if (hasCertificateError(error)) return new ProviderConnectionError(
|
|
344
|
+
"provider_certificate",
|
|
345
|
+
`${provider}'s TLS certificate could not be verified. The tunnel provider must fix its certificate; also check this computer's date and time. Your saved phone keys are unchanged.`
|
|
346
|
+
);
|
|
347
|
+
if (value?.code === "provider_sign_in" || [401, 403].includes(value?.status ?? 0) || /unexpected server response: (?:401|403)\b/i.test(value?.message ?? "")) {
|
|
348
|
+
return new ProviderConnectionError("provider_sign_in", `Sign in to ${provider} again with ${setup}. Your saved phone keys are unchanged.`);
|
|
349
|
+
}
|
|
350
|
+
if (value?.status === 402) return new ProviderConnectionError("provider_limit", `${provider}'s tunnel allowance was reached. Check your plan in its dashboard or choose another provider.`);
|
|
351
|
+
if (value?.status === 404) return new ProviderConnectionError("provider_missing", `This computer's ${provider} tunnel was removed. Run ${setup} to configure it again.`);
|
|
352
|
+
if (value?.status === 400 || value?.code === "provider_configuration") return new ProviderConnectionError("provider_configuration", `${provider}'s connection settings are incomplete or invalid. Run ${setup} to configure it again.`);
|
|
353
|
+
return new ProviderConnectionError("provider_connection", `${provider} couldn't connect. Check internet access and the provider account, then run ${setup}.`);
|
|
354
|
+
}
|
|
355
|
+
var CLOUDFLARED_VERSION = "2026.9.3";
|
|
356
|
+
var execute2 = promisify(execFile);
|
|
357
|
+
async function ensureCloudflareBinary(options = {}) {
|
|
358
|
+
const platform = options.platform ?? process.platform, arch = options.arch ?? process.arch;
|
|
359
|
+
const cpu = arch === "x64" ? "amd64" : arch === "arm64" ? "arm64" : "";
|
|
360
|
+
if (!cpu || !["darwin", "linux", "win32"].includes(platform) || platform === "win32" && cpu !== "amd64") {
|
|
361
|
+
throw new Error("Install cloudflared for this computer, or select another connection provider.");
|
|
362
|
+
}
|
|
363
|
+
const asset = `cloudflared-${platform === "win32" ? "windows" : platform}-${cpu}${platform === "darwin" ? ".tgz" : platform === "win32" ? ".exe" : ""}`;
|
|
364
|
+
const bin = path4.join(
|
|
365
|
+
options.cacheDir ?? await binaryCacheDirectory(),
|
|
366
|
+
"cloudflared",
|
|
367
|
+
CLOUDFLARED_VERSION,
|
|
368
|
+
`${platform}-${arch}`,
|
|
369
|
+
platform === "win32" ? "cloudflared.exe" : "cloudflared"
|
|
370
|
+
);
|
|
371
|
+
const cached = await cachedExecutable(bin);
|
|
372
|
+
if (cached) return cached;
|
|
373
|
+
await options.onProgress?.("Downloading the secure connection helper\u2026");
|
|
374
|
+
const request = options.fetch ?? fetch;
|
|
375
|
+
const release = await request(
|
|
376
|
+
`https://api.github.com/repos/cloudflare/cloudflared/releases/tags/${CLOUDFLARED_VERSION}`,
|
|
377
|
+
{ headers: { Accept: "application/vnd.github+json" }, signal: AbortSignal.timeout(2e4) }
|
|
378
|
+
);
|
|
379
|
+
if (!release.ok) throw new Error("Couldn't download the connection helper. Retry, install cloudflared yourself, or choose another provider.");
|
|
380
|
+
const metadata = await release.json();
|
|
381
|
+
const item = metadata.assets?.find((value) => value.name === asset);
|
|
382
|
+
const checksum = item?.digest?.match(/^sha256:([a-f0-9]{64})$/i)?.[1];
|
|
383
|
+
const expectedURL = `https://github.com/cloudflare/cloudflared/releases/download/${CLOUDFLARED_VERSION}/${asset}`;
|
|
384
|
+
if (!checksum || item?.browser_download_url !== expectedURL) throw new Error("The connection helper has no verified release asset for this computer.");
|
|
385
|
+
const downloaded = await request(expectedURL, { signal: AbortSignal.timeout(12e4) });
|
|
386
|
+
if (!downloaded.ok) throw new Error("Couldn't download the connection helper. Check your internet connection and retry.");
|
|
387
|
+
const archive = new Uint8Array(await downloaded.arrayBuffer());
|
|
388
|
+
await verifyReleaseBytes(archive, checksum, asset);
|
|
389
|
+
let executable = archive;
|
|
390
|
+
if (platform === "darwin") {
|
|
391
|
+
const temporary = await mkdtemp(path4.join(tmpdir(), "mindwire-cloudflared-"));
|
|
392
|
+
try {
|
|
393
|
+
const archivePath = path4.join(temporary, asset);
|
|
394
|
+
await writeFile(archivePath, archive, { mode: 384 });
|
|
395
|
+
const result = await execute2(
|
|
396
|
+
"/usr/bin/tar",
|
|
397
|
+
["-xzOf", archivePath, "cloudflared"],
|
|
398
|
+
{ encoding: "buffer", maxBuffer: 128 * 1024 * 1024, timeout: 3e4 }
|
|
399
|
+
);
|
|
400
|
+
executable = new Uint8Array(result.stdout);
|
|
401
|
+
} finally {
|
|
402
|
+
await rm(temporary, { recursive: true, force: true });
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
return cacheExecutable(bin, executable, await executableChecksum(executable));
|
|
406
|
+
}
|
|
407
|
+
async function cloudflareCommand(options) {
|
|
408
|
+
try {
|
|
409
|
+
await execute2("cloudflared", ["--version"], { timeout: 3e3, windowsHide: true });
|
|
410
|
+
return "cloudflared";
|
|
411
|
+
} catch (error) {
|
|
412
|
+
if (error.code !== "ENOENT") throw new Error("The installed cloudflared could not start. Repair it or choose another connection provider.");
|
|
413
|
+
return ensureCloudflareBinary(options);
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
// src/computer/relay.ts
|
|
418
|
+
async function adoptRelay(owner, route, directory, outputFile, configFile) {
|
|
419
|
+
const output = outputFile ? await RelayOutput.adopt(directory, outputFile) : void 0;
|
|
420
|
+
const ownedConfig = configFile && path4.dirname(configFile) === path4.resolve(directory) && /^\.relay-config-[a-f0-9-]{36}\.json$/.test(path4.basename(configFile)) ? configFile : void 0;
|
|
421
|
+
return {
|
|
422
|
+
route,
|
|
423
|
+
owner,
|
|
424
|
+
outputFile: output?.file,
|
|
425
|
+
configFile: ownedConfig,
|
|
426
|
+
async close() {
|
|
427
|
+
await owner.close();
|
|
428
|
+
await output?.close();
|
|
429
|
+
if (ownedConfig) await fs2.rm(ownedConfig, { force: true });
|
|
430
|
+
}
|
|
431
|
+
};
|
|
432
|
+
}
|
|
433
|
+
function websocketURL(value) {
|
|
434
|
+
const url = new URL(value);
|
|
435
|
+
if (url.protocol === "https:") url.protocol = "wss:";
|
|
436
|
+
if (url.protocol !== "wss:" || !url.hostname || url.username || url.password || url.hash) {
|
|
437
|
+
throw new Error("Relay URLs must use wss:// with no embedded credentials or fragment.");
|
|
438
|
+
}
|
|
439
|
+
if (!url.pathname || url.pathname === "/") url.pathname = "/ssh";
|
|
440
|
+
return url.toString();
|
|
441
|
+
}
|
|
442
|
+
function validateRelayOptions(options) {
|
|
443
|
+
if (options.url) websocketURL(options.url);
|
|
444
|
+
if (options.kind === "custom" && !options.url) throw new Error("A custom relay needs --relay-url wss://your-host/ssh.");
|
|
445
|
+
if (options.kind === "oblien" && (!options.url || !options.oblienCredentialsFile || !Number.isSafeInteger(options.oblienTunnelId) || options.oblienTunnelId < 1)) {
|
|
446
|
+
throw new Error("Oblien setup is incomplete. Run mindwire connection oblien.");
|
|
447
|
+
}
|
|
448
|
+
if (options.kind === "cloudflare") {
|
|
449
|
+
const named = !!options.cloudflareTokenFile || !!options.cloudflareCredentialsFile && !!options.cloudflareTunnelId;
|
|
450
|
+
if (named && !options.url || options.url && !named) throw new Error("A named Cloudflare tunnel needs its hostname and credentials. Run mindwire connection cloudflare.");
|
|
451
|
+
if (options.cloudflareTokenFile && options.cloudflareCredentialsFile) throw new Error("Choose one Cloudflare tunnel credential source.");
|
|
452
|
+
}
|
|
453
|
+
if (options.kind === "ngrok" && options.ngrokTokenFile && !options.url) throw new Error("ngrok needs a reserved domain. Run mindwire connection ngrok.");
|
|
454
|
+
}
|
|
455
|
+
async function startRelay(options, port, runtime = {}) {
|
|
456
|
+
runtime.signal?.throwIfAborted();
|
|
457
|
+
validateRelayOptions(options);
|
|
458
|
+
if (options.kind === "none") return { close() {
|
|
459
|
+
} };
|
|
460
|
+
if (options.kind === "custom") {
|
|
461
|
+
if (!options.url) throw new Error("A custom relay needs --relay-url wss://your-host/ssh.");
|
|
462
|
+
return { route: { kind: "websocket", url: websocketURL(options.url) }, close() {
|
|
463
|
+
} };
|
|
464
|
+
}
|
|
465
|
+
const origin = `http://127.0.0.1:${port}`;
|
|
466
|
+
const env = { ...process.env };
|
|
467
|
+
let configFile;
|
|
468
|
+
const managed = options.kind === "oblien" || options.kind === "ngrok" && !!options.ngrokTokenFile;
|
|
469
|
+
let command, args;
|
|
470
|
+
if (managed) {
|
|
471
|
+
if (!runtime.cliPath || !runtime.outputDirectory) throw new Error("The managed relay needs its connection controller.");
|
|
472
|
+
configFile = path4.join(runtime.outputDirectory, `.relay-config-${randomUUID()}.json`);
|
|
473
|
+
await fs2.writeFile(configFile, JSON.stringify({ options, port }), { mode: 384 });
|
|
474
|
+
command = process.execPath;
|
|
475
|
+
args = [runtime.cliPath, "_relay", "--relay-config", configFile];
|
|
476
|
+
await runtime.onProgress?.(`Connecting through ${options.kind === "oblien" ? "Oblien" : "ngrok"}\u2026`);
|
|
477
|
+
} else if (options.kind === "cloudflare") {
|
|
478
|
+
command = await cloudflareCommand(runtime);
|
|
479
|
+
await runtime.onProgress?.("Connecting through Cloudflare\u2026");
|
|
480
|
+
if (options.cloudflareCredentialsFile && options.cloudflareTunnelId) {
|
|
481
|
+
if (!options.url || !runtime.outputDirectory) throw new Error("Named Cloudflare tunnel setup is incomplete.");
|
|
482
|
+
configFile = path4.join(runtime.outputDirectory, `.relay-config-${randomUUID()}.json`);
|
|
483
|
+
await fs2.writeFile(configFile, JSON.stringify({
|
|
484
|
+
tunnel: options.cloudflareTunnelId,
|
|
485
|
+
"credentials-file": options.cloudflareCredentialsFile,
|
|
486
|
+
ingress: [{ hostname: new URL(websocketURL(options.url)).hostname, service: origin }, { service: "http_status:404" }]
|
|
487
|
+
}), { mode: 384 });
|
|
488
|
+
args = ["tunnel", "--config", configFile, "--no-autoupdate", "run"];
|
|
489
|
+
} else if (options.cloudflareTokenFile) {
|
|
490
|
+
if (!options.url) throw new Error("A named Cloudflare tunnel needs --relay-url for its configured hostname.");
|
|
491
|
+
env.TUNNEL_TOKEN = (await readFile(options.cloudflareTokenFile, "utf8")).trim();
|
|
492
|
+
args = ["tunnel", "--no-autoupdate", "run"];
|
|
493
|
+
} else {
|
|
494
|
+
if (options.url) throw new Error("Use --cloudflare-token-file with a named tunnel, or --relay custom for an existing tunnel.");
|
|
495
|
+
args = ["tunnel", "--no-autoupdate", "--url", origin];
|
|
496
|
+
}
|
|
497
|
+
} else {
|
|
498
|
+
command = "ngrok";
|
|
499
|
+
args = ["http", origin, "--log", "stdout", "--log-format", "json"];
|
|
500
|
+
if (options.url) {
|
|
501
|
+
const url = new URL(websocketURL(options.url));
|
|
502
|
+
args.push("--url", `https://${url.host}`);
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
runtime.signal?.throwIfAborted();
|
|
506
|
+
const output = await RelayOutput.create(runtime.outputDirectory);
|
|
507
|
+
const child = spawn(command, args, { env, detached: true, stdio: ["ignore", output.fd, output.fd], windowsHide: true });
|
|
508
|
+
let failure;
|
|
509
|
+
child.on("error", (error) => {
|
|
510
|
+
failure = error;
|
|
511
|
+
});
|
|
512
|
+
let owner;
|
|
513
|
+
try {
|
|
514
|
+
await new Promise((resolve3, reject) => {
|
|
515
|
+
child.once("spawn", resolve3);
|
|
516
|
+
child.once("error", reject);
|
|
517
|
+
});
|
|
518
|
+
owner = await ownChild(child);
|
|
519
|
+
await runtime.onSpawn?.(owner, output.file, configFile);
|
|
520
|
+
const deadline = Date.now() + 45e3;
|
|
521
|
+
let buffer = "", cloudflareAddress = options.url, cloudflareConnected = false;
|
|
522
|
+
while (Date.now() < deadline) {
|
|
523
|
+
runtime.signal?.throwIfAborted();
|
|
524
|
+
if (failure) throw new Error(`Could not run ${options.kind}. Check its installation.`, { cause: failure });
|
|
525
|
+
const received = buffer + (await output.read()).toString();
|
|
526
|
+
buffer = received.slice(-16384);
|
|
527
|
+
let found;
|
|
528
|
+
if (!managed && options.kind === "cloudflare") {
|
|
529
|
+
cloudflareAddress ??= received.match(/https:\/\/[a-z0-9-]+\.trycloudflare\.com/)?.[0];
|
|
530
|
+
cloudflareConnected ||= /Registered tunnel connection/.test(received);
|
|
531
|
+
if (cloudflareConnected) found = cloudflareAddress;
|
|
532
|
+
} else {
|
|
533
|
+
for (const line of received.split(/\r?\n/)) {
|
|
534
|
+
try {
|
|
535
|
+
const event = JSON.parse(line);
|
|
536
|
+
if (managed && event.event === "relay_error") failure = new ProviderConnectionError(
|
|
537
|
+
providerNeedsAction(event.code) ? event.code : "provider_connection",
|
|
538
|
+
event.message ?? "The provider couldn't connect."
|
|
539
|
+
);
|
|
540
|
+
if (managed && event.event === "relay_ready") found = event.url;
|
|
541
|
+
if (event.msg === "started tunnel" && event.url?.startsWith("https://")) found = event.url;
|
|
542
|
+
} catch {
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
if (found) {
|
|
547
|
+
const route = { kind: "websocket", url: websocketURL(found) };
|
|
548
|
+
output.discard();
|
|
549
|
+
const provider = owner;
|
|
550
|
+
return {
|
|
551
|
+
route,
|
|
552
|
+
process: child,
|
|
553
|
+
owner,
|
|
554
|
+
outputFile: output.file,
|
|
555
|
+
configFile,
|
|
556
|
+
async close() {
|
|
557
|
+
await provider.close();
|
|
558
|
+
await output.close();
|
|
559
|
+
if (configFile) await fs2.rm(configFile, { force: true });
|
|
560
|
+
}
|
|
561
|
+
};
|
|
562
|
+
}
|
|
563
|
+
if (failure) throw failure;
|
|
564
|
+
if (!owner.alive()) throw new Error(`${options.kind} exited before the tunnel connected. Check its account configuration.`);
|
|
565
|
+
await setTimeout$1(50, void 0, { signal: runtime.signal });
|
|
566
|
+
}
|
|
567
|
+
throw new Error(`${options.kind} did not establish its tunnel. Check your internet connection and its configuration.`);
|
|
568
|
+
} catch (error) {
|
|
569
|
+
if (owner) await owner.close();
|
|
570
|
+
else if (child.pid) await (await ownChild(child)).close();
|
|
571
|
+
await output.close();
|
|
572
|
+
if (configFile) await fs2.rm(configFile, { force: true });
|
|
573
|
+
throw error;
|
|
574
|
+
}
|
|
575
|
+
}
|
|
576
|
+
async function acquireProcessLock(file, options = {}) {
|
|
577
|
+
const owner = await currentProcessIdentity();
|
|
578
|
+
const queue = file + ".queue", id = randomUUID() + ".json", claimPath = queue + "/" + id;
|
|
579
|
+
await fs2.mkdir(queue, { recursive: true, mode: 448 });
|
|
580
|
+
let lock;
|
|
581
|
+
const release = async () => {
|
|
582
|
+
try {
|
|
583
|
+
if (lock) {
|
|
584
|
+
const held = lock;
|
|
585
|
+
lock = void 0;
|
|
586
|
+
const owned = await held.stat();
|
|
587
|
+
await held.close();
|
|
588
|
+
const current = await fs2.stat(file).catch(() => void 0);
|
|
589
|
+
if (current?.ino === owned.ino && current.dev === owned.dev) await fs2.rm(file, { force: true });
|
|
590
|
+
}
|
|
591
|
+
} finally {
|
|
592
|
+
await fs2.rm(claimPath, { force: true });
|
|
593
|
+
}
|
|
594
|
+
};
|
|
595
|
+
const publish = async (claim) => {
|
|
596
|
+
const temporary = claimPath + ".tmp";
|
|
597
|
+
try {
|
|
598
|
+
await fs2.writeFile(temporary, JSON.stringify(claim), { mode: 384 });
|
|
599
|
+
await fs2.rename(temporary, claimPath);
|
|
600
|
+
} finally {
|
|
601
|
+
await fs2.rm(temporary, { force: true });
|
|
602
|
+
}
|
|
603
|
+
};
|
|
604
|
+
const claims = async () => {
|
|
605
|
+
const entries = [];
|
|
606
|
+
for (const candidate of await fs2.readdir(queue)) {
|
|
607
|
+
if (!candidate.endsWith(".json")) continue;
|
|
608
|
+
const content = await fs2.readFile(queue + "/" + candidate, "utf8").catch((error) => {
|
|
609
|
+
if (error.code === "ENOENT") return void 0;
|
|
610
|
+
throw error;
|
|
611
|
+
});
|
|
612
|
+
if (content === void 0) continue;
|
|
613
|
+
const claim = JSON.parse(content);
|
|
614
|
+
if (!claim.owner || claim.ticket !== null && (!Number.isSafeInteger(claim.ticket) || claim.ticket < 1)) {
|
|
615
|
+
throw new Error("Invalid Mindwire lock ticket.");
|
|
616
|
+
}
|
|
617
|
+
if (await processStateAlive(claim.owner)) entries.push({ id: candidate, claim });
|
|
618
|
+
else await fs2.rm(queue + "/" + candidate, { force: true });
|
|
619
|
+
}
|
|
620
|
+
return entries;
|
|
621
|
+
};
|
|
622
|
+
try {
|
|
623
|
+
options.signal?.throwIfAborted();
|
|
624
|
+
await publish({ owner, ticket: null });
|
|
625
|
+
const ticket = Math.max(0, ...(await claims()).map((entry) => entry.claim.ticket ?? 0)) + 1;
|
|
626
|
+
if (!Number.isSafeInteger(ticket)) throw new Error("Mindwire's connection queue is full. Retry shortly.");
|
|
627
|
+
await publish({ owner, ticket });
|
|
628
|
+
const deadline = Date.now() + 3e4;
|
|
629
|
+
while (Date.now() < deadline) {
|
|
630
|
+
options.signal?.throwIfAborted();
|
|
631
|
+
if (await options.onWait?.()) {
|
|
632
|
+
await release();
|
|
633
|
+
return void 0;
|
|
634
|
+
}
|
|
635
|
+
const ahead = (await claims()).some((entry) => entry.id !== id && (entry.claim.ticket === null || entry.claim.ticket < ticket || entry.claim.ticket === ticket && entry.id < id));
|
|
636
|
+
if (!ahead) {
|
|
637
|
+
try {
|
|
638
|
+
lock = await fs2.open(file, "wx", 384);
|
|
639
|
+
await lock.writeFile(JSON.stringify(owner));
|
|
640
|
+
return { close: release };
|
|
641
|
+
} catch (error) {
|
|
642
|
+
if (error.code !== "EEXIST") throw error;
|
|
643
|
+
const holder = await readProcessState(file);
|
|
644
|
+
const age = Date.now() - (await fs2.stat(file).catch(() => ({ mtimeMs: Date.now() }))).mtimeMs;
|
|
645
|
+
if (age > 5e3 && !await processStateAlive(holder)) {
|
|
646
|
+
await fs2.rm(file, { force: true });
|
|
647
|
+
continue;
|
|
648
|
+
}
|
|
649
|
+
}
|
|
650
|
+
}
|
|
651
|
+
await setTimeout$1(250, void 0, { signal: options.signal });
|
|
652
|
+
}
|
|
653
|
+
throw new Error("Another Mindwire operation is in progress. Try again shortly.");
|
|
654
|
+
} catch (error) {
|
|
655
|
+
await release();
|
|
656
|
+
throw error;
|
|
657
|
+
}
|
|
658
|
+
}
|
|
243
659
|
|
|
244
660
|
// src/http.ts
|
|
245
661
|
var DEFAULT_TIMEOUT_MS = 12e4;
|
|
@@ -287,13 +703,13 @@ var Http = class {
|
|
|
287
703
|
* SDK error instead of a bare `TypeError: fetch failed`. An abort (the request's own signal, or
|
|
288
704
|
* our timeout controller) is re-thrown untouched — `fetchWithTimeout` classifies it.
|
|
289
705
|
*/
|
|
290
|
-
async fetchOnce(f, url, init, method,
|
|
706
|
+
async fetchOnce(f, url, init, method, path5) {
|
|
291
707
|
try {
|
|
292
708
|
return await f(url, init);
|
|
293
709
|
} catch (e) {
|
|
294
710
|
const aborted = init.signal?.aborted ?? false;
|
|
295
711
|
if (aborted || isAbortError(e)) throw e;
|
|
296
|
-
throw new MindwireError(`mindwire: ${method} ${
|
|
712
|
+
throw new MindwireError(`mindwire: ${method} ${path5} \u2014 network request failed`, { cause: e });
|
|
297
713
|
}
|
|
298
714
|
}
|
|
299
715
|
/**
|
|
@@ -302,9 +718,9 @@ var Http = class {
|
|
|
302
718
|
* `timeoutMs`; whichever aborts first wins. Timeout → {@link TimeoutError}; the caller's own abort
|
|
303
719
|
* propagates as-is (its `reason`). `timeoutMs<=0` disables the deadline entirely.
|
|
304
720
|
*/
|
|
305
|
-
async fetchWithTimeout(f, url, init, userSignal, method,
|
|
721
|
+
async fetchWithTimeout(f, url, init, userSignal, method, path5) {
|
|
306
722
|
if (!this.timeoutMs || this.timeoutMs <= 0) {
|
|
307
|
-
return this.fetchOnce(f, url, { ...init, ...userSignal ? { signal: userSignal } : {} }, method,
|
|
723
|
+
return this.fetchOnce(f, url, { ...init, ...userSignal ? { signal: userSignal } : {} }, method, path5);
|
|
308
724
|
}
|
|
309
725
|
const ctrl = new AbortController();
|
|
310
726
|
const onAbort = () => ctrl.abort(userSignal?.reason);
|
|
@@ -318,9 +734,9 @@ var Http = class {
|
|
|
318
734
|
ctrl.abort();
|
|
319
735
|
}, this.timeoutMs);
|
|
320
736
|
try {
|
|
321
|
-
return await this.fetchOnce(f, url, { ...init, signal: ctrl.signal }, method,
|
|
737
|
+
return await this.fetchOnce(f, url, { ...init, signal: ctrl.signal }, method, path5);
|
|
322
738
|
} catch (e) {
|
|
323
|
-
if (timedOut) throw new TimeoutError(method,
|
|
739
|
+
if (timedOut) throw new TimeoutError(method, path5, this.timeoutMs);
|
|
324
740
|
if (userSignal?.aborted) throw userSignal.reason ?? e;
|
|
325
741
|
throw e;
|
|
326
742
|
} finally {
|
|
@@ -359,8 +775,8 @@ var Http = class {
|
|
|
359
775
|
async ready() {
|
|
360
776
|
await this.base();
|
|
361
777
|
}
|
|
362
|
-
url(baseUrl,
|
|
363
|
-
const u = new URL(baseUrl + (
|
|
778
|
+
url(baseUrl, path5, query) {
|
|
779
|
+
const u = new URL(baseUrl + (path5.startsWith("/") ? path5 : `/${path5}`));
|
|
364
780
|
if (query) {
|
|
365
781
|
for (const [k, v] of Object.entries(query)) {
|
|
366
782
|
if (v !== void 0) u.searchParams.set(k, String(v));
|
|
@@ -374,10 +790,10 @@ var Http = class {
|
|
|
374
790
|
if (hasBody) h["Content-Type"] = "application/json";
|
|
375
791
|
return h;
|
|
376
792
|
}
|
|
377
|
-
async request(method,
|
|
793
|
+
async request(method, path5, init = {}) {
|
|
378
794
|
const base = await this.base();
|
|
379
795
|
const hasBody = init.body !== void 0;
|
|
380
|
-
const url = this.url(base.baseUrl,
|
|
796
|
+
const url = this.url(base.baseUrl, path5, init.query);
|
|
381
797
|
const extra = { ...base.headers, ...init.headers };
|
|
382
798
|
const f = base.fetch ?? this.fetchImpl;
|
|
383
799
|
const send = async (force) => this.fetchWithTimeout(
|
|
@@ -390,7 +806,7 @@ var Http = class {
|
|
|
390
806
|
},
|
|
391
807
|
init.signal,
|
|
392
808
|
method,
|
|
393
|
-
|
|
809
|
+
path5
|
|
394
810
|
);
|
|
395
811
|
let res = await send(false);
|
|
396
812
|
if (res.status === 401 && base.getToken) res = await send(true);
|
|
@@ -401,13 +817,13 @@ var Http = class {
|
|
|
401
817
|
try {
|
|
402
818
|
return JSON.parse(text);
|
|
403
819
|
} catch (e) {
|
|
404
|
-
throw new MindwireError(`mindwire: ${method} ${
|
|
820
|
+
throw new MindwireError(`mindwire: ${method} ${path5} returned a non-JSON body`, { cause: e });
|
|
405
821
|
}
|
|
406
822
|
}
|
|
407
823
|
/** Open a streaming response (SSE). Caller owns the body. */
|
|
408
|
-
async open(method,
|
|
824
|
+
async open(method, path5, init = {}) {
|
|
409
825
|
const base = await this.base();
|
|
410
|
-
const url = this.url(base.baseUrl,
|
|
826
|
+
const url = this.url(base.baseUrl, path5, init.query);
|
|
411
827
|
const extra = { Accept: "text/event-stream", ...base.headers, ...init.headers };
|
|
412
828
|
const f = base.fetch ?? this.fetchImpl;
|
|
413
829
|
const send = async (force) => this.fetchOnce(
|
|
@@ -420,13 +836,13 @@ var Http = class {
|
|
|
420
836
|
...init.signal ? { signal: init.signal } : {}
|
|
421
837
|
},
|
|
422
838
|
method,
|
|
423
|
-
|
|
839
|
+
path5
|
|
424
840
|
);
|
|
425
841
|
let res = await send(false);
|
|
426
842
|
if (res.status === 401 && base.getToken) res = await send(true);
|
|
427
843
|
if (!res.ok) throw await this.toApiError(method, res);
|
|
428
844
|
if (!res.body)
|
|
429
|
-
throw new MindwireError(`mindwire: ${method} ${
|
|
845
|
+
throw new MindwireError(`mindwire: ${method} ${path5} returned no response body to stream`);
|
|
430
846
|
return res;
|
|
431
847
|
}
|
|
432
848
|
async toApiError(method, res) {
|
|
@@ -676,12 +1092,12 @@ var ProjectSyncError = class extends MindwireError {
|
|
|
676
1092
|
};
|
|
677
1093
|
var active = (o) => ["queued", "exporting", "preparing", "applying"].includes(o.status);
|
|
678
1094
|
var prefix = "/workspace/sync";
|
|
679
|
-
function
|
|
1095
|
+
function delay4(ms, signal) {
|
|
680
1096
|
signal?.throwIfAborted();
|
|
681
|
-
return new Promise((
|
|
1097
|
+
return new Promise((resolve3, reject) => {
|
|
682
1098
|
const done = () => {
|
|
683
1099
|
signal?.removeEventListener("abort", abort);
|
|
684
|
-
|
|
1100
|
+
resolve3();
|
|
685
1101
|
};
|
|
686
1102
|
const timer = setTimeout(done, ms);
|
|
687
1103
|
const abort = () => {
|
|
@@ -743,7 +1159,7 @@ var ProjectSyncApi = class {
|
|
|
743
1159
|
const o = await this.get(id, { signal: options.signal });
|
|
744
1160
|
options.onUpdate?.(o);
|
|
745
1161
|
if (!active(o)) return o;
|
|
746
|
-
await
|
|
1162
|
+
await delay4(500, options.signal);
|
|
747
1163
|
}
|
|
748
1164
|
}
|
|
749
1165
|
/** Repeating this relay skips acknowledged checkpoints and verified chunks.
|
|
@@ -830,6 +1246,23 @@ var ProjectSyncApi = class {
|
|
|
830
1246
|
}
|
|
831
1247
|
};
|
|
832
1248
|
|
|
1249
|
+
// src/conversations.ts
|
|
1250
|
+
var ConversationsApi = class {
|
|
1251
|
+
constructor(mw) {
|
|
1252
|
+
this.mw = mw;
|
|
1253
|
+
}
|
|
1254
|
+
mw;
|
|
1255
|
+
/** Read-only; includes folders not registered as Mindwire projects. */
|
|
1256
|
+
list(query = {}) {
|
|
1257
|
+
return this.mw.http.request("GET", "/workspace/conversations", { query: { ...query } });
|
|
1258
|
+
}
|
|
1259
|
+
/** Idempotently associates the selected native session with its original folder.
|
|
1260
|
+
* Use chatId with messages()/turn() and the ordinary chat APIs afterward. */
|
|
1261
|
+
open(request) {
|
|
1262
|
+
return this.mw.http.request("POST", "/workspace/conversations/open", { body: request });
|
|
1263
|
+
}
|
|
1264
|
+
};
|
|
1265
|
+
|
|
833
1266
|
// src/workspace.ts
|
|
834
1267
|
var ProjectOperationsApi = class {
|
|
835
1268
|
constructor(mw) {
|
|
@@ -901,6 +1334,7 @@ var WorkspaceCollection = class {
|
|
|
901
1334
|
var WorkspaceApi = class {
|
|
902
1335
|
constructor(mw) {
|
|
903
1336
|
this.mw = mw;
|
|
1337
|
+
this.conversations = new ConversationsApi(mw);
|
|
904
1338
|
this.sync = new ProjectSyncApi(mw);
|
|
905
1339
|
this.git = new GitAccessApi(mw);
|
|
906
1340
|
this.operations = new ProjectOperationsApi(mw);
|
|
@@ -909,6 +1343,7 @@ var WorkspaceApi = class {
|
|
|
909
1343
|
this.chats = new WorkspaceCollection(mw, "chats");
|
|
910
1344
|
}
|
|
911
1345
|
mw;
|
|
1346
|
+
conversations;
|
|
912
1347
|
sync;
|
|
913
1348
|
git;
|
|
914
1349
|
operations;
|
|
@@ -919,8 +1354,8 @@ var WorkspaceApi = class {
|
|
|
919
1354
|
return this.mw.http.request("GET", "/workspace", { query: options });
|
|
920
1355
|
}
|
|
921
1356
|
/** Read the saved icon, or preview a candidate relative path within the project. Requires projectIconsVersion >= 1. */
|
|
922
|
-
projectIcon(projectId,
|
|
923
|
-
return this.mw.http.request("GET", `/workspace/projects/${encodeURIComponent(projectId)}/icon`, { query: { path:
|
|
1357
|
+
projectIcon(projectId, path5) {
|
|
1358
|
+
return this.mw.http.request("GET", `/workspace/projects/${encodeURIComponent(projectId)}/icon`, { query: { path: path5 } });
|
|
924
1359
|
}
|
|
925
1360
|
/** Start an operation owned by the daemon. The same ID/payload returns the existing operation. */
|
|
926
1361
|
createProject(request) {
|
|
@@ -1117,17 +1552,17 @@ var ExecutionApi = class {
|
|
|
1117
1552
|
resources() {
|
|
1118
1553
|
return this.client.http.request("GET", "/workspace/resources");
|
|
1119
1554
|
}
|
|
1120
|
-
files(
|
|
1121
|
-
return this.client.http.request("GET", "/workspace/files", { query: { path:
|
|
1555
|
+
files(path5 = "", query) {
|
|
1556
|
+
return this.client.http.request("GET", "/workspace/files", { query: { path: path5, query } });
|
|
1122
1557
|
}
|
|
1123
|
-
read(
|
|
1124
|
-
return this.client.http.request("GET", "/workspace/file", { query: { path:
|
|
1558
|
+
read(path5) {
|
|
1559
|
+
return this.client.http.request("GET", "/workspace/file", { query: { path: path5 } });
|
|
1125
1560
|
}
|
|
1126
|
-
write(
|
|
1127
|
-
return this.client.http.request("PUT", "/workspace/file", { body: { path:
|
|
1561
|
+
write(path5, content) {
|
|
1562
|
+
return this.client.http.request("PUT", "/workspace/file", { body: { path: path5, content } });
|
|
1128
1563
|
}
|
|
1129
|
-
remove(
|
|
1130
|
-
return this.client.http.request("DELETE", "/workspace/file", { query: { path:
|
|
1564
|
+
remove(path5) {
|
|
1565
|
+
return this.client.http.request("DELETE", "/workspace/file", { query: { path: path5 } });
|
|
1131
1566
|
}
|
|
1132
1567
|
exec(command, signal) {
|
|
1133
1568
|
return this.client.http.request("POST", "/workspace/exec", { body: command, signal });
|
|
@@ -1201,7 +1636,7 @@ async function spawnDaemon(opts) {
|
|
|
1201
1636
|
"MindWire embedded mode needs a server runtime (Node/Bun/Deno). In the browser or an edge runtime, pass { baseUrl } to connect to a running daemon."
|
|
1202
1637
|
);
|
|
1203
1638
|
}
|
|
1204
|
-
const { spawn:
|
|
1639
|
+
const { spawn: spawn3 } = await import('child_process');
|
|
1205
1640
|
const net = await import('net');
|
|
1206
1641
|
const proc = serverRuntime().process;
|
|
1207
1642
|
const { randomBytes } = await import('crypto');
|
|
@@ -1210,7 +1645,7 @@ async function spawnDaemon(opts) {
|
|
|
1210
1645
|
const port = await freePort(net);
|
|
1211
1646
|
const baseUrl = `http://127.0.0.1:${port}`;
|
|
1212
1647
|
const token = randomBytes(32).toString("hex");
|
|
1213
|
-
const child =
|
|
1648
|
+
const child = spawn3(bin, [], {
|
|
1214
1649
|
env: {
|
|
1215
1650
|
...proc.env,
|
|
1216
1651
|
ADDR: `127.0.0.1:${port}`,
|
|
@@ -1239,8 +1674,8 @@ async function spawnDaemon(opts) {
|
|
|
1239
1674
|
return { baseUrl, token, stop };
|
|
1240
1675
|
}
|
|
1241
1676
|
async function resolveBinary(explicit) {
|
|
1242
|
-
const
|
|
1243
|
-
const
|
|
1677
|
+
const fs4 = await import('fs');
|
|
1678
|
+
const path5 = await import('path');
|
|
1244
1679
|
const proc = serverRuntime().process;
|
|
1245
1680
|
const ext = proc.platform === "win32" ? ".exe" : "";
|
|
1246
1681
|
const candidates = [];
|
|
@@ -1248,14 +1683,14 @@ async function resolveBinary(explicit) {
|
|
|
1248
1683
|
if (proc.env?.MINDWIRE_DAEMON) candidates.push(proc.env.MINDWIRE_DAEMON);
|
|
1249
1684
|
try {
|
|
1250
1685
|
const url = await import('url');
|
|
1251
|
-
const here =
|
|
1686
|
+
const here = path5.dirname(url.fileURLToPath(import.meta.url));
|
|
1252
1687
|
const named = `mindwired-${proc.platform}-${proc.arch}${ext}`;
|
|
1253
|
-
candidates.push(
|
|
1688
|
+
candidates.push(path5.join(here, "bin", named), path5.join(here, "..", "bin", named));
|
|
1254
1689
|
} catch {
|
|
1255
1690
|
}
|
|
1256
1691
|
for (const c of candidates) {
|
|
1257
1692
|
try {
|
|
1258
|
-
if (
|
|
1693
|
+
if (fs4.existsSync(c)) return { bin: c, found: true, checked: candidates };
|
|
1259
1694
|
} catch {
|
|
1260
1695
|
}
|
|
1261
1696
|
}
|
|
@@ -1278,13 +1713,13 @@ function daemonStartError(resolved, cause) {
|
|
|
1278
1713
|
return `Couldn't start the mindwire daemon (${resolved.bin}): ${cause.message}. Set MINDWIRE_DAEMON to the binary path, or pass { baseUrl } to use a running daemon.`;
|
|
1279
1714
|
}
|
|
1280
1715
|
function freePort(net) {
|
|
1281
|
-
return new Promise((
|
|
1716
|
+
return new Promise((resolve3, reject) => {
|
|
1282
1717
|
const srv = net.createServer();
|
|
1283
1718
|
srv.on("error", reject);
|
|
1284
1719
|
srv.listen(0, "127.0.0.1", () => {
|
|
1285
1720
|
const addr = srv.address();
|
|
1286
1721
|
const port = addr && typeof addr === "object" ? addr.port : 0;
|
|
1287
|
-
srv.close(() =>
|
|
1722
|
+
srv.close(() => resolve3(port));
|
|
1288
1723
|
});
|
|
1289
1724
|
});
|
|
1290
1725
|
}
|
|
@@ -1542,14 +1977,14 @@ var Mindwire = class _Mindwire {
|
|
|
1542
1977
|
}
|
|
1543
1978
|
/**
|
|
1544
1979
|
* `POST /chats/{id}/fork` — clone a chat into a new id (generated when `newChatId` is omitted).
|
|
1545
|
-
*
|
|
1546
|
-
*
|
|
1547
|
-
*
|
|
1548
|
-
*
|
|
1980
|
+
* Codex and Claude preserve native context. `beforeMessageId` excludes that prompt and every
|
|
1981
|
+
* later message; send the edited prompt to the returned chat with `turn`. Reuse `newChatId`
|
|
1982
|
+
* and the turn's `requestId` on retries. The source chat and filesystem are unchanged.
|
|
1983
|
+
* A source with a live turn returns 409; an unknown source returns 404.
|
|
1549
1984
|
*/
|
|
1550
1985
|
forkChat(chatId, opts = {}) {
|
|
1551
1986
|
return this.http.request("POST", `/chats/${encodeURIComponent(chatId)}/fork`, {
|
|
1552
|
-
body: opts
|
|
1987
|
+
body: opts
|
|
1553
1988
|
});
|
|
1554
1989
|
}
|
|
1555
1990
|
/**
|
|
@@ -1688,8 +2123,8 @@ var Mindwire = class _Mindwire {
|
|
|
1688
2123
|
* agent type. Frames are live snapshots (no replay); an empty `samples` array is a valid keep-alive.
|
|
1689
2124
|
*/
|
|
1690
2125
|
async *processes(opts = {}) {
|
|
1691
|
-
const
|
|
1692
|
-
const res = await this.http.open("GET",
|
|
2126
|
+
const path5 = "/processes/stream" + (opts.agent ? `?agent=${encodeURIComponent(opts.agent)}` : "");
|
|
2127
|
+
const res = await this.http.open("GET", path5, {
|
|
1693
2128
|
...opts.signal ? { signal: opts.signal } : {}
|
|
1694
2129
|
});
|
|
1695
2130
|
yield* readSSE(res.body, opts.signal);
|
|
@@ -1979,97 +2414,14 @@ var NotifyApi = class {
|
|
|
1979
2414
|
await this.mw.http.request("DELETE", `/notify/rules/${encodeURIComponent(id)}`);
|
|
1980
2415
|
}
|
|
1981
2416
|
};
|
|
1982
|
-
async function acquireProcessLock(file, options = {}) {
|
|
1983
|
-
const owner = await currentProcessIdentity();
|
|
1984
|
-
const queue = file + ".queue", id = randomUUID() + ".json", claimPath = queue + "/" + id;
|
|
1985
|
-
await fs.mkdir(queue, { recursive: true, mode: 448 });
|
|
1986
|
-
let lock;
|
|
1987
|
-
const release = async () => {
|
|
1988
|
-
try {
|
|
1989
|
-
if (lock) {
|
|
1990
|
-
const held = lock;
|
|
1991
|
-
lock = void 0;
|
|
1992
|
-
const owned = await held.stat();
|
|
1993
|
-
await held.close();
|
|
1994
|
-
const current = await fs.stat(file).catch(() => void 0);
|
|
1995
|
-
if (current?.ino === owned.ino && current.dev === owned.dev) await fs.rm(file, { force: true });
|
|
1996
|
-
}
|
|
1997
|
-
} finally {
|
|
1998
|
-
await fs.rm(claimPath, { force: true });
|
|
1999
|
-
}
|
|
2000
|
-
};
|
|
2001
|
-
const publish = async (claim) => {
|
|
2002
|
-
const temporary = claimPath + ".tmp";
|
|
2003
|
-
try {
|
|
2004
|
-
await fs.writeFile(temporary, JSON.stringify(claim), { mode: 384 });
|
|
2005
|
-
await fs.rename(temporary, claimPath);
|
|
2006
|
-
} finally {
|
|
2007
|
-
await fs.rm(temporary, { force: true });
|
|
2008
|
-
}
|
|
2009
|
-
};
|
|
2010
|
-
const claims = async () => {
|
|
2011
|
-
const entries = [];
|
|
2012
|
-
for (const candidate of await fs.readdir(queue)) {
|
|
2013
|
-
if (!candidate.endsWith(".json")) continue;
|
|
2014
|
-
const content = await fs.readFile(queue + "/" + candidate, "utf8").catch((error) => {
|
|
2015
|
-
if (error.code === "ENOENT") return void 0;
|
|
2016
|
-
throw error;
|
|
2017
|
-
});
|
|
2018
|
-
if (content === void 0) continue;
|
|
2019
|
-
const claim = JSON.parse(content);
|
|
2020
|
-
if (!claim.owner || claim.ticket !== null && (!Number.isSafeInteger(claim.ticket) || claim.ticket < 1)) {
|
|
2021
|
-
throw new Error("Invalid Mindwire lock ticket.");
|
|
2022
|
-
}
|
|
2023
|
-
if (await processStateAlive(claim.owner)) entries.push({ id: candidate, claim });
|
|
2024
|
-
else await fs.rm(queue + "/" + candidate, { force: true });
|
|
2025
|
-
}
|
|
2026
|
-
return entries;
|
|
2027
|
-
};
|
|
2028
|
-
try {
|
|
2029
|
-
options.signal?.throwIfAborted();
|
|
2030
|
-
await publish({ owner, ticket: null });
|
|
2031
|
-
const ticket = Math.max(0, ...(await claims()).map((entry) => entry.claim.ticket ?? 0)) + 1;
|
|
2032
|
-
if (!Number.isSafeInteger(ticket)) throw new Error("Mindwire's connection queue is full. Retry shortly.");
|
|
2033
|
-
await publish({ owner, ticket });
|
|
2034
|
-
const deadline = Date.now() + 3e4;
|
|
2035
|
-
while (Date.now() < deadline) {
|
|
2036
|
-
options.signal?.throwIfAborted();
|
|
2037
|
-
if (await options.onWait?.()) {
|
|
2038
|
-
await release();
|
|
2039
|
-
return void 0;
|
|
2040
|
-
}
|
|
2041
|
-
const ahead = (await claims()).some((entry) => entry.id !== id && (entry.claim.ticket === null || entry.claim.ticket < ticket || entry.claim.ticket === ticket && entry.id < id));
|
|
2042
|
-
if (!ahead) {
|
|
2043
|
-
try {
|
|
2044
|
-
lock = await fs.open(file, "wx", 384);
|
|
2045
|
-
await lock.writeFile(JSON.stringify(owner));
|
|
2046
|
-
return { close: release };
|
|
2047
|
-
} catch (error) {
|
|
2048
|
-
if (error.code !== "EEXIST") throw error;
|
|
2049
|
-
const holder = await readProcessState(file);
|
|
2050
|
-
const age = Date.now() - (await fs.stat(file).catch(() => ({ mtimeMs: Date.now() }))).mtimeMs;
|
|
2051
|
-
if (age > 5e3 && !await processStateAlive(holder)) {
|
|
2052
|
-
await fs.rm(file, { force: true });
|
|
2053
|
-
continue;
|
|
2054
|
-
}
|
|
2055
|
-
}
|
|
2056
|
-
}
|
|
2057
|
-
await setTimeout$1(250, void 0, { signal: options.signal });
|
|
2058
|
-
}
|
|
2059
|
-
throw new Error("Another Mindwire operation is in progress. Try again shortly.");
|
|
2060
|
-
} catch (error) {
|
|
2061
|
-
await release();
|
|
2062
|
-
throw error;
|
|
2063
|
-
}
|
|
2064
|
-
}
|
|
2065
2417
|
|
|
2066
2418
|
// src/computer/lifecycle.ts
|
|
2067
|
-
var CONTROLLER_PROTOCOL =
|
|
2068
|
-
var defaultStateDirectory = () =>
|
|
2419
|
+
var CONTROLLER_PROTOCOL = 3;
|
|
2420
|
+
var defaultStateDirectory = () => path4.join(homedir(), ".mindwire", "computer");
|
|
2069
2421
|
var defaultComputerConfig = () => ({ directory: homedir(), bind: "0.0.0.0", sshPort: 8791, websocketPort: 8792, relay: { kind: "cloudflare" } });
|
|
2070
2422
|
async function readJSON(file) {
|
|
2071
2423
|
try {
|
|
2072
|
-
return JSON.parse(await
|
|
2424
|
+
return JSON.parse(await fs2.readFile(file, "utf8"));
|
|
2073
2425
|
} catch (error) {
|
|
2074
2426
|
if (error.code === "ENOENT") return void 0;
|
|
2075
2427
|
throw error;
|
|
@@ -2078,18 +2430,18 @@ async function readJSON(file) {
|
|
|
2078
2430
|
async function writeJSON(file, value) {
|
|
2079
2431
|
const temp = `${file}.${randomUUID()}.tmp`;
|
|
2080
2432
|
try {
|
|
2081
|
-
await
|
|
2082
|
-
await
|
|
2433
|
+
await fs2.writeFile(temp, JSON.stringify(value) + "\n", { mode: 384 });
|
|
2434
|
+
await fs2.rename(temp, file);
|
|
2083
2435
|
} finally {
|
|
2084
|
-
await
|
|
2436
|
+
await fs2.rm(temp, { force: true });
|
|
2085
2437
|
}
|
|
2086
2438
|
}
|
|
2087
2439
|
async function computerClient(directory) {
|
|
2088
|
-
const runtime = await readJSON(
|
|
2440
|
+
const runtime = await readJSON(path4.join(directory, "computer-runtime.json"));
|
|
2089
2441
|
if (!runtime) throw new Error("Mindwire is not running. Run mindwire connect on the computer.");
|
|
2090
2442
|
const url = new URL(`http://${runtime.apiAddress}`);
|
|
2091
2443
|
if (!["127.0.0.1", "[::1]"].includes(url.hostname) || url.username || url.password) throw new Error("Invalid local Mindwire address.");
|
|
2092
|
-
const token = (await
|
|
2444
|
+
const token = (await fs2.readFile(path4.join(directory, "daemon.token"), "utf8")).trim();
|
|
2093
2445
|
const client = new Mindwire({ target: remote(url.origin, { token }), requestTimeoutMs: 3e3 });
|
|
2094
2446
|
const info = await client.computer.info();
|
|
2095
2447
|
if (info.computerId !== runtime.computerId || info.pid !== runtime.pid) throw new Error("The local Mindwire process changed. Retry the command.");
|
|
@@ -2107,20 +2459,21 @@ function directRoutes(config, port) {
|
|
|
2107
2459
|
async function ensureComputer(directory, cliPath, patch = {}, onProgress, options = {}) {
|
|
2108
2460
|
const resume = options.resume !== false;
|
|
2109
2461
|
options.signal?.throwIfAborted();
|
|
2110
|
-
await
|
|
2111
|
-
const stoppedPath =
|
|
2112
|
-
if (resume) await
|
|
2462
|
+
await fs2.mkdir(directory, { recursive: true, mode: 448 });
|
|
2463
|
+
const stoppedPath = path4.join(directory, "computer-stopped.json");
|
|
2464
|
+
if (resume) await fs2.rm(stoppedPath, { force: true });
|
|
2113
2465
|
else if ((await readJSON(stoppedPath))?.stopped) throw new Error("Mindwire was stopped by its owner.");
|
|
2114
2466
|
let current;
|
|
2115
2467
|
try {
|
|
2116
2468
|
current = await computerClient(directory);
|
|
2117
2469
|
} catch {
|
|
2118
2470
|
}
|
|
2119
|
-
const configPath =
|
|
2471
|
+
const configPath = path4.join(directory, "computer-config.json");
|
|
2120
2472
|
const previous = await readJSON(configPath);
|
|
2121
2473
|
const config = { ...defaultComputerConfig(), ...previous, ...patch };
|
|
2474
|
+
validateRelayOptions(config.relay);
|
|
2122
2475
|
const requestedAt = resume ? Date.now() : 0;
|
|
2123
|
-
if (resume) await writeJSON(
|
|
2476
|
+
if (resume) await writeJSON(path4.join(directory, "computer-check.json"), { requestedAt });
|
|
2124
2477
|
const compatible = (controller) => (controller?.protocol ?? 0) >= CONTROLLER_PROTOCOL && versionAtLeast(controller?.cliVersion, SDK_VERSION);
|
|
2125
2478
|
let launchedPID;
|
|
2126
2479
|
let launchFailure;
|
|
@@ -2130,14 +2483,21 @@ async function ensureComputer(directory, cliPath, patch = {}, onProgress, option
|
|
|
2130
2483
|
const deadline = Date.now() + (options.timeoutMs ?? 12e4);
|
|
2131
2484
|
while (Date.now() < deadline) {
|
|
2132
2485
|
options.signal?.throwIfAborted();
|
|
2133
|
-
const controller = await readJSON(
|
|
2486
|
+
const controller = await readJSON(path4.join(directory, "computer-controller.json"));
|
|
2134
2487
|
lastState = controller;
|
|
2135
2488
|
if (launchFailure) throw launchFailure;
|
|
2136
2489
|
if (launchedPID && controller?.pid !== launchedPID) {
|
|
2137
2490
|
await setTimeout$1(250, void 0, { signal: options.signal });
|
|
2138
2491
|
continue;
|
|
2139
2492
|
}
|
|
2493
|
+
if (options.waitForRoutes === false && compatible(controller) && await processStateAlive(controller)) {
|
|
2494
|
+
try {
|
|
2495
|
+
return await computerClient(directory);
|
|
2496
|
+
} catch {
|
|
2497
|
+
}
|
|
2498
|
+
}
|
|
2140
2499
|
if (controller?.error && !controller.recovering) throw new Error(controller.error);
|
|
2500
|
+
if (controller?.error && providerNeedsAction(controller.errorCode) && (controller.checkedAt ?? 0) >= requestedAt) throw new Error(controller.error);
|
|
2141
2501
|
if (controller?.ready && compatible(controller) && (controller.checkedAt ?? 0) >= requestedAt && await processStateAlive(controller)) return computerClient(directory);
|
|
2142
2502
|
if (controller?.phase && controller.phase !== phase) {
|
|
2143
2503
|
phase = controller.phase;
|
|
@@ -2150,17 +2510,27 @@ async function ensureComputer(directory, cliPath, patch = {}, onProgress, option
|
|
|
2150
2510
|
throw new Error(`Mindwire's connection is not ready. ${lastStep}${recovering ? " Background recovery continues." : ""} Run mindwire reconnect to retry, or mindwire status for details.`);
|
|
2151
2511
|
};
|
|
2152
2512
|
if (current) {
|
|
2153
|
-
|
|
2154
|
-
|
|
2513
|
+
const changed = Object.keys(patch).filter((key) => JSON.stringify(config[key]) !== JSON.stringify(previous?.[key]));
|
|
2514
|
+
if (changed.length) {
|
|
2515
|
+
if (changed.some((key) => !["relay", "host"].includes(key))) {
|
|
2516
|
+
throw new Error("Changing the service's directory or listening ports requires mindwire stop when idle. Connection providers can be changed live with mindwire connection.");
|
|
2517
|
+
}
|
|
2518
|
+
const controller2 = await readJSON(path4.join(directory, "computer-controller.json"));
|
|
2519
|
+
if (!compatible(controller2) || !await processStateAlive(controller2)) {
|
|
2520
|
+
await ensureComputer(directory, cliPath, {}, onProgress, { ...options, waitForRoutes: false });
|
|
2521
|
+
}
|
|
2522
|
+
const { changeComputerConnection } = await import('./connection-change-5F4WNCYV.js');
|
|
2523
|
+
await changeComputerConnection(directory, Object.fromEntries(changed.map((key) => [key, patch[key]])), { signal: options.signal, onProgress });
|
|
2524
|
+
return computerClient(directory);
|
|
2155
2525
|
}
|
|
2156
|
-
const controller = await readJSON(
|
|
2526
|
+
const controller = await readJSON(path4.join(directory, "computer-controller.json"));
|
|
2157
2527
|
if (compatible(controller) && await processStateAlive(controller)) return waitUntilReady();
|
|
2158
2528
|
}
|
|
2159
|
-
const lockPath =
|
|
2529
|
+
const lockPath = path4.join(directory, "launch.lock");
|
|
2160
2530
|
const lock = await acquireProcessLock(lockPath, {
|
|
2161
2531
|
signal: options.signal,
|
|
2162
2532
|
onWait: async () => {
|
|
2163
|
-
const controller = await readJSON(
|
|
2533
|
+
const controller = await readJSON(path4.join(directory, "computer-controller.json"));
|
|
2164
2534
|
if (compatible(controller) && await processStateAlive(controller)) {
|
|
2165
2535
|
const saved = await readJSON(configPath);
|
|
2166
2536
|
if (Object.keys(patch).some((key) => JSON.stringify(patch[key]) !== JSON.stringify(saved?.[key]))) {
|
|
@@ -2184,13 +2554,13 @@ async function ensureComputer(directory, cliPath, patch = {}, onProgress, option
|
|
|
2184
2554
|
if (Object.keys(patch).some((key) => JSON.stringify(patch[key]) !== JSON.stringify(saved?.[key]))) {
|
|
2185
2555
|
throw new Error("Another start used different connection settings. Stop Mindwire when idle before changing them.");
|
|
2186
2556
|
}
|
|
2187
|
-
const controller2 = await readJSON(
|
|
2557
|
+
const controller2 = await readJSON(path4.join(directory, "computer-controller.json"));
|
|
2188
2558
|
if (compatible(controller2) && await processStateAlive(controller2)) return waitUntilReady();
|
|
2189
2559
|
}
|
|
2190
|
-
let controller = await readJSON(
|
|
2560
|
+
let controller = await readJSON(path4.join(directory, "computer-controller.json"));
|
|
2191
2561
|
if (existing && controller && !compatible(controller) && await processStateAlive(controller)) {
|
|
2192
2562
|
onProgress?.("Refreshing the background connection service\u2026");
|
|
2193
|
-
const { handoffController } = await import('./controller-handoff-
|
|
2563
|
+
const { handoffController } = await import('./controller-handoff-KV7WRDAI.js');
|
|
2194
2564
|
await handoffController(directory, controller, config, await existing.computer.info());
|
|
2195
2565
|
controller = void 0;
|
|
2196
2566
|
}
|
|
@@ -2198,20 +2568,20 @@ async function ensureComputer(directory, cliPath, patch = {}, onProgress, option
|
|
|
2198
2568
|
if (controller?.error && !controller.recovering) throw new Error(controller.error);
|
|
2199
2569
|
} else {
|
|
2200
2570
|
await writeJSON(configPath, config);
|
|
2201
|
-
const log = await
|
|
2571
|
+
const log = await fs2.open(path4.join(directory, "computer.log"), "a", 384);
|
|
2202
2572
|
try {
|
|
2203
2573
|
const child = spawn(process.execPath, [cliPath, "_serve", "--state-dir", directory], {
|
|
2204
2574
|
detached: true,
|
|
2205
2575
|
stdio: ["ignore", log.fd, log.fd],
|
|
2206
2576
|
windowsHide: true
|
|
2207
2577
|
});
|
|
2208
|
-
await new Promise((
|
|
2209
|
-
child.once("spawn",
|
|
2578
|
+
await new Promise((resolve3, reject) => {
|
|
2579
|
+
child.once("spawn", resolve3);
|
|
2210
2580
|
child.once("error", reject);
|
|
2211
2581
|
});
|
|
2212
2582
|
launchedPID = child.pid;
|
|
2213
2583
|
child.on("exit", () => {
|
|
2214
|
-
launchFailure = new Error(`The computer controller stopped. Inspect ${
|
|
2584
|
+
launchFailure = new Error(`The computer controller stopped. Inspect ${path4.join(directory, "computer.log")}.`);
|
|
2215
2585
|
});
|
|
2216
2586
|
child.unref();
|
|
2217
2587
|
} finally {
|
|
@@ -2224,20 +2594,20 @@ async function ensureComputer(directory, cliPath, patch = {}, onProgress, option
|
|
|
2224
2594
|
}
|
|
2225
2595
|
}
|
|
2226
2596
|
async function superviseComputer(directory) {
|
|
2227
|
-
const controller = await import('./controller-
|
|
2597
|
+
const controller = await import('./controller-3SZI7TBW.js');
|
|
2228
2598
|
await controller.superviseComputer(directory);
|
|
2229
2599
|
}
|
|
2230
2600
|
async function stopComputer(directory, force = false) {
|
|
2231
2601
|
const client = await computerClient(directory);
|
|
2232
|
-
const controller = await readJSON(
|
|
2602
|
+
const controller = await readJSON(path4.join(directory, "computer-controller.json"));
|
|
2233
2603
|
if (!await processStateAlive(controller)) throw new Error("The Mindwire controller is not running.");
|
|
2234
2604
|
const lease = force ? void 0 : await client.service.acquireUpdate();
|
|
2235
2605
|
try {
|
|
2236
|
-
await writeJSON(
|
|
2606
|
+
await writeJSON(path4.join(directory, "computer-stopped.json"), { stopped: true });
|
|
2237
2607
|
if (!await processStateAlive(controller)) throw new Error("The Mindwire controller changed. Retry the command.");
|
|
2238
2608
|
process.kill(controller.pid, "SIGTERM");
|
|
2239
2609
|
} catch (error) {
|
|
2240
|
-
await
|
|
2610
|
+
await fs2.rm(path4.join(directory, "computer-stopped.json"), { force: true });
|
|
2241
2611
|
if (lease) await client.service.releaseUpdate(lease.id);
|
|
2242
2612
|
throw error;
|
|
2243
2613
|
}
|
|
@@ -2248,6 +2618,6 @@ async function stopComputer(directory, force = false) {
|
|
|
2248
2618
|
throw new Error("Mindwire is still stopping. Check computer.log.");
|
|
2249
2619
|
}
|
|
2250
2620
|
|
|
2251
|
-
export { CONTROLLER_PROTOCOL, SDK_VERSION, acquireProcessLock, adoptProcess, computerClient, computerPairingQRData, computerPairingURI, currentProcessIdentity, defaultStateDirectory, directRoutes, ensureComputer, ensureDaemonBinary, ownChild, processIdentity, processStateAlive, readJSON, stopComputer, superviseComputer, writeJSON };
|
|
2252
|
-
//# sourceMappingURL=chunk-
|
|
2253
|
-
//# sourceMappingURL=chunk-
|
|
2621
|
+
export { CONTROLLER_PROTOCOL, ProviderConnectionError, RelayOutput, SDK_VERSION, acquireProcessLock, adoptProcess, adoptRelay, cloudflareCommand, computerClient, computerPairingQRData, computerPairingURI, currentProcessIdentity, defaultComputerConfig, defaultStateDirectory, directRoutes, ensureComputer, ensureDaemonBinary, hasCertificateError, ownChild, processIdentity, processStateAlive, providerFailure, providerNeedsAction, readJSON, startRelay, stopComputer, superviseComputer, websocketURL, writeJSON };
|
|
2622
|
+
//# sourceMappingURL=chunk-CKTVMUS4.js.map
|
|
2623
|
+
//# sourceMappingURL=chunk-CKTVMUS4.js.map
|