mindwire 0.1.25 → 0.1.26

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.
@@ -0,0 +1,2021 @@
1
+ import { MindwireError, TimeoutError, ApiError, RunFailedError } from './chunk-T6TD76UA.js';
2
+ import { execFile, spawn } from 'child_process';
3
+ import * as fs from 'fs/promises';
4
+ import { readFile } from 'fs/promises';
5
+ import { promisify } from 'util';
6
+ import { setTimeout as setTimeout$1 } from 'timers/promises';
7
+ import { randomUUID } from 'crypto';
8
+ import { homedir, networkInterfaces } from 'os';
9
+ import * as path from 'path';
10
+
11
+ // src/computer.ts
12
+ var ComputerApi = class {
13
+ constructor(client) {
14
+ this.client = client;
15
+ }
16
+ client;
17
+ info() {
18
+ return this.client.http.request("GET", "/computer");
19
+ }
20
+ setRoutes(routes) {
21
+ return this.client.http.request("PUT", "/computer/routes", { body: { routes } });
22
+ }
23
+ updateStatus() {
24
+ return this.client.http.request("GET", "/computer/update");
25
+ }
26
+ requestUpdate(version) {
27
+ return this.client.http.request("POST", "/computer/update", { body: { version } });
28
+ }
29
+ invite(routes) {
30
+ return this.client.http.request("POST", "/computer/pairings", { body: { routes } });
31
+ }
32
+ pairing(id) {
33
+ return this.client.http.request("GET", `/computer/pairings/${encodeURIComponent(id)}`);
34
+ }
35
+ completePairing(id, requestId) {
36
+ return this.client.http.request("POST", `/computer/pairings/${encodeURIComponent(id)}/complete`, { body: { requestId } });
37
+ }
38
+ decide(id, requestId, approve) {
39
+ return this.client.http.request("POST", `/computer/pairings/${encodeURIComponent(id)}/decision`, { body: { requestId, approve } });
40
+ }
41
+ devices() {
42
+ return this.client.http.request("GET", "/computer/devices");
43
+ }
44
+ revoke(id) {
45
+ return this.client.http.request("DELETE", `/computer/devices/${encodeURIComponent(id)}`);
46
+ }
47
+ forwards() {
48
+ return this.client.http.request("GET", "/computer/forwards");
49
+ }
50
+ /** Authorize a loopback port for this paired device. Renew the same ID every 30s;
51
+ * grants expire after two minutes. Carry traffic using SSH direct-tcpip. */
52
+ forward(request) {
53
+ return this.client.http.request("POST", "/computer/forwards", { body: request });
54
+ }
55
+ closeForward(id) {
56
+ return this.client.http.request("DELETE", `/computer/forwards/${encodeURIComponent(id)}`);
57
+ }
58
+ };
59
+ function computerPairingQRData(invitation) {
60
+ return JSON.stringify(invitation);
61
+ }
62
+ function computerPairingURI(invitation) {
63
+ const bytes = new TextEncoder().encode(computerPairingQRData(invitation));
64
+ let binary = "";
65
+ for (const byte of bytes) binary += String.fromCharCode(byte);
66
+ const encoded = btoa(binary).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
67
+ return `mindwire://pair?v=1#${encoded}`;
68
+ }
69
+
70
+ // src/version.ts
71
+ var SDK_VERSION = "0.1.26" ;
72
+
73
+ // src/daemon-binary.ts
74
+ function supported(platform, arch) {
75
+ if (!["darwin", "linux", "win32"].includes(platform) || !["x64", "arm64"].includes(arch)) {
76
+ throw new MindwireError(`mindwire: no daemon release for ${platform}-${arch}`);
77
+ }
78
+ }
79
+ function assetName(version, platform, arch) {
80
+ const releasePlatform = platform === "win32" ? "windows" : platform;
81
+ const releaseArch = arch === "x64" ? "amd64" : arch;
82
+ return `mindwired-v${version}-${releasePlatform}-${releaseArch}${platform === "win32" ? ".exe" : ""}`;
83
+ }
84
+ function checksumFor(text, asset) {
85
+ const line = text.split("\n").find((l) => l.trim().endsWith(` ${asset}`) || l.trim().endsWith(` ${asset}`));
86
+ const hash = line?.trim().split(/\s+/)[0];
87
+ return hash && /^[a-f0-9]{64}$/i.test(hash) ? hash.toLowerCase() : void 0;
88
+ }
89
+ async function ensureDaemonBinary(opts = {}) {
90
+ const proc = globalThis;
91
+ const platform = opts.platform ?? proc.process?.platform;
92
+ const arch = opts.arch ?? proc.process?.arch;
93
+ supported(platform ?? "unknown", arch ?? "unknown");
94
+ const daemonPlatform = platform;
95
+ const daemonArch = arch;
96
+ const version = opts.version ?? SDK_VERSION;
97
+ if (!/^[0-9]+\.[0-9]+\.[0-9]+(?:-[0-9A-Za-z.-]+)?$/.test(version)) {
98
+ throw new MindwireError(`mindwire: cannot download daemon for non-release SDK version ${version}`);
99
+ }
100
+ const path2 = await import('path');
101
+ const { binaryCacheDirectory, cachedExecutable, cacheExecutable } = await import('./binary-cache-VM7VHCEU.js');
102
+ const dir = path2.join(opts.cacheDir ?? await binaryCacheDirectory(daemonPlatform), version, `${daemonPlatform}-${daemonArch}`);
103
+ const asset = assetName(version, daemonPlatform, daemonArch);
104
+ const bin = path2.join(dir, asset);
105
+ const cached = await cachedExecutable(bin);
106
+ if (cached) return cached;
107
+ const base = (opts.releaseBaseUrl ?? proc.process?.env?.MINDWIRE_RELEASE_BASE_URL ?? "https://github.com/oblien/mindwire/releases/download").replace(/\/$/, "");
108
+ const release = `${base}/v${version}`;
109
+ const request = opts.fetch ?? globalThis.fetch;
110
+ if (!request) throw new MindwireError("mindwire: fetch is unavailable; set daemonBin or MINDWIRE_DAEMON");
111
+ const [checksums, binary] = await Promise.all([request(`${release}/checksums.txt`), request(`${release}/${asset}`)]);
112
+ if (!checksums.ok || !binary.ok) {
113
+ if (!opts.releaseBaseUrl && !proc.process?.env?.MINDWIRE_RELEASE_BASE_URL) {
114
+ const latest = await request("https://api.github.com/repos/oblien/mindwire/releases/latest");
115
+ if (latest.ok) {
116
+ const tag = (await latest.json()).tag_name;
117
+ const fallback = typeof tag === "string" ? tag.replace(/^v/, "") : "";
118
+ if (/^[0-9]+\.[0-9]+\.[0-9]+(?:-[0-9A-Za-z.-]+)?$/.test(fallback) && fallback !== version) {
119
+ return ensureDaemonBinary({ ...opts, version: fallback });
120
+ }
121
+ }
122
+ }
123
+ throw new MindwireError(`mindwire: failed to download daemon v${version} for ${daemonPlatform}-${daemonArch}`);
124
+ }
125
+ const expected = checksumFor(await checksums.text(), asset);
126
+ if (!expected) throw new MindwireError(`mindwire: release v${version} has no checksum for ${asset}`);
127
+ const bytes = new Uint8Array(await binary.arrayBuffer());
128
+ return cacheExecutable(bin, bytes, expected);
129
+ }
130
+ var execute = promisify(execFile);
131
+ function processAlive(pid) {
132
+ if (!pid || !Number.isSafeInteger(pid) || pid < 1) return false;
133
+ try {
134
+ process.kill(pid, 0);
135
+ return true;
136
+ } catch {
137
+ return false;
138
+ }
139
+ }
140
+ async function processIdentity(pid) {
141
+ if (!processAlive(pid)) return void 0;
142
+ try {
143
+ let start;
144
+ if (process.platform === "linux") {
145
+ const stat2 = await readFile(`/proc/${pid}/stat`, "utf8");
146
+ const fields = stat2.slice(stat2.lastIndexOf(")") + 2).split(" ");
147
+ const boot = (await readFile("/proc/sys/kernel/random/boot_id", "utf8")).trim();
148
+ start = `${boot}:${fields[19]}`;
149
+ } else if (process.platform === "win32") {
150
+ const result = await execute("powershell.exe", [
151
+ "-NoProfile",
152
+ "-NonInteractive",
153
+ "-Command",
154
+ `(Get-Process -Id ${pid} -ErrorAction Stop).StartTime.ToUniversalTime().Ticks`
155
+ ], { timeout: 5e3, windowsHide: true });
156
+ start = result.stdout.trim();
157
+ } else {
158
+ const result = await execute("/bin/ps", ["-p", String(pid), "-o", "lstart=", "-o", "comm="], {
159
+ timeout: 3e3,
160
+ env: { ...process.env, LC_ALL: "C", TZ: "UTC" }
161
+ });
162
+ start = result.stdout.trim();
163
+ }
164
+ return start ? { pid, start } : void 0;
165
+ } catch {
166
+ return void 0;
167
+ }
168
+ }
169
+ async function currentProcessIdentity() {
170
+ const identity = await processIdentity(process.pid);
171
+ if (!identity) throw new Error("Couldn't verify the Mindwire process identity. Retry the command.");
172
+ return identity;
173
+ }
174
+ async function processStateAlive(state) {
175
+ if (!state || !processAlive(state.pid)) return false;
176
+ if (state.start === void 0) return true;
177
+ const current = await processIdentity(state.pid);
178
+ if (!current && processAlive(state.pid)) throw new Error("Couldn't verify the running Mindwire process. Retry the command.");
179
+ return !!state.start && current?.start === state.start;
180
+ }
181
+ async function readProcessState(file) {
182
+ try {
183
+ const state = JSON.parse(await readFile(file, "utf8"));
184
+ if (typeof state === "number") return { pid: state };
185
+ return state && typeof state.pid === "number" ? state : void 0;
186
+ } catch (error) {
187
+ if (error instanceof SyntaxError || error.code === "ENOENT") return void 0;
188
+ throw error;
189
+ }
190
+ }
191
+ async function ownChild(child) {
192
+ if (!child.pid) throw new Error("The background process could not start.");
193
+ const pid = child.pid;
194
+ const identity = await processIdentity(pid);
195
+ const alive = () => child.exitCode === null && child.signalCode === null;
196
+ return { pid, identity, alive, async close() {
197
+ if (!alive()) return;
198
+ const exited = new Promise((resolve) => child.once("exit", () => resolve()));
199
+ child.kill("SIGTERM");
200
+ const timer = setTimeout(() => {
201
+ if (alive()) child.kill("SIGKILL");
202
+ }, 15e3);
203
+ try {
204
+ await exited;
205
+ } finally {
206
+ clearTimeout(timer);
207
+ }
208
+ } };
209
+ }
210
+ async function adoptProcess(identity) {
211
+ const matches = async () => (await processIdentity(identity.pid))?.start === identity.start;
212
+ if (!await matches()) return void 0;
213
+ return { pid: identity.pid, identity, alive: () => processAlive(identity.pid), async close() {
214
+ if (!await matches()) return;
215
+ process.kill(identity.pid, "SIGTERM");
216
+ for (let attempt = 0; attempt < 60; attempt++) {
217
+ if (!processAlive(identity.pid)) return;
218
+ await setTimeout$1(250);
219
+ }
220
+ if (await matches()) process.kill(identity.pid, "SIGKILL");
221
+ } };
222
+ }
223
+
224
+ // src/http.ts
225
+ var DEFAULT_TIMEOUT_MS = 12e4;
226
+ function isAbortError(e) {
227
+ return typeof e === "object" && e !== null && e.name === "AbortError";
228
+ }
229
+ var Http = class {
230
+ fetchImpl;
231
+ baseHeaders;
232
+ resolver;
233
+ timeoutMs;
234
+ cached = null;
235
+ pending = null;
236
+ constructor(opts) {
237
+ const f = opts.fetch ?? globalThis.fetch;
238
+ if (!f) {
239
+ throw new MindwireError(
240
+ "mindwire: no global fetch found \u2014 pass a `fetch` implementation in the client options"
241
+ );
242
+ }
243
+ this.fetchImpl = f;
244
+ this.baseHeaders = { ...opts.headers };
245
+ this.timeoutMs = opts.timeoutMs ?? DEFAULT_TIMEOUT_MS;
246
+ if (opts.baseUrl) {
247
+ const base = { baseUrl: opts.baseUrl.replace(/\/+$/, ""), token: opts.token };
248
+ this.resolver = async () => base;
249
+ } else if (opts.resolveBase) {
250
+ const rb = opts.resolveBase;
251
+ this.resolver = async () => {
252
+ const r = await rb();
253
+ return {
254
+ baseUrl: r.baseUrl.replace(/\/+$/, ""),
255
+ token: r.token ?? opts.token,
256
+ getToken: r.getToken,
257
+ headers: r.headers,
258
+ fetch: r.fetch
259
+ };
260
+ };
261
+ } else {
262
+ throw new MindwireError("mindwire: Http requires a baseUrl or a resolveBase");
263
+ }
264
+ }
265
+ /**
266
+ * One fetch, with raw network failures wrapped as {@link MindwireError} so callers see a typed
267
+ * SDK error instead of a bare `TypeError: fetch failed`. An abort (the request's own signal, or
268
+ * our timeout controller) is re-thrown untouched — `fetchWithTimeout` classifies it.
269
+ */
270
+ async fetchOnce(f, url, init, method, path2) {
271
+ try {
272
+ return await f(url, init);
273
+ } catch (e) {
274
+ const aborted = init.signal?.aborted ?? false;
275
+ if (aborted || isAbortError(e)) throw e;
276
+ throw new MindwireError(`mindwire: ${method} ${path2} \u2014 network request failed`, { cause: e });
277
+ }
278
+ }
279
+ /**
280
+ * `fetchOnce` plus a timeout that composes with the caller's `signal`. A hand-rolled controller
281
+ * (not `AbortSignal.timeout`/`AbortSignal.any`, which need Node 20.3+/18.17+) fires after
282
+ * `timeoutMs`; whichever aborts first wins. Timeout → {@link TimeoutError}; the caller's own abort
283
+ * propagates as-is (its `reason`). `timeoutMs<=0` disables the deadline entirely.
284
+ */
285
+ async fetchWithTimeout(f, url, init, userSignal, method, path2) {
286
+ if (!this.timeoutMs || this.timeoutMs <= 0) {
287
+ return this.fetchOnce(f, url, { ...init, ...userSignal ? { signal: userSignal } : {} }, method, path2);
288
+ }
289
+ const ctrl = new AbortController();
290
+ const onAbort = () => ctrl.abort(userSignal?.reason);
291
+ if (userSignal) {
292
+ if (userSignal.aborted) ctrl.abort(userSignal.reason);
293
+ else userSignal.addEventListener("abort", onAbort, { once: true });
294
+ }
295
+ let timedOut = false;
296
+ const timer = setTimeout(() => {
297
+ timedOut = true;
298
+ ctrl.abort();
299
+ }, this.timeoutMs);
300
+ try {
301
+ return await this.fetchOnce(f, url, { ...init, signal: ctrl.signal }, method, path2);
302
+ } catch (e) {
303
+ if (timedOut) throw new TimeoutError(method, path2, this.timeoutMs);
304
+ if (userSignal?.aborted) throw userSignal.reason ?? e;
305
+ throw e;
306
+ } finally {
307
+ clearTimeout(timer);
308
+ if (userSignal) userSignal.removeEventListener("abort", onAbort);
309
+ }
310
+ }
311
+ /**
312
+ * The bearer token for the next request. Prefers the transport's dynamic `getToken` (which
313
+ * caches and mints on demand); falls back to the static token resolved at base time. `force`
314
+ * asks the getter to re-mint — used once on a 401 before retrying.
315
+ */
316
+ async authToken(base, force) {
317
+ if (base.getToken) {
318
+ const t = await base.getToken(force ? { force: true } : void 0);
319
+ return t ?? base.token;
320
+ }
321
+ return base.token;
322
+ }
323
+ base() {
324
+ if (this.cached) return Promise.resolve(this.cached);
325
+ if (!this.pending) {
326
+ this.pending = this.resolver().then((b) => this.cached = b).catch((err) => {
327
+ this.pending = null;
328
+ throw err;
329
+ });
330
+ }
331
+ return this.pending;
332
+ }
333
+ /**
334
+ * Resolve the base eagerly and memoize it — provisions the transport (spawns the embedded daemon,
335
+ * connects the sandbox/SSH/Docker target, …) now instead of on the first request. Idempotent: this
336
+ * awaits the *same* memoized promise the first `request()`/`open()` awaits, so the target's
337
+ * `connect()` fires exactly once. Backs {@link import("./client.js").Mindwire.ensure}.
338
+ */
339
+ async ready() {
340
+ await this.base();
341
+ }
342
+ url(baseUrl, path2, query) {
343
+ const u = new URL(baseUrl + (path2.startsWith("/") ? path2 : `/${path2}`));
344
+ if (query) {
345
+ for (const [k, v] of Object.entries(query)) {
346
+ if (v !== void 0) u.searchParams.set(k, String(v));
347
+ }
348
+ }
349
+ return u.toString();
350
+ }
351
+ headers(token, extra, hasBody = false) {
352
+ const h = { Accept: "application/json", ...this.baseHeaders, ...extra };
353
+ if (token) h["Authorization"] = `Bearer ${token}`;
354
+ if (hasBody) h["Content-Type"] = "application/json";
355
+ return h;
356
+ }
357
+ async request(method, path2, init = {}) {
358
+ const base = await this.base();
359
+ const hasBody = init.body !== void 0;
360
+ const url = this.url(base.baseUrl, path2, init.query);
361
+ const extra = { ...base.headers, ...init.headers };
362
+ const f = base.fetch ?? this.fetchImpl;
363
+ const send = async (force) => this.fetchWithTimeout(
364
+ f,
365
+ url,
366
+ {
367
+ method,
368
+ headers: this.headers(await this.authToken(base, force), extra, hasBody),
369
+ body: hasBody ? JSON.stringify(init.body) : void 0
370
+ },
371
+ init.signal,
372
+ method,
373
+ path2
374
+ );
375
+ let res = await send(false);
376
+ if (res.status === 401 && base.getToken) res = await send(true);
377
+ if (!res.ok) throw await this.toApiError(method, res);
378
+ if (res.status === 204) return void 0;
379
+ const text = await res.text();
380
+ if (text === "") return void 0;
381
+ try {
382
+ return JSON.parse(text);
383
+ } catch (e) {
384
+ throw new MindwireError(`mindwire: ${method} ${path2} returned a non-JSON body`, { cause: e });
385
+ }
386
+ }
387
+ /** Open a streaming response (SSE). Caller owns the body. */
388
+ async open(method, path2, init = {}) {
389
+ const base = await this.base();
390
+ const url = this.url(base.baseUrl, path2, init.query);
391
+ const extra = { Accept: "text/event-stream", ...base.headers, ...init.headers };
392
+ const f = base.fetch ?? this.fetchImpl;
393
+ const send = async (force) => this.fetchOnce(
394
+ f,
395
+ url,
396
+ {
397
+ method,
398
+ headers: this.headers(await this.authToken(base, force), extra, init.body !== void 0),
399
+ ...init.body !== void 0 ? { body: JSON.stringify(init.body) } : {},
400
+ ...init.signal ? { signal: init.signal } : {}
401
+ },
402
+ method,
403
+ path2
404
+ );
405
+ let res = await send(false);
406
+ if (res.status === 401 && base.getToken) res = await send(true);
407
+ if (!res.ok) throw await this.toApiError(method, res);
408
+ if (!res.body)
409
+ throw new MindwireError(`mindwire: ${method} ${path2} returned no response body to stream`);
410
+ return res;
411
+ }
412
+ async toApiError(method, res) {
413
+ let body = "";
414
+ try {
415
+ const text = await res.text();
416
+ try {
417
+ body = text ? JSON.parse(text) : "";
418
+ } catch {
419
+ body = text;
420
+ }
421
+ } catch {
422
+ }
423
+ return new ApiError({ status: res.status, url: res.url, method, body });
424
+ }
425
+ };
426
+
427
+ // src/sse.ts
428
+ async function* readSSE(body, signal) {
429
+ const reader = body.getReader();
430
+ const decoder = new TextDecoder();
431
+ let buffer = "";
432
+ const onAbort = () => void reader.cancel().catch(() => {
433
+ });
434
+ if (signal) {
435
+ if (signal.aborted) {
436
+ await reader.cancel().catch(() => {
437
+ });
438
+ return;
439
+ }
440
+ signal.addEventListener("abort", onAbort, { once: true });
441
+ }
442
+ try {
443
+ for (; ; ) {
444
+ const { done, value } = await reader.read();
445
+ if (done) break;
446
+ buffer += decoder.decode(value, { stream: true });
447
+ let sep;
448
+ while ((sep = indexOfBlankLine(buffer)) !== -1) {
449
+ const rawEvent = buffer.slice(0, sep);
450
+ buffer = buffer.slice(sepEnd(buffer, sep));
451
+ const payload2 = parseEvent(rawEvent);
452
+ if (payload2 !== void 0) yield JSON.parse(payload2);
453
+ }
454
+ }
455
+ const payload = parseEvent(buffer);
456
+ if (payload !== void 0) yield JSON.parse(payload);
457
+ } finally {
458
+ if (signal) signal.removeEventListener("abort", onAbort);
459
+ await reader.cancel().catch(() => {
460
+ });
461
+ reader.releaseLock();
462
+ }
463
+ }
464
+ function indexOfBlankLine(s) {
465
+ const a = s.indexOf("\n\n");
466
+ const b = s.indexOf("\r\n\r\n");
467
+ if (a === -1) return b;
468
+ if (b === -1) return a;
469
+ return Math.min(a, b);
470
+ }
471
+ function sepEnd(s, idx) {
472
+ return s.startsWith("\r\n\r\n", idx) ? idx + 4 : idx + 2;
473
+ }
474
+ function parseEvent(block) {
475
+ const lines = block.split(/\r\n|\n/);
476
+ const data = [];
477
+ for (const line of lines) {
478
+ if (line === "" || line.startsWith(":")) continue;
479
+ const colon = line.indexOf(":");
480
+ const field = colon === -1 ? line : line.slice(0, colon);
481
+ if (field !== "data") continue;
482
+ let value = colon === -1 ? "" : line.slice(colon + 1);
483
+ if (value.startsWith(" ")) value = value.slice(1);
484
+ data.push(value);
485
+ }
486
+ if (data.length === 0) return void 0;
487
+ return data.join("\n");
488
+ }
489
+
490
+ // src/run.ts
491
+ var TERMINAL = /* @__PURE__ */ new Set(["done", "error", "cancelled"]);
492
+ var Run = class _Run {
493
+ data;
494
+ http;
495
+ constructor(http, data) {
496
+ this.http = http;
497
+ this.data = data;
498
+ }
499
+ get id() {
500
+ return this.data.id;
501
+ }
502
+ get chatId() {
503
+ return this.data.chatId;
504
+ }
505
+ get agent() {
506
+ return this.data.agent;
507
+ }
508
+ get status() {
509
+ return this.data.status;
510
+ }
511
+ /** `"resolve"` on the parent of a global-resolve run; `undefined` for an ordinary turn. */
512
+ get kind() {
513
+ return this.data.kind;
514
+ }
515
+ /** On a child iteration of a resolve run, the id of its parent resolve run; else `undefined`. */
516
+ get parentId() {
517
+ return this.data.parentId;
518
+ }
519
+ /** Parent of a resolve run only: why the loop ended (`"done"` | `"capped"` | `"error"` | …). */
520
+ get stopReason() {
521
+ return this.data.stopReason;
522
+ }
523
+ /** Parent of a resolve run only: how many child turns the loop ran. */
524
+ get iterations() {
525
+ return this.data.iterations;
526
+ }
527
+ /** The latest known run record. */
528
+ get value() {
529
+ return this.data;
530
+ }
531
+ /** Unified SSE event stream: replay buffer, then live events, then close. */
532
+ async *stream(opts = {}) {
533
+ const cursor = opts.after === void 0 ? "" : `?after=${encodeURIComponent(opts.after)}`;
534
+ const res = await this.http.open("GET", `/runs/${encodeURIComponent(this.id)}/stream${cursor}`, {
535
+ ...opts.signal ? { signal: opts.signal } : {}
536
+ });
537
+ for await (const ev of readSSE(res.body, opts.signal)) {
538
+ if (!opts.includeOpenSentinel && ev.type === "status" && ev.meta?.["stream"] === "open") {
539
+ continue;
540
+ }
541
+ yield ev;
542
+ }
543
+ }
544
+ /** `for await (const ev of run)` — sugar for `run.stream()`. */
545
+ [Symbol.asyncIterator]() {
546
+ return this.stream();
547
+ }
548
+ /** Cancel the in-flight turn (kills the underlying agent process). */
549
+ async cancel() {
550
+ await this.http.request("POST", `/runs/${encodeURIComponent(this.id)}/cancel`);
551
+ }
552
+ /**
553
+ * Answer a pending permission, question or plan. Message-mode questions remain answerable
554
+ * after completion; the daemon steers or resumes the conversation. Read the chat's latest
555
+ * run after replying to a completed run. Requires the agent's `respond` capability.
556
+ */
557
+ async respond(input = {}) {
558
+ await this.http.request("POST", `/runs/${encodeURIComponent(this.id)}/respond`, { body: input });
559
+ }
560
+ /**
561
+ * Steer a follow-up message into the running turn without cancelling it. Requires the agent's
562
+ * `input` capability.
563
+ */
564
+ async sendInput(text) {
565
+ await this.http.request("POST", `/runs/${encodeURIComponent(this.id)}/input`, { body: { text } });
566
+ }
567
+ /**
568
+ * Soft-stop the running turn (ask the agent to halt current work) without the hard process kill
569
+ * {@link Run.cancel} does — the turn stays open for a follow-up via {@link Run.sendInput}.
570
+ * Requires the agent's `interrupt` capability.
571
+ */
572
+ async interrupt() {
573
+ await this.http.request("POST", `/runs/${encodeURIComponent(this.id)}/interrupt`);
574
+ }
575
+ /**
576
+ * Switch the model of the live turn. An empty/omitted `model` resets the turn to the agent/CLI
577
+ * default. Only meaningful on a persistent (non-bypass) turn; on a one-shot turn it is a
578
+ * best-effort no-op. Requires the agent's `setModel` capability.
579
+ */
580
+ async setModel(model) {
581
+ await this.http.request("POST", `/runs/${encodeURIComponent(this.id)}/set-model`, {
582
+ body: { model: model ?? "" }
583
+ });
584
+ }
585
+ /**
586
+ * Switch the live permission mode using a value from the agent's settings schema.
587
+ * Resolves after the harness acknowledges the change; rejects if it cannot apply it.
588
+ * Requires `setPermissionMode`. Codex settings apply on the next turn instead.
589
+ */
590
+ async setPermissionMode(mode) {
591
+ await this.http.request("POST", `/runs/${encodeURIComponent(this.id)}/set-permission-mode`, {
592
+ body: { mode }
593
+ });
594
+ }
595
+ /**
596
+ * `GET /runs/{id}/children` — the child iterations of a global-resolve run, oldest→newest, each as
597
+ * its own {@link Run} handle. An ordinary turn (or a resolve that ran a single iteration) returns an
598
+ * empty array. Use it to inspect the run tree a {@link Mindwire.resolve} produced.
599
+ */
600
+ async children() {
601
+ const data = await this.http.request(
602
+ "GET",
603
+ `/runs/${encodeURIComponent(this.id)}/children`
604
+ );
605
+ return data.map((d) => new _Run(this.http, d));
606
+ }
607
+ /** Re-fetch the run record from the daemon and update this handle. */
608
+ async refresh() {
609
+ this.data = await this.http.request("GET", `/runs/${encodeURIComponent(this.id)}`);
610
+ return this.data;
611
+ }
612
+ /** Restore current output once, then follow with `stream({ after: snapshot.sequence })`. */
613
+ async snapshot() {
614
+ const snapshot = await this.http.request("GET", `/runs/${encodeURIComponent(this.id)}/snapshot`);
615
+ this.data = snapshot.run;
616
+ return snapshot;
617
+ }
618
+ /**
619
+ * Consume the event stream to completion. Returns the final run record and the `result`
620
+ * event's summary (if any). Throws {@link RunFailedError} on an `error`/`cancelled` outcome
621
+ * unless `throwOnError` is set to `false`.
622
+ */
623
+ async wait(opts = {}) {
624
+ let result;
625
+ let streamError;
626
+ for await (const ev of this.stream(opts)) {
627
+ if (ev.type === "result") result = ev.result;
628
+ else if (ev.type === "error") streamError = ev.error;
629
+ }
630
+ const run = await this.refresh();
631
+ if (opts.throwOnError !== false) {
632
+ if (TERMINAL.has(run.status)) {
633
+ if (run.status !== "done") {
634
+ throw new RunFailedError(run.id, run.status, run.error ?? streamError);
635
+ }
636
+ } else {
637
+ throw new RunFailedError(
638
+ run.id,
639
+ run.status,
640
+ streamError ?? "event stream ended before the run reached a terminal state"
641
+ );
642
+ }
643
+ }
644
+ return result !== void 0 ? { run, result } : { run };
645
+ }
646
+ };
647
+
648
+ // src/workspace.ts
649
+ var ProjectOperationsApi = class {
650
+ constructor(mw) {
651
+ this.mw = mw;
652
+ }
653
+ mw;
654
+ async list(activeOnly = false) {
655
+ const response = await this.mw.http.request("GET", "/workspace/operations", {
656
+ query: { active: activeOnly }
657
+ });
658
+ return response.operations;
659
+ }
660
+ get(id) {
661
+ return this.mw.http.request("GET", `/workspace/operations/${encodeURIComponent(id)}`);
662
+ }
663
+ cancel(id) {
664
+ return this.mw.http.request("POST", `/workspace/operations/${encodeURIComponent(id)}/cancel`);
665
+ }
666
+ retry(id, auth) {
667
+ return this.mw.http.request("POST", `/workspace/operations/${encodeURIComponent(id)}/retry`, { body: { auth } });
668
+ }
669
+ /** The first event is the current snapshot, then live changes. Reconnecting never replays old
670
+ * progress. Breaking the loop/aborting detaches the observer; cancel(id) explicitly stops work.
671
+ */
672
+ async *watch(id, opts = {}) {
673
+ const controller = new AbortController();
674
+ const abort = () => controller.abort();
675
+ opts.signal?.addEventListener("abort", abort, { once: true });
676
+ if (opts.signal?.aborted) controller.abort();
677
+ try {
678
+ const response = await this.mw.http.open("GET", `/workspace/operations/${encodeURIComponent(id)}/stream`, {
679
+ signal: controller.signal
680
+ });
681
+ let sequence = -1;
682
+ for await (const operation of readSSE(response.body, controller.signal)) {
683
+ if (operation.sequence > sequence) {
684
+ sequence = operation.sequence;
685
+ yield operation;
686
+ }
687
+ }
688
+ } finally {
689
+ controller.abort();
690
+ opts.signal?.removeEventListener("abort", abort);
691
+ }
692
+ }
693
+ };
694
+ var WorkspaceCollection = class {
695
+ constructor(mw, kind) {
696
+ this.mw = mw;
697
+ this.kind = kind;
698
+ }
699
+ mw;
700
+ kind;
701
+ /** Create with a stable client-generated ID. For updates, supply the record's last revision. */
702
+ put(id, record, expectedRevision) {
703
+ return this.mw.http.request("PUT", `/workspace/${this.kind}/${encodeURIComponent(id)}`, {
704
+ body: { record, expectedRevision }
705
+ });
706
+ }
707
+ /** Remove membership and dependent chat links. Files and native transcripts are retained.
708
+ * Use deleteChat() for an explicit transcript purge. Running chats reject removal with 409.
709
+ */
710
+ delete(id, revision) {
711
+ return this.mw.http.request("DELETE", `/workspace/${this.kind}/${encodeURIComponent(id)}`, {
712
+ query: { revision }
713
+ });
714
+ }
715
+ };
716
+ var WorkspaceApi = class {
717
+ constructor(mw) {
718
+ this.mw = mw;
719
+ this.git = new GitAccessApi(mw);
720
+ this.operations = new ProjectOperationsApi(mw);
721
+ this.agents = new WorkspaceCollection(mw, "agents");
722
+ this.projects = new WorkspaceCollection(mw, "projects");
723
+ this.chats = new WorkspaceCollection(mw, "chats");
724
+ }
725
+ mw;
726
+ git;
727
+ operations;
728
+ agents;
729
+ projects;
730
+ chats;
731
+ snapshot(options = {}) {
732
+ return this.mw.http.request("GET", "/workspace", { query: options });
733
+ }
734
+ /** Start an operation owned by the daemon. The same ID/payload returns the existing operation. */
735
+ createProject(request) {
736
+ return this.mw.http.request("POST", "/workspace/projects", { body: request });
737
+ }
738
+ /** Permanently remove the confirmed project's directory and membership.
739
+ * projects.delete() retains files. Native harness transcripts are not purged.
740
+ */
741
+ removeProjectFiles(id, request) {
742
+ return this.mw.http.request("POST", `/workspace/projects/${encodeURIComponent(id)}/remove`, { body: request });
743
+ }
744
+ /** Incremental reconciliation. Pass the previous identity to detect a replaced/restored workspace.
745
+ * A 409 requires fetching snapshot() again; never apply a delta to a different registry.
746
+ */
747
+ changes(since, workspaceId, options = {}) {
748
+ return this.mw.http.request("GET", "/workspace/changes", { query: { since, workspaceId, ...options } });
749
+ }
750
+ /** Import legacy metadata before replacing a local cache. Safe to repeat after interruption. */
751
+ import(records) {
752
+ return this.mw.http.request("POST", "/workspace/import", { body: records });
753
+ }
754
+ };
755
+ var GitAccessApi = class {
756
+ constructor(mw) {
757
+ this.mw = mw;
758
+ }
759
+ mw;
760
+ /** Reads attribution independently of GitHub authentication. Requires gitIdentityVersion >= 1. */
761
+ identity(context = {}) {
762
+ return this.mw.http.request("GET", "/workspace/git/identity", { query: context });
763
+ }
764
+ /** Saves settings only; never stages or commits. all_workspaces installs this
765
+ * workspace's copy of a client-managed default; the client handles fan-out. */
766
+ setIdentity(update, context = {}) {
767
+ return this.mw.http.request("PUT", "/workspace/git/identity", { query: context, body: update });
768
+ }
769
+ state() {
770
+ return this.mw.http.request("GET", "/workspace/git");
771
+ }
772
+ setDefault(connection, auth) {
773
+ return this.mw.http.request("PUT", "/workspace/git", { body: { connection, auth } });
774
+ }
775
+ forget(connectionId) {
776
+ return this.mw.http.request("DELETE", `/workspace/git/connections/${encodeURIComponent(connectionId)}`);
777
+ }
778
+ project(projectId) {
779
+ return this.mw.http.request("GET", `/workspace/projects/${encodeURIComponent(projectId)}/git`);
780
+ }
781
+ setProject(projectId, connection, expectedRevision, auth) {
782
+ return this.mw.http.request("PUT", `/workspace/projects/${encodeURIComponent(projectId)}/git`, {
783
+ body: { connection, expectedRevision, auth }
784
+ });
785
+ }
786
+ /** Compatibility call. Prefer start() and operation()/watch() for reconnectable writes. */
787
+ run(projectId, operation, auth) {
788
+ return this.mw.http.request("POST", `/workspace/projects/${encodeURIComponent(projectId)}/git/${operation}`, { body: { auth } });
789
+ }
790
+ /** Requires health.gitOperationsVersion >= 1 (>= 2 for branches, >= 4 for restore_commit). Acceptance persists before Git runs;
791
+ * disconnecting only detaches the client. The same ID/intent never runs twice.
792
+ */
793
+ start(projectId, request) {
794
+ return this.mw.http.request("POST", `/workspace/projects/${encodeURIComponent(projectId)}/git/operations`, { body: request });
795
+ }
796
+ async operations(projectId, activeOnly = false) {
797
+ const result = await this.mw.http.request(
798
+ "GET",
799
+ `/workspace/projects/${encodeURIComponent(projectId)}/git/operations`,
800
+ { query: { active: activeOnly, actionsVersion: 4 } }
801
+ );
802
+ return result.operations;
803
+ }
804
+ operation(id) {
805
+ return this.mw.http.request("GET", `/workspace/git/operations/${encodeURIComponent(id)}`);
806
+ }
807
+ cancel(id) {
808
+ return this.mw.http.request("POST", `/workspace/git/operations/${encodeURIComponent(id)}/cancel`);
809
+ }
810
+ /** Current snapshot followed by state changes. Aborting observation never cancels the operation. */
811
+ async *watch(id, opts = {}) {
812
+ const controller = new AbortController();
813
+ const abort = () => controller.abort();
814
+ opts.signal?.addEventListener("abort", abort, { once: true });
815
+ if (opts.signal?.aborted) controller.abort();
816
+ try {
817
+ const response = await this.mw.http.open("GET", `/workspace/git/operations/${encodeURIComponent(id)}/stream`, {
818
+ signal: controller.signal
819
+ });
820
+ let sequence = -1;
821
+ for await (const operation of readSSE(response.body, controller.signal)) {
822
+ if (operation.sequence > sequence) {
823
+ sequence = operation.sequence;
824
+ yield operation;
825
+ }
826
+ }
827
+ } finally {
828
+ controller.abort();
829
+ opts.signal?.removeEventListener("abort", abort);
830
+ }
831
+ }
832
+ };
833
+
834
+ // src/surfaces.ts
835
+ var SurfacesApi = class {
836
+ constructor(mw) {
837
+ this.mw = mw;
838
+ }
839
+ mw;
840
+ list() {
841
+ return this.mw.http.request("GET", "/surfaces");
842
+ }
843
+ status(refresh = false) {
844
+ return this.mw.http.request("GET", "/surfaces/desktop", { query: { refresh } });
845
+ }
846
+ bind(binding) {
847
+ return this.mw.http.request("PUT", "/surfaces/desktop/binding", { body: binding });
848
+ }
849
+ open(request) {
850
+ return this.mw.http.request("POST", "/surfaces/desktop/sessions", { body: request });
851
+ }
852
+ control(id, request) {
853
+ return this.mw.http.request("POST", `/surfaces/desktop/sessions/${encodeURIComponent(id)}/control`, { body: request });
854
+ }
855
+ close(id) {
856
+ return this.mw.http.request("DELETE", `/surfaces/desktop/sessions/${encodeURIComponent(id)}`);
857
+ }
858
+ capture(id) {
859
+ return this.mw.http.request("POST", `/surfaces/desktop/sessions/${encodeURIComponent(id)}/captures`);
860
+ }
861
+ action(request) {
862
+ return this.mw.http.request("POST", "/surfaces/desktop/actions", { body: request });
863
+ }
864
+ receipt(id) {
865
+ return this.mw.http.request("GET", `/surfaces/desktop/actions/${encodeURIComponent(id)}`);
866
+ }
867
+ artifact(id) {
868
+ return this.mw.http.request("GET", `/artifacts/${encodeURIComponent(id)}`);
869
+ }
870
+ /** Every connection starts with the current snapshot, then revisions. Never replays input. */
871
+ async *watch(opts = {}) {
872
+ const controller = new AbortController();
873
+ const abort = () => controller.abort();
874
+ opts.signal?.addEventListener("abort", abort, { once: true });
875
+ if (opts.signal?.aborted) controller.abort();
876
+ try {
877
+ const response = await this.mw.http.open("GET", "/surfaces/desktop/events", { signal: controller.signal });
878
+ let instance;
879
+ let revision = -1;
880
+ for await (const snapshot of readSSE(response.body, controller.signal)) {
881
+ if (instance !== snapshot.instanceId || snapshot.revision > revision) {
882
+ instance = snapshot.instanceId;
883
+ revision = snapshot.revision;
884
+ yield snapshot;
885
+ }
886
+ }
887
+ } finally {
888
+ controller.abort();
889
+ opts.signal?.removeEventListener("abort", abort);
890
+ }
891
+ }
892
+ };
893
+
894
+ // src/service.ts
895
+ var ServiceApi = class {
896
+ constructor(client) {
897
+ this.client = client;
898
+ }
899
+ client;
900
+ updateStatus() {
901
+ return this.client.http.request("GET", "/service/update");
902
+ }
903
+ /** Download first. Acquire immediately before replacement; busy work returns 409.
904
+ * Admission stays closed until release, process exit, or the one-minute expiry. */
905
+ acquireUpdate() {
906
+ return this.client.http.request("POST", "/service/update");
907
+ }
908
+ releaseUpdate(id) {
909
+ return this.client.http.request("DELETE", `/service/update/${encodeURIComponent(id)}`);
910
+ }
911
+ };
912
+
913
+ // src/execution.ts
914
+ var ExecutionApi = class {
915
+ constructor(client) {
916
+ this.client = client;
917
+ this.terminals = new TerminalsApi(client);
918
+ }
919
+ client;
920
+ terminals;
921
+ host() {
922
+ return this.client.http.request("GET", "/workspace/host");
923
+ }
924
+ resources() {
925
+ return this.client.http.request("GET", "/workspace/resources");
926
+ }
927
+ files(path2 = "", query) {
928
+ return this.client.http.request("GET", "/workspace/files", { query: { path: path2, query } });
929
+ }
930
+ read(path2) {
931
+ return this.client.http.request("GET", "/workspace/file", { query: { path: path2 } });
932
+ }
933
+ write(path2, content) {
934
+ return this.client.http.request("PUT", "/workspace/file", { body: { path: path2, content } });
935
+ }
936
+ remove(path2) {
937
+ return this.client.http.request("DELETE", "/workspace/file", { query: { path: path2 } });
938
+ }
939
+ exec(command, signal) {
940
+ return this.client.http.request("POST", "/workspace/exec", { body: command, signal });
941
+ }
942
+ /** Output data is base64, preserving partial UTF-8 chunks. Cancelling stops this command. */
943
+ async *stream(command, signal) {
944
+ const response = await this.client.http.open("POST", "/workspace/exec/stream", { body: command, signal });
945
+ yield* readSSE(response.body, signal);
946
+ }
947
+ };
948
+ var TerminalsApi = class {
949
+ constructor(client) {
950
+ this.client = client;
951
+ }
952
+ client;
953
+ list(directory) {
954
+ return this.client.http.request("GET", "/workspace/terminals", { query: { directory } });
955
+ }
956
+ open(request) {
957
+ return this.client.http.request("POST", "/workspace/terminals", { body: request });
958
+ }
959
+ get(id) {
960
+ return this.client.http.request("GET", this.path(id));
961
+ }
962
+ close(id) {
963
+ return this.client.http.request("DELETE", this.path(id));
964
+ }
965
+ input(id, input) {
966
+ return this.client.http.request("POST", `${this.path(id)}/input`, { body: input });
967
+ }
968
+ resize(id, columns, rows) {
969
+ return this.client.http.request("PUT", `${this.path(id)}/size`, { body: { columns, rows } });
970
+ }
971
+ /** Detaching only closes the subscription. Resume from the last sequence; reset replaces the screen. */
972
+ async *events(id, after = 0, signal) {
973
+ const response = await this.client.http.open("GET", `${this.path(id)}/events`, { query: { after }, signal });
974
+ yield* readSSE(response.body, signal);
975
+ }
976
+ path(id) {
977
+ return `/workspace/terminals/${encodeURIComponent(id)}`;
978
+ }
979
+ };
980
+
981
+ // src/embedded.ts
982
+ var shared = /* @__PURE__ */ new Map();
983
+ function configKey(opts) {
984
+ return JSON.stringify([opts.cwd ?? "", opts.statePath ?? "", opts.bin ?? ""]);
985
+ }
986
+ function startEmbedded(opts = {}) {
987
+ const key = configKey(opts);
988
+ let daemon = shared.get(key);
989
+ if (!daemon) {
990
+ daemon = spawnDaemon(opts).catch((err) => {
991
+ shared.delete(key);
992
+ throw err;
993
+ });
994
+ shared.set(key, daemon);
995
+ }
996
+ return daemon;
997
+ }
998
+ function serverRuntime() {
999
+ return globalThis;
1000
+ }
1001
+ function isServerRuntime() {
1002
+ const g = serverRuntime();
1003
+ return typeof g.process !== "undefined" && !!g.process.versions?.node || typeof g.Bun !== "undefined" || typeof g.Deno !== "undefined";
1004
+ }
1005
+ async function spawnDaemon(opts) {
1006
+ if (!isServerRuntime()) {
1007
+ throw new MindwireError(
1008
+ "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."
1009
+ );
1010
+ }
1011
+ const { spawn: spawn2 } = await import('child_process');
1012
+ const net = await import('net');
1013
+ const proc = serverRuntime().process;
1014
+ const { randomBytes } = await import('crypto');
1015
+ const resolved = await resolveBinary(opts.bin);
1016
+ const bin = resolved.bin;
1017
+ const port = await freePort(net);
1018
+ const baseUrl = `http://127.0.0.1:${port}`;
1019
+ const token = randomBytes(32).toString("hex");
1020
+ const child = spawn2(bin, [], {
1021
+ env: {
1022
+ ...proc.env,
1023
+ ADDR: `127.0.0.1:${port}`,
1024
+ AGENT_CWD: opts.cwd ?? proc.cwd(),
1025
+ STATE_PATH: opts.statePath ?? ".mindwire-state.json",
1026
+ DAEMON_TOKEN: token
1027
+ },
1028
+ stdio: "ignore"
1029
+ });
1030
+ let spawnError = null;
1031
+ child.on("error", (e) => {
1032
+ spawnError = e;
1033
+ });
1034
+ const stop = () => {
1035
+ try {
1036
+ child.kill();
1037
+ } catch {
1038
+ }
1039
+ };
1040
+ proc.once?.("exit", stop);
1041
+ proc.once?.("SIGINT", () => {
1042
+ stop();
1043
+ proc.exit?.(130);
1044
+ });
1045
+ await waitHealthy(baseUrl, token, () => spawnError, resolved);
1046
+ return { baseUrl, token, stop };
1047
+ }
1048
+ async function resolveBinary(explicit) {
1049
+ const fs3 = await import('fs');
1050
+ const path2 = await import('path');
1051
+ const proc = serverRuntime().process;
1052
+ const ext = proc.platform === "win32" ? ".exe" : "";
1053
+ const candidates = [];
1054
+ if (explicit) candidates.push(explicit);
1055
+ if (proc.env?.MINDWIRE_DAEMON) candidates.push(proc.env.MINDWIRE_DAEMON);
1056
+ try {
1057
+ const url = await import('url');
1058
+ const here = path2.dirname(url.fileURLToPath(import.meta.url));
1059
+ const named = `mindwired-${proc.platform}-${proc.arch}${ext}`;
1060
+ candidates.push(path2.join(here, "bin", named), path2.join(here, "..", "bin", named));
1061
+ } catch {
1062
+ }
1063
+ for (const c of candidates) {
1064
+ try {
1065
+ if (fs3.existsSync(c)) return { bin: c, found: true, checked: candidates };
1066
+ } catch {
1067
+ }
1068
+ }
1069
+ const downloaded = await ensureDaemonBinary({ platform: proc.platform, arch: proc.arch });
1070
+ return { bin: downloaded, found: true, checked: [...candidates, downloaded] };
1071
+ }
1072
+ function daemonStartError(resolved, cause) {
1073
+ if (!resolved.found) {
1074
+ return [
1075
+ "Couldn't start the mindwire daemon: the `mindwired` binary was not found.",
1076
+ `Looked in: ${resolved.checked.join(", ")}.`,
1077
+ "Fixes:",
1078
+ " \u2022 check GitHub Release access so the SDK can download its matching daemon binary; or",
1079
+ " \u2022 set MINDWIRE_DAEMON to a `mindwired` binary; or",
1080
+ " \u2022 pass { baseUrl } to connect to a daemon you run yourself.",
1081
+ " \u2022 Working inside the mindwire monorepo? Run `go build -o /tmp/mindwired ./daemon/cmd/daemon` and set MINDWIRE_DAEMON.",
1082
+ `(underlying error: ${cause.message})`
1083
+ ].join("\n");
1084
+ }
1085
+ 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.`;
1086
+ }
1087
+ function freePort(net) {
1088
+ return new Promise((resolve, reject) => {
1089
+ const srv = net.createServer();
1090
+ srv.on("error", reject);
1091
+ srv.listen(0, "127.0.0.1", () => {
1092
+ const addr = srv.address();
1093
+ const port = addr && typeof addr === "object" ? addr.port : 0;
1094
+ srv.close(() => resolve(port));
1095
+ });
1096
+ });
1097
+ }
1098
+ async function waitHealthy(baseUrl, token, getError, resolved, timeoutMs = 15e3) {
1099
+ const deadline = Date.now() + timeoutMs;
1100
+ while (Date.now() < deadline) {
1101
+ const err = getError();
1102
+ if (err) {
1103
+ throw new MindwireError(daemonStartError(resolved, err), { cause: err });
1104
+ }
1105
+ try {
1106
+ const res = await fetch(`${baseUrl}/healthz`, { headers: { Authorization: `Bearer ${token}` } });
1107
+ if (res.ok) return;
1108
+ } catch {
1109
+ }
1110
+ await new Promise((r) => setTimeout(r, 150));
1111
+ }
1112
+ throw new MindwireError("mindwire embedded daemon did not become healthy in time.");
1113
+ }
1114
+
1115
+ // src/target/index.ts
1116
+ function emit(spec, e) {
1117
+ if (!spec.onLog) return;
1118
+ try {
1119
+ spec.onLog(e);
1120
+ } catch {
1121
+ }
1122
+ }
1123
+ function local(opts = {}) {
1124
+ return {
1125
+ name: "local",
1126
+ async connect(spec) {
1127
+ const d = await startEmbedded(opts);
1128
+ emit(spec, { target: "local", phase: "ready", message: `embedded daemon on ${d.baseUrl}` });
1129
+ return {
1130
+ id: d.baseUrl,
1131
+ baseUrl: d.baseUrl,
1132
+ ...d.token !== void 0 ? { token: d.token } : {},
1133
+ // No-op: the embedded daemon is shared (keyed memoization) and reaped on process exit.
1134
+ stop: async () => {
1135
+ }
1136
+ };
1137
+ }
1138
+ };
1139
+ }
1140
+ function remote(baseUrl, opts = {}) {
1141
+ return {
1142
+ name: "remote",
1143
+ async connect(spec) {
1144
+ emit(spec, { target: "remote", phase: "skip", message: `using remote daemon at ${baseUrl}` });
1145
+ return {
1146
+ id: baseUrl,
1147
+ baseUrl,
1148
+ ...opts.token !== void 0 ? { token: opts.token } : {},
1149
+ ...opts.headers !== void 0 ? { headers: opts.headers } : {},
1150
+ ...opts.fetch !== void 0 ? { fetch: opts.fetch } : {},
1151
+ stop: async () => {
1152
+ }
1153
+ };
1154
+ }
1155
+ };
1156
+ }
1157
+
1158
+ // src/client.ts
1159
+ var handleByTransport = /* @__PURE__ */ new WeakMap();
1160
+ var Mindwire = class _Mindwire {
1161
+ /** Workspace registry: saved agent profiles, projects and chat relationships. */
1162
+ workspace;
1163
+ surfaces;
1164
+ service;
1165
+ execution;
1166
+ computer;
1167
+ http;
1168
+ /** The default agent type applied to agent-scoped calls, if set. */
1169
+ defaultAgent;
1170
+ /** Step-flow auth, scoped to this client's default agent (override per call). */
1171
+ auth;
1172
+ /** Persistent memory files + saved prompt templates, scoped to this client's default agent. */
1173
+ prompts;
1174
+ /** Persistent MCP-server config (the config an agent loads every run), scoped to this client's default agent. */
1175
+ mcp;
1176
+ /** Custom LLM-provider registration (opencode/Codex native config), scoped to this client's default agent. */
1177
+ providers;
1178
+ /** Daemon-driven notification channels + routing rules (webhook/slack/discord/telegram; per-agent/session/global). */
1179
+ notify;
1180
+ constructor(opts = {}) {
1181
+ const target = opts.target ?? local();
1182
+ const spec = { agent: opts.agent, onLog: opts.logger };
1183
+ this.http = new Http({
1184
+ fetch: opts.fetch,
1185
+ headers: opts.headers,
1186
+ timeoutMs: opts.requestTimeoutMs,
1187
+ resolveBase: () => {
1188
+ const h = target.connect(spec);
1189
+ handleByTransport.set(this.http, h);
1190
+ return h.then((x) => ({
1191
+ baseUrl: x.baseUrl,
1192
+ token: x.token,
1193
+ getToken: x.getToken,
1194
+ headers: x.headers,
1195
+ fetch: x.fetch
1196
+ }));
1197
+ }
1198
+ });
1199
+ this.defaultAgent = opts.agent;
1200
+ this.workspace = new WorkspaceApi(this);
1201
+ this.surfaces = new SurfacesApi(this);
1202
+ this.service = new ServiceApi(this);
1203
+ this.execution = new ExecutionApi(this);
1204
+ this.computer = new ComputerApi(this);
1205
+ this.auth = new AuthApi(this);
1206
+ this.prompts = new PromptsApi(this);
1207
+ this.mcp = new McpApi(this);
1208
+ this.providers = new ProvidersApi(this);
1209
+ this.notify = new NotifyApi(this);
1210
+ }
1211
+ /**
1212
+ * Provision the destination and await readiness **now**, rather than lazily on the first request —
1213
+ * useful to front-load a fresh SSH/Docker/Oblien box (and to stream its {@link EnsureEvent}s to the
1214
+ * `logger`) before the first `turn()`. Idempotent and memoized: repeated `ensure()` calls, and the
1215
+ * first real request, all await the same provisioning, so the target connects exactly once.
1216
+ */
1217
+ async ensure() {
1218
+ await this.http.ready();
1219
+ }
1220
+ /** Return a new client bound to a different default agent (shares the same transport config). */
1221
+ withAgent(agent) {
1222
+ const clone = Object.create(_Mindwire.prototype);
1223
+ clone.http = this.http;
1224
+ clone.defaultAgent = agent;
1225
+ clone.workspace = new WorkspaceApi(clone);
1226
+ clone.surfaces = new SurfacesApi(clone);
1227
+ clone.service = new ServiceApi(clone);
1228
+ clone.execution = new ExecutionApi(clone);
1229
+ clone.computer = new ComputerApi(clone);
1230
+ clone.auth = new AuthApi(clone);
1231
+ clone.prompts = new PromptsApi(clone);
1232
+ clone.mcp = new McpApi(clone);
1233
+ clone.providers = new ProvidersApi(clone);
1234
+ clone.notify = new NotifyApi(clone);
1235
+ return clone;
1236
+ }
1237
+ /** The `?agent=` value to send for a scoped call: explicit override → client default → none. */
1238
+ agentParam(scoped) {
1239
+ const a = scoped?.agent ?? this.defaultAgent;
1240
+ return a ? { agent: a } : void 0;
1241
+ }
1242
+ /**
1243
+ * Release target-owned resources by calling the {@link TargetHandle}'s `stop()` — once, even across
1244
+ * {@link withAgent} clones that share the transport. For an `ssh`/`docker`/`oblien` target this reaps
1245
+ * the box (tear down the tunnel, stop or delete the container/workspace, per `stopOnExit`); a no-op
1246
+ * for `local`/`remote` (embedded self-cleans on process exit; remote is not ours to stop). Safe to
1247
+ * call before the target has connected (nothing to reap yet).
1248
+ */
1249
+ async close() {
1250
+ const handle = handleByTransport.get(this.http);
1251
+ if (!handle) return;
1252
+ handleByTransport.delete(this.http);
1253
+ const h = await handle.catch(() => null);
1254
+ if (h) await h.stop();
1255
+ }
1256
+ // ---- health & catalog ----------------------------------------------------
1257
+ /** `GET /healthz` — authenticated liveness check. Resolves with the daemon's health payload when it is up. */
1258
+ health() {
1259
+ return this.http.request("GET", "/healthz");
1260
+ }
1261
+ /**
1262
+ * `GET /stats` — the daemon **process's** resource snapshot (heap in use, memory reserved from the
1263
+ * OS, goroutines, GC cycles, cores, platform, uptime). Cheap enough to call on demand — the daemon
1264
+ * reads its own Go runtime, not the machine — so a UI can fetch it when a user opens a daemon's page
1265
+ * without any background polling. See {@link Stats} for what each field means and doesn't.
1266
+ */
1267
+ stats() {
1268
+ return this.http.request("GET", "/stats");
1269
+ }
1270
+ /** `GET /catalog` — every agent this daemon binary supports. */
1271
+ catalog() {
1272
+ return this.http.request("GET", "/catalog");
1273
+ }
1274
+ /** `GET /agent` — capabilities + settings schema + auth methods/status for the selected agent. */
1275
+ agent(scoped) {
1276
+ return this.http.request("GET", "/agent", { query: this.agentParam(scoped) });
1277
+ }
1278
+ /** `GET /agent/software` — installed version, compatibility and approved update target. */
1279
+ software(opts = {}) {
1280
+ return this.http.request("GET", "/agent/software", {
1281
+ query: { ...this.agentParam(opts), ...opts.refresh ? { refresh: "true" } : {} }
1282
+ });
1283
+ }
1284
+ /**
1285
+ * `GET /models` — the models the selected agent can run for the configured account. An empty array
1286
+ * is valid (no credentials yet / offline). Throws a 400 {@link ApiError} for an agent whose model is
1287
+ * free text (check `capabilities.models` first).
1288
+ */
1289
+ models(scoped) {
1290
+ return this.http.request("GET", "/models", { query: this.agentParam(scoped) });
1291
+ }
1292
+ /** `GET /doctor` — daemon-level health plus the selected agent's own checks. */
1293
+ doctor(scoped) {
1294
+ return this.http.request("GET", "/doctor", { query: this.agentParam(scoped) });
1295
+ }
1296
+ // ---- toolchain setup -----------------------------------------------------
1297
+ /** `POST /setup` — start the agent's install toolchain (background; poll {@link setupStatus}). */
1298
+ setup(scoped) {
1299
+ return this.http.request("POST", "/setup", { query: this.agentParam(scoped) });
1300
+ }
1301
+ /** `POST /update` — install the catalog's newest tested version compatible with this daemon. */
1302
+ update(scoped) {
1303
+ return this.http.request("POST", "/update", { query: this.agentParam(scoped) });
1304
+ }
1305
+ /** `GET /setup` — current toolchain install progress. */
1306
+ setupStatus(scoped) {
1307
+ return this.http.request("GET", "/setup", { query: this.agentParam(scoped) });
1308
+ }
1309
+ // ---- config --------------------------------------------------------------
1310
+ /** `GET /config` — the declared, non-secret settings for the agent. */
1311
+ getConfig(scoped) {
1312
+ return this.http.request("GET", "/config", {
1313
+ query: this.agentParam(scoped)
1314
+ });
1315
+ }
1316
+ /** `PUT /config` — merge recognized (non-secret) setting keys. Unknown keys are ignored server-side. */
1317
+ async setConfig(values, scoped) {
1318
+ await this.http.request("PUT", "/config", {
1319
+ query: this.agentParam(scoped),
1320
+ body: values
1321
+ });
1322
+ }
1323
+ // ---- chats & history -----------------------------------------------------
1324
+ /** Native project conversations and drafts, newest first, shared across agents.
1325
+ * `cwd` matches an exact directory; `projectId` selects saved membership.
1326
+ * `refresh` bypasses the daemon's short native metadata cache.
1327
+ */
1328
+ chats(options = {}) {
1329
+ return this.http.request("GET", "/chats", { query: options });
1330
+ }
1331
+ /**
1332
+ * `PUT /chats/{id}` — rename a chat. The user title wins over the agent's native auto-title in
1333
+ * every listing; an empty title clears the rename (reverting to the native/derived title).
1334
+ * Returns the updated summary.
1335
+ */
1336
+ renameChat(chatId, title) {
1337
+ return this.http.request("PUT", `/chats/${encodeURIComponent(chatId)}`, {
1338
+ body: { title }
1339
+ });
1340
+ }
1341
+ /**
1342
+ * `DELETE /chats/{id}` — a true, irreversible delete: purges ALL of the chat's mindwire
1343
+ * bookkeeping and, for every session the chat mapped to, removes that agent's native transcript
1344
+ * (the source of truth). Rejects with a 409 error if a turn is live. Native deletion is
1345
+ * best-effort per agent; the result reports what was purged vs. failed.
1346
+ */
1347
+ deleteChat(chatId) {
1348
+ return this.http.request("DELETE", `/chats/${encodeURIComponent(chatId)}`);
1349
+ }
1350
+ /**
1351
+ * `POST /chats/{id}/fork` — clone a chat into a new id (generated when `newChatId` is omitted).
1352
+ * The fork shares the source's native session until its first turn, which branches it (natively
1353
+ * on Claude via `--fork-session`; a fresh session on agents without native fork). Rejects with a
1354
+ * 409 if the source has a live turn, 404 if the source is unknown, 400 if the target id is in
1355
+ * use. Returns the new chat's summary.
1356
+ */
1357
+ forkChat(chatId, opts = {}) {
1358
+ return this.http.request("POST", `/chats/${encodeURIComponent(chatId)}/fork`, {
1359
+ body: opts.newChatId ? { newChatId: opts.newChatId } : {}
1360
+ });
1361
+ }
1362
+ /**
1363
+ * `GET /chats/{id}/messages` — a chat's transcript (native when the agent supports it, else
1364
+ * the recorded fallback). `limit` caps to the newest N; `before` pages older history.
1365
+ */
1366
+ messages(chatId, opts = {}) {
1367
+ const query = { ...this.agentParam(opts) };
1368
+ if (opts.limit !== void 0) query["limit"] = opts.limit;
1369
+ if (opts.before !== void 0) query["before"] = opts.before;
1370
+ return this.http.request("GET", `/chats/${encodeURIComponent(chatId)}/messages`, {
1371
+ query
1372
+ });
1373
+ }
1374
+ /** `GET /chats/{id}/run` — the latest run for a chat (reattach anchor), or `null` if none yet. */
1375
+ async latestRun(chatId) {
1376
+ const data = await this.http.request(
1377
+ "GET",
1378
+ `/chats/${encodeURIComponent(chatId)}/run`
1379
+ );
1380
+ return data ? new Run(this.http, data) : null;
1381
+ }
1382
+ // ---- turns & runs --------------------------------------------------------
1383
+ /**
1384
+ * `POST /turns` — start a turn. Returns a {@link Run} handle you can stream, cancel, or await.
1385
+ * Rejects with an {@link ApiError} (409) if a turn is already running for the chat.
1386
+ *
1387
+ * `mode` defaults to `"turn"` — one agent turn that ends when the CLI settles. Pass
1388
+ * `mode: "resolve"` (with optional {@link ResolveOptions} `resolve` caps) for a global-resolve run
1389
+ * that auto-continues the agent's multi-step work until it's done; {@link Mindwire.resolve} is the
1390
+ * clearer entry point for that. The returned {@link Run} is then the parent of the run tree.
1391
+ */
1392
+ async turn(input) {
1393
+ const body = {
1394
+ chatId: input.chatId,
1395
+ message: input.message
1396
+ };
1397
+ if (input.requestId !== void 0) body.requestId = input.requestId;
1398
+ if (input.cwd !== void 0) body.cwd = input.cwd;
1399
+ if (input.options !== void 0) body.options = input.options;
1400
+ if (input.mode !== void 0) body.mode = input.mode;
1401
+ if (input.resolve !== void 0) body.resolve = input.resolve;
1402
+ if (input.gitAuth !== void 0) body.gitAuth = input.gitAuth;
1403
+ const data = await this.http.request("POST", "/turns", {
1404
+ query: this.agentParam(input),
1405
+ body
1406
+ });
1407
+ return new Run(this.http, data);
1408
+ }
1409
+ /**
1410
+ * `POST /turns {mode:"resolve"}` — start a **global-resolve** run: instead of returning after one
1411
+ * turn, the daemon holds the task open and auto-continues the agent (resuming on continuable stops
1412
+ * and probing for completion) until the work is globally resolved, then aggregates one final result.
1413
+ *
1414
+ * The returned {@link Run} is the **parent** of a run tree: each auto-continued iteration is a child
1415
+ * turn ({@link Run.children}) whose events stream onto the parent's topic, delimited by `continuation`
1416
+ * boundary events. `run.wait()` resolves once with the aggregated result; `run.stopReason` /
1417
+ * `run.iterations` report how the loop ended. Resolve turns run unattended (no mid-turn approvals)
1418
+ * and are bounded by {@link ResolveOptions} caps — see the resolve guide. Rejects with an
1419
+ * {@link ApiError} (409) if a turn is already running for the chat.
1420
+ */
1421
+ async resolve(input) {
1422
+ return this.turn({ ...input, mode: "resolve" });
1423
+ }
1424
+ /** `GET /runs/{id}` — fetch an existing run as a {@link Run} handle. */
1425
+ async run(id) {
1426
+ const data = await this.http.request("GET", `/runs/${encodeURIComponent(id)}`);
1427
+ return new Run(this.http, data);
1428
+ }
1429
+ /**
1430
+ * `POST /chats/{id}/compact` — run an on-demand conversation compaction as a first-class {@link Run}
1431
+ * you can stream or await. The agent folds prior context into a summary it carries forward, emitting
1432
+ * a `compaction` event on the stream and recording the boundary in history exactly like an
1433
+ * auto-compaction. Optional `instructions` focus the continuation summary (Claude's
1434
+ * `/compact <instructions>`; agents that don't honor focus still compact). Rejects with an
1435
+ * {@link ApiError}: 400 if the agent doesn't support compaction (`capabilities.compactNow`) or the
1436
+ * chat has no conversation yet, 409 if a turn is already running for the chat.
1437
+ */
1438
+ async compact(chatId, opts = {}) {
1439
+ const data = await this.http.request(
1440
+ "POST",
1441
+ `/chats/${encodeURIComponent(chatId)}/compact`,
1442
+ {
1443
+ query: this.agentParam(opts),
1444
+ body: opts.instructions ? { instructions: opts.instructions } : {}
1445
+ }
1446
+ );
1447
+ return new Run(this.http, data);
1448
+ }
1449
+ // ---- notifications -------------------------------------------------------
1450
+ /** `GET /notify/config` — whether a notification channel is wired (token never returned). */
1451
+ getNotifyConfig() {
1452
+ return this.http.request("GET", "/notify/config");
1453
+ }
1454
+ /** `PUT /notify/config` — store the provisioned notification channel (daemon-wide). */
1455
+ async setNotifyConfig(input) {
1456
+ await this.http.request("PUT", "/notify/config", { body: input });
1457
+ }
1458
+ /** `GET /notify/stream` — SSE feed of the daemon's notifications (replay, then live). */
1459
+ async *notifications(opts = {}) {
1460
+ const res = await this.http.open("GET", "/notify/stream", {
1461
+ ...opts.signal ? { signal: opts.signal } : {}
1462
+ });
1463
+ yield* readSSE(res.body, opts.signal);
1464
+ }
1465
+ // ---- live resources ------------------------------------------------------
1466
+ /**
1467
+ * `GET /processes/stream` — SSE feed of live per-turn CPU/memory, one {@link ProcessFrame} per tick.
1468
+ * Sampling is **on demand**: the daemon starts measuring only while a client is connected and stops
1469
+ * the instant the last one disconnects, so aborting the `signal` (or ending the loop) tells the
1470
+ * daemon to stop — no background work, no leak. Pass `agent` to filter each frame's samples to one
1471
+ * agent type. Frames are live snapshots (no replay); an empty `samples` array is a valid keep-alive.
1472
+ */
1473
+ async *processes(opts = {}) {
1474
+ const path2 = "/processes/stream" + (opts.agent ? `?agent=${encodeURIComponent(opts.agent)}` : "");
1475
+ const res = await this.http.open("GET", path2, {
1476
+ ...opts.signal ? { signal: opts.signal } : {}
1477
+ });
1478
+ yield* readSSE(res.body, opts.signal);
1479
+ }
1480
+ };
1481
+ var AuthApi = class {
1482
+ constructor(mw) {
1483
+ this.mw = mw;
1484
+ }
1485
+ mw;
1486
+ /** `GET /auth/methods` — the options list to present. */
1487
+ methods(scoped) {
1488
+ return this.mw.http.request("GET", "/auth/methods", {
1489
+ query: this.mw.agentParam(scoped)
1490
+ });
1491
+ }
1492
+ /** `POST /auth/begin` — start one method (may return `{ url, code, fields, pending }`). */
1493
+ begin(method, scoped) {
1494
+ return this.mw.http.request("POST", "/auth/begin", {
1495
+ query: this.mw.agentParam(scoped),
1496
+ body: { method }
1497
+ });
1498
+ }
1499
+ /** `POST /auth/step` — submit fields, or poll an interactive login to completion. */
1500
+ step(input, scoped) {
1501
+ return this.mw.http.request("POST", "/auth/step", {
1502
+ query: this.mw.agentParam(scoped),
1503
+ body: input
1504
+ });
1505
+ }
1506
+ /** Read one interactive attempt, scoped by the returned AuthState.flowId. */
1507
+ poll(flowId, scoped) {
1508
+ return this.step({ _flowId: flowId }, scoped);
1509
+ }
1510
+ /** Cancel this native login without affecting a newer sign-in attempt. */
1511
+ cancel(flowId, scoped) {
1512
+ return this.step({ _flowId: flowId, _action: "cancel" }, scoped);
1513
+ }
1514
+ /** `GET /auth/status` — is the agent authenticated, and via which method. */
1515
+ status(scoped) {
1516
+ return this.mw.http.request("GET", "/auth/status", {
1517
+ query: this.mw.agentParam(scoped)
1518
+ });
1519
+ }
1520
+ /** `POST /auth/logout` — disconnect this harness once its running chats finish. */
1521
+ logout(scoped) {
1522
+ return this.mw.http.request("POST", "/auth/logout", {
1523
+ query: this.mw.agentParam(scoped)
1524
+ });
1525
+ }
1526
+ };
1527
+ var PromptsApi = class {
1528
+ constructor(mw) {
1529
+ this.mw = mw;
1530
+ }
1531
+ mw;
1532
+ query(scoped, extra) {
1533
+ return { ...this.mw.agentParam(scoped), ...extra };
1534
+ }
1535
+ /**
1536
+ * `GET /memory` — the agent's memory file at every supported scope (project + user for both
1537
+ * Claude and Codex). Each entry carries the resolved `path` and `exists`; `content` is `""` for
1538
+ * an absent file.
1539
+ */
1540
+ memory(opts = {}) {
1541
+ return this.mw.http.request("GET", "/memory", {
1542
+ query: this.query(opts, { dir: opts.dir })
1543
+ });
1544
+ }
1545
+ /** `PUT /memory` — write the memory file at `scope`. Returns the resulting {@link MemoryDoc}. */
1546
+ setMemory(input, opts = {}) {
1547
+ return this.mw.http.request("PUT", "/memory", {
1548
+ query: this.query(opts, { dir: opts.dir }),
1549
+ body: input
1550
+ });
1551
+ }
1552
+ /**
1553
+ * `DELETE /memory` — remove the memory file at `scope` (defaults to `user`). Returns the resulting
1554
+ * {@link MemoryDoc} (`exists: false` at the resolved path). Idempotent: deleting an absent file still
1555
+ * succeeds.
1556
+ */
1557
+ deleteMemory(opts = {}) {
1558
+ return this.mw.http.request("DELETE", "/memory", {
1559
+ query: this.query(opts, { scope: opts.scope, dir: opts.dir })
1560
+ });
1561
+ }
1562
+ /**
1563
+ * `GET /prompts` — saved prompt templates across every supported scope (Claude: project + user;
1564
+ * Codex: user only). `content` is omitted here; fetch it with {@link get}. A missing project
1565
+ * directory yields an empty list for that scope rather than an error.
1566
+ */
1567
+ list(opts = {}) {
1568
+ return this.mw.http.request("GET", "/prompts", {
1569
+ query: this.query(opts, { dir: opts.dir })
1570
+ });
1571
+ }
1572
+ /** `GET /prompts/{name}` — one template's full body. Rejects with a 404 `ApiError` if absent. */
1573
+ get(name, opts = {}) {
1574
+ return this.mw.http.request("GET", `/prompts/${encodeURIComponent(name)}`, {
1575
+ query: this.query(opts, { scope: opts.scope, dir: opts.dir })
1576
+ });
1577
+ }
1578
+ /** `PUT /prompts/{name}` — create or overwrite a template. Returns the resulting {@link PromptTemplate}. */
1579
+ set(name, content, opts = {}) {
1580
+ return this.mw.http.request("PUT", `/prompts/${encodeURIComponent(name)}`, {
1581
+ query: this.query(opts, { scope: opts.scope, dir: opts.dir }),
1582
+ body: { content }
1583
+ });
1584
+ }
1585
+ /**
1586
+ * `DELETE /prompts/{name}` — remove one template at `scope` (defaults to `user`). Idempotent:
1587
+ * deleting an absent template still succeeds. A traversal name rejects with a 400 `ApiError`.
1588
+ */
1589
+ async delete(name, opts = {}) {
1590
+ await this.mw.http.request("DELETE", `/prompts/${encodeURIComponent(name)}`, {
1591
+ query: this.query(opts, { scope: opts.scope, dir: opts.dir })
1592
+ });
1593
+ }
1594
+ /**
1595
+ * `GET /subagents` — persistent subagent definitions (Claude `.claude/agents/*.md`) across every
1596
+ * supported scope. `content` is omitted here (fetch it with {@link subagent}); `meta` is the parsed
1597
+ * frontmatter view. Rejects with a 400 `ApiError` on an agent without the subagent-definition module.
1598
+ * Distinct from a turn's per-turn `subagents` passthrough — this is the on-disk definition store.
1599
+ */
1600
+ subagents(opts = {}) {
1601
+ return this.mw.http.request("GET", "/subagents", {
1602
+ query: this.query(opts, { dir: opts.dir })
1603
+ });
1604
+ }
1605
+ /** `GET /subagents/{name}` — one definition's raw body + parsed meta. 404 `ApiError` if absent. */
1606
+ subagent(name, opts = {}) {
1607
+ return this.mw.http.request("GET", `/subagents/${encodeURIComponent(name)}`, {
1608
+ query: this.query(opts, { scope: opts.scope, dir: opts.dir })
1609
+ });
1610
+ }
1611
+ /** `PUT /subagents/{name}` — create or overwrite a definition (raw content is canonical). Returns it. */
1612
+ setSubagent(name, content, opts = {}) {
1613
+ return this.mw.http.request("PUT", `/subagents/${encodeURIComponent(name)}`, {
1614
+ query: this.query(opts, { scope: opts.scope, dir: opts.dir }),
1615
+ body: { content }
1616
+ });
1617
+ }
1618
+ /**
1619
+ * `DELETE /subagents/{name}` — remove one definition at `scope` (defaults to `user`). Idempotent:
1620
+ * deleting an absent definition still succeeds. A traversal name rejects with a 400 `ApiError`.
1621
+ */
1622
+ async deleteSubagent(name, opts = {}) {
1623
+ await this.mw.http.request("DELETE", `/subagents/${encodeURIComponent(name)}`, {
1624
+ query: this.query(opts, { scope: opts.scope, dir: opts.dir })
1625
+ });
1626
+ }
1627
+ };
1628
+ var McpApi = class {
1629
+ constructor(mw) {
1630
+ this.mw = mw;
1631
+ }
1632
+ mw;
1633
+ query(scoped, extra) {
1634
+ return { ...this.mw.agentParam(scoped), ...extra };
1635
+ }
1636
+ /**
1637
+ * `GET /mcp` — every persistent MCP server across the agent's supported scopes, keyed
1638
+ * `scope → name → server` (Claude: project + user; Codex: user only). A missing config file yields
1639
+ * an empty object for that scope rather than an error.
1640
+ */
1641
+ list(opts = {}) {
1642
+ return this.mw.http.request("GET", "/mcp", {
1643
+ query: this.query(opts, { dir: opts.dir })
1644
+ });
1645
+ }
1646
+ /** `GET /mcp/{name}` — one server's definition. Rejects with a 404 `ApiError` if it isn't configured. */
1647
+ get(name, opts = {}) {
1648
+ return this.mw.http.request("GET", `/mcp/${encodeURIComponent(name)}`, {
1649
+ query: this.query(opts, { scope: opts.scope, dir: opts.dir })
1650
+ });
1651
+ }
1652
+ /** `PUT /mcp/{name}` — create or overwrite one server. Returns the stored definition. */
1653
+ set(name, server, opts = {}) {
1654
+ return this.mw.http.request("PUT", `/mcp/${encodeURIComponent(name)}`, {
1655
+ query: this.query(opts, { scope: opts.scope, dir: opts.dir }),
1656
+ body: server
1657
+ });
1658
+ }
1659
+ /** `DELETE /mcp/{name}` — remove one server. Idempotent: deleting an absent server still succeeds. */
1660
+ async delete(name, opts = {}) {
1661
+ await this.mw.http.request("DELETE", `/mcp/${encodeURIComponent(name)}`, {
1662
+ query: this.query(opts, { scope: opts.scope, dir: opts.dir })
1663
+ });
1664
+ }
1665
+ };
1666
+ var ProvidersApi = class {
1667
+ constructor(mw) {
1668
+ this.mw = mw;
1669
+ }
1670
+ mw;
1671
+ query(scoped, extra) {
1672
+ return { ...this.mw.agentParam(scoped), ...extra };
1673
+ }
1674
+ /**
1675
+ * `GET /providers` — every registered custom provider across the agent's supported scopes, keyed
1676
+ * `scope → id → provider` (opencode/Codex: user only). A missing config file yields an empty object for
1677
+ * that scope rather than an error. `hasKey` reports whether a secret is stored; the key is never returned.
1678
+ */
1679
+ list(opts = {}) {
1680
+ return this.mw.http.request("GET", "/providers", {
1681
+ query: this.query(opts, { dir: opts.dir })
1682
+ });
1683
+ }
1684
+ /** `GET /providers/{id}` — one provider's definition. Rejects with a 404 `ApiError` if it isn't configured. */
1685
+ get(id, opts = {}) {
1686
+ return this.mw.http.request("GET", `/providers/${encodeURIComponent(id)}`, {
1687
+ query: this.query(opts, { scope: opts.scope, dir: opts.dir })
1688
+ });
1689
+ }
1690
+ /**
1691
+ * `PUT /providers/{id}` — create or overwrite one provider, returning the stored definition (with
1692
+ * `hasKey` and the stored `envVars`). Two write-only secret channels, both optional: `opts.apiKey` is a
1693
+ * single key (custom endpoints, single-key catalog brands); `opts.secrets` is a NAME→VALUE map for a
1694
+ * catalog provider whose entry declares MULTIPLE env vars (e.g. AWS Bedrock). Omitting both leaves any
1695
+ * previously stored secret intact. The path `id` wins over any `id` on the provider value.
1696
+ */
1697
+ set(id, provider, opts = {}) {
1698
+ return this.mw.http.request("PUT", `/providers/${encodeURIComponent(id)}`, {
1699
+ query: this.query(opts, { scope: opts.scope, dir: opts.dir }),
1700
+ body: { ...provider, id, apiKey: opts.apiKey, secrets: opts.secrets }
1701
+ });
1702
+ }
1703
+ /** `DELETE /providers/{id}` — remove one provider and clear its stored key. Idempotent. */
1704
+ async delete(id, opts = {}) {
1705
+ await this.mw.http.request("DELETE", `/providers/${encodeURIComponent(id)}`, {
1706
+ query: this.query(opts, { scope: opts.scope, dir: opts.dir })
1707
+ });
1708
+ }
1709
+ };
1710
+ var NotifyApi = class {
1711
+ constructor(mw) {
1712
+ this.mw = mw;
1713
+ }
1714
+ mw;
1715
+ /** `GET /notify/channels` — every channel, masked (no secrets). */
1716
+ channels() {
1717
+ return this.mw.http.request("GET", "/notify/channels");
1718
+ }
1719
+ /** `POST /notify/channels` — create a channel (server-assigns the id). Returns it masked. */
1720
+ createChannel(input) {
1721
+ return this.mw.http.request("POST", "/notify/channels", { body: input });
1722
+ }
1723
+ /**
1724
+ * `PUT /notify/channels/{id}` — update a channel, merge-preserving any omitted secret
1725
+ * (`url`/`token`/`secret`). Returns the updated masked channel. 404 `ApiError` if unknown.
1726
+ */
1727
+ setChannel(id, input) {
1728
+ return this.mw.http.request("PUT", `/notify/channels/${encodeURIComponent(id)}`, {
1729
+ body: input
1730
+ });
1731
+ }
1732
+ /** `DELETE /notify/channels/{id}` — remove a channel. Idempotent. */
1733
+ async deleteChannel(id) {
1734
+ await this.mw.http.request("DELETE", `/notify/channels/${encodeURIComponent(id)}`);
1735
+ }
1736
+ /**
1737
+ * `POST /notify/channels/{id}/test` — deliver a synthetic notification to a channel. A failed
1738
+ * delivery is DATA (`{ ok: false, error }`), not a thrown error; only an unknown id rejects (404).
1739
+ */
1740
+ testChannel(id) {
1741
+ return this.mw.http.request(
1742
+ "POST",
1743
+ `/notify/channels/${encodeURIComponent(id)}/test`
1744
+ );
1745
+ }
1746
+ /** `GET /notify/rules` — every routing rule. */
1747
+ rules() {
1748
+ return this.mw.http.request("GET", "/notify/rules");
1749
+ }
1750
+ /** `POST /notify/rules` — create a rule (server-assigns the id). Returns it. */
1751
+ createRule(input) {
1752
+ return this.mw.http.request("POST", "/notify/rules", { body: input });
1753
+ }
1754
+ /** `PUT /notify/rules/{id}` — replace a rule. Returns it. 404 `ApiError` if unknown. */
1755
+ setRule(id, input) {
1756
+ return this.mw.http.request("PUT", `/notify/rules/${encodeURIComponent(id)}`, {
1757
+ body: input
1758
+ });
1759
+ }
1760
+ /** `DELETE /notify/rules/{id}` — remove a rule. Idempotent. */
1761
+ async deleteRule(id) {
1762
+ await this.mw.http.request("DELETE", `/notify/rules/${encodeURIComponent(id)}`);
1763
+ }
1764
+ };
1765
+ async function acquireProcessLock(file, options = {}) {
1766
+ const owner = await currentProcessIdentity();
1767
+ const queue = file + ".queue", id = randomUUID() + ".json", claimPath = queue + "/" + id;
1768
+ await fs.mkdir(queue, { recursive: true, mode: 448 });
1769
+ let lock;
1770
+ const release = async () => {
1771
+ try {
1772
+ if (lock) {
1773
+ const held = lock;
1774
+ lock = void 0;
1775
+ const owned = await held.stat();
1776
+ await held.close();
1777
+ const current = await fs.stat(file).catch(() => void 0);
1778
+ if (current?.ino === owned.ino && current.dev === owned.dev) await fs.rm(file, { force: true });
1779
+ }
1780
+ } finally {
1781
+ await fs.rm(claimPath, { force: true });
1782
+ }
1783
+ };
1784
+ const publish = async (claim) => {
1785
+ const temporary = claimPath + ".tmp";
1786
+ try {
1787
+ await fs.writeFile(temporary, JSON.stringify(claim), { mode: 384 });
1788
+ await fs.rename(temporary, claimPath);
1789
+ } finally {
1790
+ await fs.rm(temporary, { force: true });
1791
+ }
1792
+ };
1793
+ const claims = async () => {
1794
+ const entries = [];
1795
+ for (const candidate of await fs.readdir(queue)) {
1796
+ if (!candidate.endsWith(".json")) continue;
1797
+ const content = await fs.readFile(queue + "/" + candidate, "utf8").catch((error) => {
1798
+ if (error.code === "ENOENT") return void 0;
1799
+ throw error;
1800
+ });
1801
+ if (content === void 0) continue;
1802
+ const claim = JSON.parse(content);
1803
+ if (!claim.owner || claim.ticket !== null && (!Number.isSafeInteger(claim.ticket) || claim.ticket < 1)) {
1804
+ throw new Error("Invalid Mindwire lock ticket.");
1805
+ }
1806
+ if (await processStateAlive(claim.owner)) entries.push({ id: candidate, claim });
1807
+ else await fs.rm(queue + "/" + candidate, { force: true });
1808
+ }
1809
+ return entries;
1810
+ };
1811
+ try {
1812
+ options.signal?.throwIfAborted();
1813
+ await publish({ owner, ticket: null });
1814
+ const ticket = Math.max(0, ...(await claims()).map((entry) => entry.claim.ticket ?? 0)) + 1;
1815
+ if (!Number.isSafeInteger(ticket)) throw new Error("Mindwire's connection queue is full. Retry shortly.");
1816
+ await publish({ owner, ticket });
1817
+ const deadline = Date.now() + 3e4;
1818
+ while (Date.now() < deadline) {
1819
+ options.signal?.throwIfAborted();
1820
+ if (await options.onWait?.()) {
1821
+ await release();
1822
+ return void 0;
1823
+ }
1824
+ const ahead = (await claims()).some((entry) => entry.id !== id && (entry.claim.ticket === null || entry.claim.ticket < ticket || entry.claim.ticket === ticket && entry.id < id));
1825
+ if (!ahead) {
1826
+ try {
1827
+ lock = await fs.open(file, "wx", 384);
1828
+ await lock.writeFile(JSON.stringify(owner));
1829
+ return { close: release };
1830
+ } catch (error) {
1831
+ if (error.code !== "EEXIST") throw error;
1832
+ const holder = await readProcessState(file);
1833
+ const age = Date.now() - (await fs.stat(file).catch(() => ({ mtimeMs: Date.now() }))).mtimeMs;
1834
+ if (age > 5e3 && !await processStateAlive(holder)) {
1835
+ await fs.rm(file, { force: true });
1836
+ continue;
1837
+ }
1838
+ }
1839
+ }
1840
+ await setTimeout$1(250, void 0, { signal: options.signal });
1841
+ }
1842
+ throw new Error("Another Mindwire operation is in progress. Try again shortly.");
1843
+ } catch (error) {
1844
+ await release();
1845
+ throw error;
1846
+ }
1847
+ }
1848
+
1849
+ // src/computer/lifecycle.ts
1850
+ var defaultStateDirectory = () => path.join(homedir(), ".mindwire", "computer");
1851
+ var defaultComputerConfig = () => ({ directory: homedir(), bind: "0.0.0.0", sshPort: 8791, websocketPort: 8792, relay: { kind: "cloudflare" } });
1852
+ async function readJSON(file) {
1853
+ try {
1854
+ return JSON.parse(await fs.readFile(file, "utf8"));
1855
+ } catch (error) {
1856
+ if (error.code === "ENOENT") return void 0;
1857
+ throw error;
1858
+ }
1859
+ }
1860
+ async function writeJSON(file, value) {
1861
+ const temp = `${file}.${randomUUID()}.tmp`;
1862
+ try {
1863
+ await fs.writeFile(temp, JSON.stringify(value) + "\n", { mode: 384 });
1864
+ await fs.rename(temp, file);
1865
+ } finally {
1866
+ await fs.rm(temp, { force: true });
1867
+ }
1868
+ }
1869
+ async function computerClient(directory) {
1870
+ const runtime = await readJSON(path.join(directory, "computer-runtime.json"));
1871
+ if (!runtime) throw new Error("Mindwire is not running. Run mindwire connect on the computer.");
1872
+ const url = new URL(`http://${runtime.apiAddress}`);
1873
+ if (!["127.0.0.1", "[::1]"].includes(url.hostname) || url.username || url.password) throw new Error("Invalid local Mindwire address.");
1874
+ const token = (await fs.readFile(path.join(directory, "daemon.token"), "utf8")).trim();
1875
+ const client = new Mindwire({ target: remote(url.origin, { token }), requestTimeoutMs: 3e3 });
1876
+ const info = await client.computer.info();
1877
+ if (info.computerId !== runtime.computerId || info.pid !== runtime.pid) throw new Error("The local Mindwire process changed. Retry the command.");
1878
+ return client;
1879
+ }
1880
+ function directRoutes(config, port) {
1881
+ if (config.host) return [{ kind: "ssh", host: config.host, port }];
1882
+ if (!["0.0.0.0", "::"].includes(config.bind)) return [{ kind: "ssh", host: config.bind, port }];
1883
+ const addresses = /* @__PURE__ */ new Set();
1884
+ for (const entries of Object.values(networkInterfaces())) {
1885
+ for (const address of entries ?? []) if (address.family === "IPv4" && !address.internal) addresses.add(address.address);
1886
+ }
1887
+ return [...addresses].slice(0, 6).map((host) => ({ kind: "ssh", host, port }));
1888
+ }
1889
+ async function ensureComputer(directory, cliPath, patch = {}, onProgress, options = {}) {
1890
+ const resume = options.resume !== false;
1891
+ options.signal?.throwIfAborted();
1892
+ await fs.mkdir(directory, { recursive: true, mode: 448 });
1893
+ const stoppedPath = path.join(directory, "computer-stopped.json");
1894
+ if (resume) await fs.rm(stoppedPath, { force: true });
1895
+ else if ((await readJSON(stoppedPath))?.stopped) throw new Error("Mindwire was stopped by its owner.");
1896
+ let current;
1897
+ try {
1898
+ current = await computerClient(directory);
1899
+ } catch {
1900
+ }
1901
+ const configPath = path.join(directory, "computer-config.json");
1902
+ const previous = await readJSON(configPath);
1903
+ const config = { ...defaultComputerConfig(), ...previous, ...patch };
1904
+ let launchedPID;
1905
+ let launchFailure;
1906
+ const waitUntilReady = async () => {
1907
+ let phase;
1908
+ for (let attempt = 0; attempt < 1200; attempt++) {
1909
+ options.signal?.throwIfAborted();
1910
+ const controller = await readJSON(path.join(directory, "computer-controller.json"));
1911
+ if (launchFailure) throw launchFailure;
1912
+ if (launchedPID && controller?.pid !== launchedPID) {
1913
+ await setTimeout$1(250);
1914
+ continue;
1915
+ }
1916
+ if (controller?.error && !controller.recovering) throw new Error(controller.error);
1917
+ if (controller?.ready && await processStateAlive(controller)) return computerClient(directory);
1918
+ if (controller?.phase && controller.phase !== phase) {
1919
+ phase = controller.phase;
1920
+ onProgress?.(phase);
1921
+ }
1922
+ await setTimeout$1(250);
1923
+ }
1924
+ throw new Error(`Mindwire did not finish starting. Inspect ${path.join(directory, "computer.log")}.`);
1925
+ };
1926
+ if (current) {
1927
+ if (Object.keys(patch).some((key) => JSON.stringify(config[key]) !== JSON.stringify(previous?.[key]))) {
1928
+ throw new Error("Mindwire is running with different connection settings. Run mindwire stop when idle, then retry with the new settings.");
1929
+ }
1930
+ const controller = await readJSON(path.join(directory, "computer-controller.json"));
1931
+ if (await processStateAlive(controller)) return waitUntilReady();
1932
+ }
1933
+ const lockPath = path.join(directory, "launch.lock");
1934
+ const lock = await acquireProcessLock(lockPath, {
1935
+ signal: options.signal,
1936
+ onWait: async () => {
1937
+ const controller = await readJSON(path.join(directory, "computer-controller.json"));
1938
+ if (await processStateAlive(controller)) {
1939
+ const saved = await readJSON(configPath);
1940
+ if (Object.keys(patch).some((key) => JSON.stringify(patch[key]) !== JSON.stringify(saved?.[key]))) {
1941
+ throw new Error("Another start used different connection settings. Stop Mindwire when idle before changing them.");
1942
+ }
1943
+ return true;
1944
+ }
1945
+ return false;
1946
+ }
1947
+ });
1948
+ if (!lock) return waitUntilReady();
1949
+ try {
1950
+ if (!resume && (await readJSON(stoppedPath))?.stopped) throw new Error("Mindwire was stopped by its owner.");
1951
+ let existing;
1952
+ try {
1953
+ existing = await computerClient(directory);
1954
+ } catch {
1955
+ }
1956
+ if (existing) {
1957
+ const saved = await readJSON(configPath);
1958
+ if (Object.keys(patch).some((key) => JSON.stringify(patch[key]) !== JSON.stringify(saved?.[key]))) {
1959
+ throw new Error("Another start used different connection settings. Stop Mindwire when idle before changing them.");
1960
+ }
1961
+ const controller2 = await readJSON(path.join(directory, "computer-controller.json"));
1962
+ if (await processStateAlive(controller2)) return waitUntilReady();
1963
+ }
1964
+ const controller = await readJSON(path.join(directory, "computer-controller.json"));
1965
+ if (await processStateAlive(controller)) {
1966
+ if (controller?.error && !controller.recovering) throw new Error(controller.error);
1967
+ } else {
1968
+ await writeJSON(configPath, config);
1969
+ const log = await fs.open(path.join(directory, "computer.log"), "a", 384);
1970
+ try {
1971
+ const child = spawn(process.execPath, [cliPath, "_serve", "--state-dir", directory], {
1972
+ detached: true,
1973
+ stdio: ["ignore", log.fd, log.fd],
1974
+ windowsHide: true
1975
+ });
1976
+ await new Promise((resolve, reject) => {
1977
+ child.once("spawn", resolve);
1978
+ child.once("error", reject);
1979
+ });
1980
+ launchedPID = child.pid;
1981
+ child.on("exit", () => {
1982
+ launchFailure = new Error(`The computer controller stopped. Inspect ${path.join(directory, "computer.log")}.`);
1983
+ });
1984
+ child.unref();
1985
+ } finally {
1986
+ await log.close();
1987
+ }
1988
+ }
1989
+ return await waitUntilReady();
1990
+ } finally {
1991
+ await lock.close();
1992
+ }
1993
+ }
1994
+ async function superviseComputer(directory) {
1995
+ const controller = await import('./controller-CGSJZZZD.js');
1996
+ await controller.superviseComputer(directory);
1997
+ }
1998
+ async function stopComputer(directory, force = false) {
1999
+ const client = await computerClient(directory);
2000
+ const controller = await readJSON(path.join(directory, "computer-controller.json"));
2001
+ if (!await processStateAlive(controller)) throw new Error("The Mindwire controller is not running.");
2002
+ const lease = force ? void 0 : await client.service.acquireUpdate();
2003
+ try {
2004
+ await writeJSON(path.join(directory, "computer-stopped.json"), { stopped: true });
2005
+ if (!await processStateAlive(controller)) throw new Error("The Mindwire controller changed. Retry the command.");
2006
+ process.kill(controller.pid, "SIGTERM");
2007
+ } catch (error) {
2008
+ await fs.rm(path.join(directory, "computer-stopped.json"), { force: true });
2009
+ if (lease) await client.service.releaseUpdate(lease.id);
2010
+ throw error;
2011
+ }
2012
+ for (let attempt = 0; attempt < 80; attempt++) {
2013
+ if (!await processStateAlive(controller)) return;
2014
+ await setTimeout$1(250);
2015
+ }
2016
+ throw new Error("Mindwire is still stopping. Check computer.log.");
2017
+ }
2018
+
2019
+ export { SDK_VERSION, acquireProcessLock, adoptProcess, computerClient, computerPairingQRData, computerPairingURI, currentProcessIdentity, defaultStateDirectory, directRoutes, ensureComputer, ensureDaemonBinary, ownChild, processIdentity, processStateAlive, readJSON, stopComputer, superviseComputer, writeJSON };
2020
+ //# sourceMappingURL=chunk-L3USUTZZ.js.map
2021
+ //# sourceMappingURL=chunk-L3USUTZZ.js.map