mindwire 0.1.1 → 0.1.3
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/README.md +4 -12
- package/dist/client.d.ts +1 -1
- package/dist/index.cjs +82 -39
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +82 -39
- package/dist/index.js.map +1 -1
- package/dist/target/container.d.ts +2 -0
- package/dist/target/docker.d.ts +7 -1
- package/dist/target/host.d.ts +13 -5
- package/dist/target/oblien.d.ts +5 -1
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# mindwire
|
|
2
2
|
|
|
3
3
|
A typed TypeScript client for the **mindwire daemon** — one SDK for every coding-agent harness
|
|
4
|
-
(Claude Code, Codex,
|
|
4
|
+
(Claude Code, Codex, Grok Build, opencode). The daemon normalizes each agent to a single
|
|
5
5
|
protocol; this package is a thin, dependency-free client over its REST + SSE surface.
|
|
6
6
|
|
|
7
7
|
- **Embedded by default.** On a server runtime (Node/Bun/Deno) the SDK auto-spawns the bundled
|
|
@@ -107,11 +107,9 @@ const mw = new Mindwire({
|
|
|
107
107
|
await mw.ensure(); // resolves once the daemon is healthy; then turns stream as usual
|
|
108
108
|
```
|
|
109
109
|
|
|
110
|
-
Binary discovery for the `local` daemon, in order: the `bin` option → `$MINDWIRE_DAEMON` →
|
|
111
|
-
the
|
|
112
|
-
|
|
113
|
-
`mindwired` on `PATH`. For local dev without the prebuilt package, set `$MINDWIRE_DAEMON` to a
|
|
114
|
-
`go build`-produced binary.
|
|
110
|
+
Binary discovery for the `local` daemon, in order: the `bin` option → `$MINDWIRE_DAEMON` → a
|
|
111
|
+
verified SDK-matched binary downloaded from the GitHub Release → `mindwired` on `PATH`. For local
|
|
112
|
+
development, set `$MINDWIRE_DAEMON` to a `go build`-produced binary.
|
|
115
113
|
|
|
116
114
|
`ssh` / `docker` / `oblien` require an optional peer (`ssh2` / `dockerode` / `oblien`) — install only
|
|
117
115
|
the one you use. Importing `mindwire` never loads them.
|
|
@@ -180,12 +178,6 @@ bun install
|
|
|
180
178
|
bun run build # tsup → dist (ESM + CJS + .d.ts)
|
|
181
179
|
bun run typecheck # tsc --noEmit
|
|
182
180
|
bun test # client + SSE parser tests (mocked fetch)
|
|
183
|
-
bun run build:daemon # cross-compile mindwired → npm/mindwire-daemon-<platform>-<arch>/ (needs Go)
|
|
184
181
|
```
|
|
185
182
|
|
|
186
|
-
`build:daemon` produces the per-platform daemon packages and syncs the main package's
|
|
187
|
-
`optionalDependencies`. **Release order:** publish every `npm/mindwire-daemon-*` package first,
|
|
188
|
-
then publish `mindwire` — its optional dependencies must already exist on the registry at the
|
|
189
|
-
same version.
|
|
190
|
-
|
|
191
183
|
License: Apache-2.0.
|
package/dist/client.d.ts
CHANGED
|
@@ -102,7 +102,7 @@ export declare class Mindwire {
|
|
|
102
102
|
* call before the target has connected (nothing to reap yet).
|
|
103
103
|
*/
|
|
104
104
|
close(): Promise<void>;
|
|
105
|
-
/** `GET /healthz` —
|
|
105
|
+
/** `GET /healthz` — authenticated liveness check. Resolves with the daemon's health payload when it is up. */
|
|
106
106
|
health(): Promise<Health>;
|
|
107
107
|
/**
|
|
108
108
|
* `GET /stats` — the daemon **process's** resource snapshot (heap in use, memory reserved from the
|
package/dist/index.cjs
CHANGED
|
@@ -469,7 +469,7 @@ var Run = class _Run {
|
|
|
469
469
|
};
|
|
470
470
|
|
|
471
471
|
// src/version.ts
|
|
472
|
-
var SDK_VERSION = "0.1.
|
|
472
|
+
var SDK_VERSION = "0.1.3" ;
|
|
473
473
|
|
|
474
474
|
// src/daemon-binary.ts
|
|
475
475
|
function supported(platform, arch) {
|
|
@@ -580,17 +580,19 @@ async function spawnDaemon(opts) {
|
|
|
580
580
|
const { spawn } = await import('child_process');
|
|
581
581
|
const net = await import('net');
|
|
582
582
|
const proc = serverRuntime().process;
|
|
583
|
+
const { randomBytes } = await import('crypto');
|
|
583
584
|
const resolved = await resolveBinary(opts.bin);
|
|
584
585
|
const bin = resolved.bin;
|
|
585
586
|
const port = await freePort(net);
|
|
586
587
|
const baseUrl = `http://127.0.0.1:${port}`;
|
|
588
|
+
const token = randomBytes(32).toString("hex");
|
|
587
589
|
const child = spawn(bin, [], {
|
|
588
590
|
env: {
|
|
589
591
|
...proc.env,
|
|
590
592
|
ADDR: `127.0.0.1:${port}`,
|
|
591
593
|
AGENT_CWD: opts.cwd ?? proc.cwd(),
|
|
592
594
|
STATE_PATH: opts.statePath ?? ".mindwire-state.json",
|
|
593
|
-
DAEMON_TOKEN:
|
|
595
|
+
DAEMON_TOKEN: token
|
|
594
596
|
},
|
|
595
597
|
stdio: "ignore"
|
|
596
598
|
});
|
|
@@ -609,8 +611,8 @@ async function spawnDaemon(opts) {
|
|
|
609
611
|
stop();
|
|
610
612
|
proc.exit?.(130);
|
|
611
613
|
});
|
|
612
|
-
await waitHealthy(baseUrl, () => spawnError, resolved);
|
|
613
|
-
return { baseUrl, stop };
|
|
614
|
+
await waitHealthy(baseUrl, token, () => spawnError, resolved);
|
|
615
|
+
return { baseUrl, token, stop };
|
|
614
616
|
}
|
|
615
617
|
async function resolveBinary(explicit) {
|
|
616
618
|
const fs = await import('fs');
|
|
@@ -645,7 +647,7 @@ function daemonStartError(resolved, cause) {
|
|
|
645
647
|
" \u2022 check GitHub Release access so the SDK can download its matching daemon binary; or",
|
|
646
648
|
" \u2022 set MINDWIRE_DAEMON to a `mindwired` binary; or",
|
|
647
649
|
" \u2022 pass { baseUrl } to connect to a daemon you run yourself.",
|
|
648
|
-
" \u2022 Working inside the mindwire monorepo? Run `
|
|
650
|
+
" \u2022 Working inside the mindwire monorepo? Run `go build -o /tmp/mindwired ./daemon/cmd/daemon` and set MINDWIRE_DAEMON.",
|
|
649
651
|
`(underlying error: ${cause.message})`
|
|
650
652
|
].join("\n");
|
|
651
653
|
}
|
|
@@ -662,7 +664,7 @@ function freePort(net) {
|
|
|
662
664
|
});
|
|
663
665
|
});
|
|
664
666
|
}
|
|
665
|
-
async function waitHealthy(baseUrl, getError, resolved, timeoutMs = 15e3) {
|
|
667
|
+
async function waitHealthy(baseUrl, token, getError, resolved, timeoutMs = 15e3) {
|
|
666
668
|
const deadline = Date.now() + timeoutMs;
|
|
667
669
|
while (Date.now() < deadline) {
|
|
668
670
|
const err = getError();
|
|
@@ -670,7 +672,7 @@ async function waitHealthy(baseUrl, getError, resolved, timeoutMs = 15e3) {
|
|
|
670
672
|
throw new MindwireError(daemonStartError(resolved, err), { cause: err });
|
|
671
673
|
}
|
|
672
674
|
try {
|
|
673
|
-
const res = await fetch(`${baseUrl}/healthz`);
|
|
675
|
+
const res = await fetch(`${baseUrl}/healthz`, { headers: { Authorization: `Bearer ${token}` } });
|
|
674
676
|
if (res.ok) return;
|
|
675
677
|
} catch {
|
|
676
678
|
}
|
|
@@ -805,7 +807,7 @@ var Mindwire = class _Mindwire {
|
|
|
805
807
|
if (h) await h.stop();
|
|
806
808
|
}
|
|
807
809
|
// ---- health & catalog ----------------------------------------------------
|
|
808
|
-
/** `GET /healthz` —
|
|
810
|
+
/** `GET /healthz` — authenticated liveness check. Resolves with the daemon's health payload when it is up. */
|
|
809
811
|
health() {
|
|
810
812
|
return this.http.request("GET", "/healthz");
|
|
811
813
|
}
|
|
@@ -1307,11 +1309,12 @@ var STATE = `${DAEMON_DIR}/agent-state.json`;
|
|
|
1307
1309
|
var LOG = `${DAEMON_DIR}/daemon.log`;
|
|
1308
1310
|
async function ensureDaemon(host, cfg) {
|
|
1309
1311
|
const emit2 = makeEmit(cfg);
|
|
1312
|
+
const token = cfg.token ?? (await import('crypto')).randomBytes(32).toString("hex");
|
|
1310
1313
|
try {
|
|
1311
1314
|
await waitHostReady(host);
|
|
1312
1315
|
emit2({ phase: "connect", message: "runtime ready" });
|
|
1313
1316
|
const desired = cfg.desiredVersion ?? SDK_VERSION;
|
|
1314
|
-
const health = await probeHealth(host, cfg.port);
|
|
1317
|
+
const health = await probeHealth(host, cfg.port, token);
|
|
1315
1318
|
if (health.reachable) {
|
|
1316
1319
|
emit2({
|
|
1317
1320
|
phase: "probe",
|
|
@@ -1319,31 +1322,32 @@ async function ensureDaemon(host, cfg) {
|
|
|
1319
1322
|
version: health.version
|
|
1320
1323
|
});
|
|
1321
1324
|
const upToDate = health.version !== void 0 && health.version === desired;
|
|
1322
|
-
if (upToDate || !cfg.autoUpdate) {
|
|
1325
|
+
if (!cfg.forceDeploy && (upToDate || !cfg.autoUpdate)) {
|
|
1323
1326
|
emit2({
|
|
1324
1327
|
phase: "skip",
|
|
1325
1328
|
message: upToDate ? `daemon already at v${desired}` : "keeping the running daemon",
|
|
1326
1329
|
version: health.version
|
|
1327
1330
|
});
|
|
1328
|
-
return;
|
|
1331
|
+
return token;
|
|
1329
1332
|
}
|
|
1330
1333
|
} else {
|
|
1331
1334
|
emit2({ phase: "probe", message: "no daemon reachable; deploying" });
|
|
1332
1335
|
}
|
|
1333
|
-
await deploy(host, cfg, emit2);
|
|
1336
|
+
await deploy(host, cfg, emit2, token);
|
|
1337
|
+
return token;
|
|
1334
1338
|
} catch (err) {
|
|
1335
1339
|
emit2({ phase: "error", message: "ensure failed", error: err instanceof Error ? err.message : String(err) });
|
|
1336
1340
|
throw err;
|
|
1337
1341
|
}
|
|
1338
1342
|
}
|
|
1339
|
-
async function deploy(host, cfg, emit2) {
|
|
1343
|
+
async function deploy(host, cfg, emit2, token) {
|
|
1340
1344
|
const arch = await probeArch(host);
|
|
1341
1345
|
const desired = cfg.desiredVersion ?? SDK_VERSION;
|
|
1342
1346
|
let acquire = "";
|
|
1343
1347
|
if (cfg.daemonBin) {
|
|
1344
1348
|
const binPath = await resolveLinuxDaemon(cfg.daemonBin, arch);
|
|
1345
1349
|
const bytes = await readBytes(binPath);
|
|
1346
|
-
emit2({ phase: "upload", message: `uploading daemon (${arch}, ${bytes.length}
|
|
1350
|
+
emit2({ phase: "upload", message: `uploading daemon (${arch}, ${formatMiB(bytes.length)})`, arch, bytes: bytes.length });
|
|
1347
1351
|
await host.putFile(BIN_NEW, bytes, { mode: "0755" });
|
|
1348
1352
|
} else {
|
|
1349
1353
|
if (!/^[0-9]+\.[0-9]+\.[0-9]+(?:-[0-9A-Za-z.-]+)?$/.test(desired)) {
|
|
@@ -1386,9 +1390,9 @@ async function deploy(host, cfg, emit2) {
|
|
|
1386
1390
|
`mv -f ${BIN_NEW} ${BIN}`,
|
|
1387
1391
|
`chmod +x ${BIN}`,
|
|
1388
1392
|
// Detach so the daemon survives this exec's shell exiting. ADDR=":<port>" binds 0.0.0.0.
|
|
1389
|
-
`setsid nohup env ADDR=":${cfg.port}" AGENT_TYPE="${cfg.agent}" AGENT_CWD="${cfg.agentCwd}" STATE_PATH="${STATE}" DAEMON_TOKEN
|
|
1393
|
+
`setsid nohup env ADDR=":${cfg.port}" AGENT_TYPE="${cfg.agent}" AGENT_CWD="${cfg.agentCwd}" STATE_PATH="${STATE}" DAEMON_TOKEN=${shellQuote(token)} ${BIN} > ${LOG} 2>&1 < /dev/null &`,
|
|
1390
1394
|
// Health-poll from inside the VM (loopback) and emit a marker — exit codes are unreliable here.
|
|
1391
|
-
`for i in $(seq 1 60); do curl -fsS --max-time 2 http://127.0.0.1:${cfg.port}/healthz >/dev/null 2>&1 && { echo MINDWIRE_READY; exit 0; }; sleep 0.25; done`,
|
|
1395
|
+
`for i in $(seq 1 60); do curl -fsS --max-time 2 -H ${shellQuote(`Authorization: Bearer ${token}`)} http://127.0.0.1:${cfg.port}/healthz >/dev/null 2>&1 && { echo MINDWIRE_READY; exit 0; }; sleep 0.25; done`,
|
|
1392
1396
|
"echo MINDWIRE_FAIL",
|
|
1393
1397
|
`tail -n 40 ${LOG} 2>/dev/null || true`
|
|
1394
1398
|
].join("\n");
|
|
@@ -1402,8 +1406,8 @@ async function deploy(host, cfg, emit2) {
|
|
|
1402
1406
|
}
|
|
1403
1407
|
emit2({ phase: "ready", message: "daemon deployed and healthy" });
|
|
1404
1408
|
}
|
|
1405
|
-
async function probeHealth(host, port) {
|
|
1406
|
-
const script = `out=$(curl -fsS --max-time 3 http://127.0.0.1:${port}/healthz 2>/dev/null) && printf '<<MW_H>>%s<<MW_H>>' "$out" || printf '<<MW_H>><<MW_H>>'`;
|
|
1409
|
+
async function probeHealth(host, port, token) {
|
|
1410
|
+
const script = `out=$(curl -fsS --max-time 3 -H ${shellQuote(`Authorization: Bearer ${token}`)} http://127.0.0.1:${port}/healthz 2>/dev/null) && printf '<<MW_H>>%s<<MW_H>>' "$out" || printf '<<MW_H>><<MW_H>>'`;
|
|
1407
1411
|
const res = await host.exec(["bash", "-lc", script], { timeoutSeconds: 15 });
|
|
1408
1412
|
const body = ((res.stdout ?? "").match(/<<MW_H>>([\s\S]*?)<<MW_H>>/)?.[1] ?? "").trim();
|
|
1409
1413
|
if (!body) return { reachable: false };
|
|
@@ -1439,10 +1443,11 @@ async function waitHostReady(host, timeoutMs = 6e4) {
|
|
|
1439
1443
|
async function resolveLinuxDaemon(explicit, arch) {
|
|
1440
1444
|
const fs = await import('fs');
|
|
1441
1445
|
if (explicit) {
|
|
1442
|
-
|
|
1443
|
-
|
|
1446
|
+
const resolved = explicit.replaceAll("{arch}", arch);
|
|
1447
|
+
if (!fs.existsSync(resolved)) {
|
|
1448
|
+
throw new MindwireError(`mindwire: sandbox daemonBin not found at ${resolved}`);
|
|
1444
1449
|
}
|
|
1445
|
-
return
|
|
1450
|
+
return resolved;
|
|
1446
1451
|
}
|
|
1447
1452
|
return ensureDaemonBinary({ platform: "linux", arch: arch === "arm64" ? "arm64" : "x64" });
|
|
1448
1453
|
}
|
|
@@ -1456,6 +1461,9 @@ function sleep(ms) {
|
|
|
1456
1461
|
function shellQuote(value) {
|
|
1457
1462
|
return `'${value.replace(/'/g, "'\\''")}'`;
|
|
1458
1463
|
}
|
|
1464
|
+
function formatMiB(bytes) {
|
|
1465
|
+
return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
|
|
1466
|
+
}
|
|
1459
1467
|
|
|
1460
1468
|
// src/target/container.ts
|
|
1461
1469
|
async function provisionContainer(host, cfg, onLog) {
|
|
@@ -1469,7 +1477,7 @@ async function provisionContainer(host, cfg, onLog) {
|
|
|
1469
1477
|
};
|
|
1470
1478
|
await ensureDockerReady(host, cfg.install ?? "never", emit2);
|
|
1471
1479
|
const { containerId, hostPort, created } = await runContainer(host, cfg, emit2);
|
|
1472
|
-
await ensureDaemon(new ContainerHost(host, containerId), {
|
|
1480
|
+
const token = await ensureDaemon(new ContainerHost(host, containerId), {
|
|
1473
1481
|
port: cfg.daemonPort,
|
|
1474
1482
|
agent: cfg.agent,
|
|
1475
1483
|
agentCwd: cfg.agentCwd,
|
|
@@ -1488,7 +1496,7 @@ async function provisionContainer(host, cfg, onLog) {
|
|
|
1488
1496
|
} catch {
|
|
1489
1497
|
}
|
|
1490
1498
|
};
|
|
1491
|
-
return { containerId, hostPort, stop };
|
|
1499
|
+
return { containerId, hostPort, token, stop };
|
|
1492
1500
|
}
|
|
1493
1501
|
var ContainerHost = class {
|
|
1494
1502
|
constructor(base, containerId) {
|
|
@@ -1724,7 +1732,7 @@ async function connectSsh(opts, spec) {
|
|
|
1724
1732
|
});
|
|
1725
1733
|
}
|
|
1726
1734
|
async function provisionSsh(client, makeTunnel, cfg) {
|
|
1727
|
-
await ensureDaemon(new SshHost(client), {
|
|
1735
|
+
const token = await ensureDaemon(new SshHost(client), {
|
|
1728
1736
|
port: cfg.daemonPort,
|
|
1729
1737
|
agent: cfg.agent,
|
|
1730
1738
|
agentCwd: cfg.agentCwd,
|
|
@@ -1747,7 +1755,7 @@ async function provisionSsh(client, makeTunnel, cfg) {
|
|
|
1747
1755
|
}
|
|
1748
1756
|
}
|
|
1749
1757
|
};
|
|
1750
|
-
return { id: cfg.id, baseUrl: tunnel.baseUrl, stop };
|
|
1758
|
+
return { id: cfg.id, baseUrl: tunnel.baseUrl, token, stop };
|
|
1751
1759
|
}
|
|
1752
1760
|
async function provisionSshContainer(client, makeTunnel, cfg) {
|
|
1753
1761
|
const container = await provisionContainer(
|
|
@@ -1784,7 +1792,7 @@ async function provisionSshContainer(client, makeTunnel, cfg) {
|
|
|
1784
1792
|
}
|
|
1785
1793
|
}
|
|
1786
1794
|
};
|
|
1787
|
-
return { id: cfg.id, baseUrl: tunnel.baseUrl, stop };
|
|
1795
|
+
return { id: cfg.id, baseUrl: tunnel.baseUrl, token: container.token, stop };
|
|
1788
1796
|
}
|
|
1789
1797
|
async function openConnection(mod, opts) {
|
|
1790
1798
|
const auth = await buildAuth(opts);
|
|
@@ -1917,6 +1925,7 @@ function docker(config = {}) {
|
|
|
1917
1925
|
var DEFAULT_PORT2 = 8790;
|
|
1918
1926
|
var DEFAULT_AGENT2 = "claude-code";
|
|
1919
1927
|
var DEFAULT_AGENT_CWD2 = "/root";
|
|
1928
|
+
var DEFAULT_RUNTIME_IMAGE = `ghcr.io/oblien/mindwire-runtime:${SDK_VERSION}`;
|
|
1920
1929
|
var DockerHost = class {
|
|
1921
1930
|
constructor(container) {
|
|
1922
1931
|
this.container = container;
|
|
@@ -1971,15 +1980,14 @@ async function provisionDocker(docker2, config = {}, onLog) {
|
|
|
1971
1980
|
if (config.container !== void 0) {
|
|
1972
1981
|
container = docker2.getContainer(config.container);
|
|
1973
1982
|
} else {
|
|
1974
|
-
|
|
1975
|
-
|
|
1976
|
-
"mindwire: the Docker adapter needs an image or a container \u2014 pass docker({ image }) to create one, or docker({ container }) to attach to an existing one."
|
|
1977
|
-
);
|
|
1978
|
-
}
|
|
1983
|
+
const image = config.image ?? DEFAULT_RUNTIME_IMAGE;
|
|
1984
|
+
await ensureImage(docker2, image, config.pull ?? "missing", onLog);
|
|
1979
1985
|
const portKey = `${port}/tcp`;
|
|
1980
1986
|
container = await docker2.createContainer({
|
|
1981
|
-
|
|
1982
|
-
|
|
1987
|
+
// The runtime image starts mindwired itself. Do not replace its command: doing so turns the
|
|
1988
|
+
// image ENTRYPOINT into `mindwired sleep infinity` and prevents the runtime from starting.
|
|
1989
|
+
Image: image,
|
|
1990
|
+
Env: [`ADDR=:${port}`, `AGENT_TYPE=${agent}`, `AGENT_CWD=${agentCwd}`],
|
|
1983
1991
|
Tty: false,
|
|
1984
1992
|
ExposedPorts: { [portKey]: {} },
|
|
1985
1993
|
HostConfig: { PortBindings: { [portKey]: [{ HostPort: "0" }] } },
|
|
@@ -1996,7 +2004,7 @@ async function provisionDocker(docker2, config = {}, onLog) {
|
|
|
1996
2004
|
`mindwire: the Docker container does not publish a host port for ${port}/tcp. ` + (created ? "This is unexpected for a container mindwire created." : "Attach to a container started with that port published (e.g. `-p 0:" + port + "`).")
|
|
1997
2005
|
);
|
|
1998
2006
|
}
|
|
1999
|
-
await ensureDaemon(new DockerHost(container), {
|
|
2007
|
+
const token = await ensureDaemon(new DockerHost(container), {
|
|
2000
2008
|
port,
|
|
2001
2009
|
agent,
|
|
2002
2010
|
agentCwd,
|
|
@@ -2015,7 +2023,37 @@ async function provisionDocker(docker2, config = {}, onLog) {
|
|
|
2015
2023
|
} catch {
|
|
2016
2024
|
}
|
|
2017
2025
|
};
|
|
2018
|
-
return { id: container.id, baseUrl: `http://127.0.0.1:${hostPort}`, stop };
|
|
2026
|
+
return { id: container.id, baseUrl: `http://127.0.0.1:${hostPort}`, token, stop };
|
|
2027
|
+
}
|
|
2028
|
+
async function ensureImage(docker2, image, policy, onLog) {
|
|
2029
|
+
const emit2 = (message, error) => {
|
|
2030
|
+
try {
|
|
2031
|
+
onLog?.({ phase: "pull", target: "docker", message, ...error ? { error } : {} });
|
|
2032
|
+
} catch {
|
|
2033
|
+
}
|
|
2034
|
+
};
|
|
2035
|
+
if (policy === "never") return;
|
|
2036
|
+
if (policy === "missing") {
|
|
2037
|
+
try {
|
|
2038
|
+
await docker2.getImage(image).inspect();
|
|
2039
|
+
return;
|
|
2040
|
+
} catch {
|
|
2041
|
+
}
|
|
2042
|
+
}
|
|
2043
|
+
emit2(`pulling runtime image ${image}`);
|
|
2044
|
+
try {
|
|
2045
|
+
const stream = await docker2.pull(image);
|
|
2046
|
+
await new Promise((resolve, reject) => {
|
|
2047
|
+
stream.on("end", () => resolve());
|
|
2048
|
+
stream.on("close", () => resolve());
|
|
2049
|
+
stream.on("error", (error) => reject(error));
|
|
2050
|
+
});
|
|
2051
|
+
emit2(`runtime image ready: ${image}`);
|
|
2052
|
+
} catch (error) {
|
|
2053
|
+
const detail = error instanceof Error ? error.message : String(error);
|
|
2054
|
+
emit2(`failed to pull runtime image ${image}`, detail);
|
|
2055
|
+
throw new MindwireError(`mindwire: failed to pull Docker image ${image}: ${detail}`, { cause: error });
|
|
2056
|
+
}
|
|
2019
2057
|
}
|
|
2020
2058
|
function tarSingleFile(name, data, mode) {
|
|
2021
2059
|
const header = Buffer.alloc(512, 0);
|
|
@@ -2122,26 +2160,31 @@ async function provisionOblien(client, config = {}, onLog) {
|
|
|
2122
2160
|
const ws = await client.workspaces.create(params);
|
|
2123
2161
|
workspaceId = ws.id;
|
|
2124
2162
|
}
|
|
2163
|
+
await config.onWorkspace?.(workspaceId);
|
|
2125
2164
|
const handle = client.workspace(workspaceId);
|
|
2126
2165
|
await handle.start().catch(() => {
|
|
2127
2166
|
});
|
|
2128
2167
|
let rt = await handle.runtime();
|
|
2129
|
-
await ensureDaemon(new OblienHost(rt), {
|
|
2168
|
+
const token = await ensureDaemon(new OblienHost(rt), {
|
|
2130
2169
|
port,
|
|
2131
2170
|
agent,
|
|
2132
2171
|
agentCwd,
|
|
2133
2172
|
daemonBin: config.daemonBin,
|
|
2134
2173
|
autoUpdate: config.autoUpdate,
|
|
2174
|
+
forceDeploy: config.forceDeploy,
|
|
2135
2175
|
target: "oblien",
|
|
2136
2176
|
onLog
|
|
2137
2177
|
});
|
|
2138
2178
|
const fetchImpl = async (url, init) => {
|
|
2139
2179
|
const { pathname, search } = new URL(url);
|
|
2140
2180
|
const path = pathname + search;
|
|
2141
|
-
|
|
2181
|
+
const headers = new Headers(init?.headers);
|
|
2182
|
+
headers.set("X-Mindwire-Token", token);
|
|
2183
|
+
headers.delete("Authorization");
|
|
2184
|
+
let res = await rt.proxy(port).fetch(path, { ...init, headers });
|
|
2142
2185
|
if (res.status === 401) {
|
|
2143
2186
|
rt = await handle.runtime({ force: true });
|
|
2144
|
-
res = await rt.proxy(port).fetch(path, init);
|
|
2187
|
+
res = await rt.proxy(port).fetch(path, { ...init, headers });
|
|
2145
2188
|
}
|
|
2146
2189
|
return res;
|
|
2147
2190
|
};
|
|
@@ -2152,7 +2195,7 @@ async function provisionOblien(client, config = {}, onLog) {
|
|
|
2152
2195
|
} catch {
|
|
2153
2196
|
}
|
|
2154
2197
|
};
|
|
2155
|
-
return { id: workspaceId, workspaceId, baseUrl: SENTINEL_BASE, fetch: fetchImpl, stop };
|
|
2198
|
+
return { id: workspaceId, workspaceId, baseUrl: SENTINEL_BASE, token, fetch: fetchImpl, stop };
|
|
2156
2199
|
}
|
|
2157
2200
|
async function importOblien() {
|
|
2158
2201
|
const spec = "oblien";
|