mindwire 0.1.11 → 0.1.14
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 +73 -0
- package/dist/client.d.ts +5 -0
- package/dist/index.cjs +271 -32
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +4 -0
- package/dist/index.js +268 -33
- package/dist/index.js.map +1 -1
- package/dist/run.d.ts +3 -3
- package/dist/surfaces.d.ts +177 -0
- package/dist/target/host.d.ts +5 -3
- package/dist/target/oblien.d.ts +1 -1
- package/dist/target/ssh.d.ts +1 -1
- package/dist/types.d.ts +46 -5
- package/dist/workspace.d.ts +149 -0
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -32,3 +32,7 @@ export { catalogProviders, catalogProvider, catalogModels, lookupModel, loadCata
|
|
|
32
32
|
export type { CatalogOptions } from "./catalog/index.js";
|
|
33
33
|
export { MindwireError, ApiError, RunFailedError, TimeoutError } from "./errors.js";
|
|
34
34
|
export * from "./types.js";
|
|
35
|
+
export { WorkspaceApi, WorkspaceCollection, ProjectOperationsApi } from "./workspace.js";
|
|
36
|
+
export type { ProjectRequest, ProjectAuth, ProjectOperation, ProjectRemoveRequest } from "./workspace.js";
|
|
37
|
+
export type { WorkspaceRecord, WorkspaceAgent, WorkspaceProject, WorkspaceChat, WorkspaceKind, WorkspaceInput, WorkspaceImport, WorkspaceSnapshot } from "./workspace.js";
|
|
38
|
+
export * from "./surfaces.js";
|
package/dist/index.js
CHANGED
|
@@ -286,6 +286,8 @@ async function* readSSE(body, signal) {
|
|
|
286
286
|
if (payload !== void 0) yield JSON.parse(payload);
|
|
287
287
|
} finally {
|
|
288
288
|
if (signal) signal.removeEventListener("abort", onAbort);
|
|
289
|
+
await reader.cancel().catch(() => {
|
|
290
|
+
});
|
|
289
291
|
reader.releaseLock();
|
|
290
292
|
}
|
|
291
293
|
}
|
|
@@ -410,9 +412,9 @@ var Run = class _Run {
|
|
|
410
412
|
});
|
|
411
413
|
}
|
|
412
414
|
/**
|
|
413
|
-
* Switch the permission mode
|
|
414
|
-
*
|
|
415
|
-
*
|
|
415
|
+
* Switch the live permission mode using a value from the agent's settings schema.
|
|
416
|
+
* Resolves after the harness acknowledges the change; rejects if it cannot apply it.
|
|
417
|
+
* Requires `setPermissionMode`. Codex settings apply on the next turn instead.
|
|
416
418
|
*/
|
|
417
419
|
async setPermissionMode(mode) {
|
|
418
420
|
await this.http.request("POST", `/runs/${encodeURIComponent(this.id)}/set-permission-mode`, {
|
|
@@ -472,8 +474,174 @@ var Run = class _Run {
|
|
|
472
474
|
}
|
|
473
475
|
};
|
|
474
476
|
|
|
477
|
+
// src/workspace.ts
|
|
478
|
+
var ProjectOperationsApi = class {
|
|
479
|
+
constructor(mw) {
|
|
480
|
+
this.mw = mw;
|
|
481
|
+
}
|
|
482
|
+
mw;
|
|
483
|
+
async list(activeOnly = false) {
|
|
484
|
+
const response = await this.mw.http.request("GET", "/workspace/operations", {
|
|
485
|
+
query: { active: activeOnly }
|
|
486
|
+
});
|
|
487
|
+
return response.operations;
|
|
488
|
+
}
|
|
489
|
+
get(id) {
|
|
490
|
+
return this.mw.http.request("GET", `/workspace/operations/${encodeURIComponent(id)}`);
|
|
491
|
+
}
|
|
492
|
+
cancel(id) {
|
|
493
|
+
return this.mw.http.request("POST", `/workspace/operations/${encodeURIComponent(id)}/cancel`);
|
|
494
|
+
}
|
|
495
|
+
retry(id, auth) {
|
|
496
|
+
return this.mw.http.request("POST", `/workspace/operations/${encodeURIComponent(id)}/retry`, { body: { auth } });
|
|
497
|
+
}
|
|
498
|
+
/** The first event is the current snapshot, then live changes. Reconnecting never replays old
|
|
499
|
+
* progress. Breaking the loop/aborting detaches the observer; cancel(id) explicitly stops work.
|
|
500
|
+
*/
|
|
501
|
+
async *watch(id, opts = {}) {
|
|
502
|
+
const controller = new AbortController();
|
|
503
|
+
const abort = () => controller.abort();
|
|
504
|
+
opts.signal?.addEventListener("abort", abort, { once: true });
|
|
505
|
+
if (opts.signal?.aborted) controller.abort();
|
|
506
|
+
try {
|
|
507
|
+
const response = await this.mw.http.open("GET", `/workspace/operations/${encodeURIComponent(id)}/stream`, {
|
|
508
|
+
signal: controller.signal
|
|
509
|
+
});
|
|
510
|
+
let sequence = -1;
|
|
511
|
+
for await (const operation of readSSE(response.body, controller.signal)) {
|
|
512
|
+
if (operation.sequence > sequence) {
|
|
513
|
+
sequence = operation.sequence;
|
|
514
|
+
yield operation;
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
} finally {
|
|
518
|
+
controller.abort();
|
|
519
|
+
opts.signal?.removeEventListener("abort", abort);
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
};
|
|
523
|
+
var WorkspaceCollection = class {
|
|
524
|
+
constructor(mw, kind) {
|
|
525
|
+
this.mw = mw;
|
|
526
|
+
this.kind = kind;
|
|
527
|
+
}
|
|
528
|
+
mw;
|
|
529
|
+
kind;
|
|
530
|
+
/** Create with a stable client-generated ID. For updates, supply the record's last revision. */
|
|
531
|
+
put(id, record, expectedRevision) {
|
|
532
|
+
return this.mw.http.request("PUT", `/workspace/${this.kind}/${encodeURIComponent(id)}`, {
|
|
533
|
+
body: { record, expectedRevision }
|
|
534
|
+
});
|
|
535
|
+
}
|
|
536
|
+
/** Remove membership and dependent chat links. Files and native transcripts are retained.
|
|
537
|
+
* Use deleteChat() for an explicit transcript purge. Running chats reject removal with 409.
|
|
538
|
+
*/
|
|
539
|
+
delete(id, revision) {
|
|
540
|
+
return this.mw.http.request("DELETE", `/workspace/${this.kind}/${encodeURIComponent(id)}`, {
|
|
541
|
+
query: { revision }
|
|
542
|
+
});
|
|
543
|
+
}
|
|
544
|
+
};
|
|
545
|
+
var WorkspaceApi = class {
|
|
546
|
+
constructor(mw) {
|
|
547
|
+
this.mw = mw;
|
|
548
|
+
this.operations = new ProjectOperationsApi(mw);
|
|
549
|
+
this.agents = new WorkspaceCollection(mw, "agents");
|
|
550
|
+
this.projects = new WorkspaceCollection(mw, "projects");
|
|
551
|
+
this.chats = new WorkspaceCollection(mw, "chats");
|
|
552
|
+
}
|
|
553
|
+
mw;
|
|
554
|
+
operations;
|
|
555
|
+
agents;
|
|
556
|
+
projects;
|
|
557
|
+
chats;
|
|
558
|
+
snapshot() {
|
|
559
|
+
return this.mw.http.request("GET", "/workspace");
|
|
560
|
+
}
|
|
561
|
+
/** Start an operation owned by the daemon. The same ID/payload returns the existing operation. */
|
|
562
|
+
createProject(request) {
|
|
563
|
+
return this.mw.http.request("POST", "/workspace/projects", { body: request });
|
|
564
|
+
}
|
|
565
|
+
/** Permanently remove the confirmed project's directory and membership.
|
|
566
|
+
* projects.delete() retains files. Native harness transcripts are not purged.
|
|
567
|
+
*/
|
|
568
|
+
removeProjectFiles(id, request) {
|
|
569
|
+
return this.mw.http.request("POST", `/workspace/projects/${encodeURIComponent(id)}/remove`, { body: request });
|
|
570
|
+
}
|
|
571
|
+
/** Incremental reconciliation. Pass the previous identity to detect a replaced/restored workspace.
|
|
572
|
+
* A 409 requires fetching snapshot() again; never apply a delta to a different registry.
|
|
573
|
+
*/
|
|
574
|
+
changes(since, workspaceId) {
|
|
575
|
+
return this.mw.http.request("GET", "/workspace/changes", { query: { since, workspaceId } });
|
|
576
|
+
}
|
|
577
|
+
/** Import legacy metadata before replacing a local cache. Safe to repeat after interruption. */
|
|
578
|
+
import(records) {
|
|
579
|
+
return this.mw.http.request("POST", "/workspace/import", { body: records });
|
|
580
|
+
}
|
|
581
|
+
};
|
|
582
|
+
|
|
583
|
+
// src/surfaces.ts
|
|
584
|
+
var SurfacesApi = class {
|
|
585
|
+
constructor(mw) {
|
|
586
|
+
this.mw = mw;
|
|
587
|
+
}
|
|
588
|
+
mw;
|
|
589
|
+
list() {
|
|
590
|
+
return this.mw.http.request("GET", "/surfaces");
|
|
591
|
+
}
|
|
592
|
+
status(refresh = false) {
|
|
593
|
+
return this.mw.http.request("GET", "/surfaces/desktop", { query: { refresh } });
|
|
594
|
+
}
|
|
595
|
+
bind(binding) {
|
|
596
|
+
return this.mw.http.request("PUT", "/surfaces/desktop/binding", { body: binding });
|
|
597
|
+
}
|
|
598
|
+
open(request) {
|
|
599
|
+
return this.mw.http.request("POST", "/surfaces/desktop/sessions", { body: request });
|
|
600
|
+
}
|
|
601
|
+
control(id, request) {
|
|
602
|
+
return this.mw.http.request("POST", `/surfaces/desktop/sessions/${encodeURIComponent(id)}/control`, { body: request });
|
|
603
|
+
}
|
|
604
|
+
close(id) {
|
|
605
|
+
return this.mw.http.request("DELETE", `/surfaces/desktop/sessions/${encodeURIComponent(id)}`);
|
|
606
|
+
}
|
|
607
|
+
capture(id) {
|
|
608
|
+
return this.mw.http.request("POST", `/surfaces/desktop/sessions/${encodeURIComponent(id)}/captures`);
|
|
609
|
+
}
|
|
610
|
+
action(request) {
|
|
611
|
+
return this.mw.http.request("POST", "/surfaces/desktop/actions", { body: request });
|
|
612
|
+
}
|
|
613
|
+
receipt(id) {
|
|
614
|
+
return this.mw.http.request("GET", `/surfaces/desktop/actions/${encodeURIComponent(id)}`);
|
|
615
|
+
}
|
|
616
|
+
artifact(id) {
|
|
617
|
+
return this.mw.http.request("GET", `/artifacts/${encodeURIComponent(id)}`);
|
|
618
|
+
}
|
|
619
|
+
/** Every connection starts with the current snapshot, then revisions. Never replays input. */
|
|
620
|
+
async *watch(opts = {}) {
|
|
621
|
+
const controller = new AbortController();
|
|
622
|
+
const abort = () => controller.abort();
|
|
623
|
+
opts.signal?.addEventListener("abort", abort, { once: true });
|
|
624
|
+
if (opts.signal?.aborted) controller.abort();
|
|
625
|
+
try {
|
|
626
|
+
const response = await this.mw.http.open("GET", "/surfaces/desktop/events", { signal: controller.signal });
|
|
627
|
+
let instance;
|
|
628
|
+
let revision = -1;
|
|
629
|
+
for await (const snapshot of readSSE(response.body, controller.signal)) {
|
|
630
|
+
if (instance !== snapshot.instanceId || snapshot.revision > revision) {
|
|
631
|
+
instance = snapshot.instanceId;
|
|
632
|
+
revision = snapshot.revision;
|
|
633
|
+
yield snapshot;
|
|
634
|
+
}
|
|
635
|
+
}
|
|
636
|
+
} finally {
|
|
637
|
+
controller.abort();
|
|
638
|
+
opts.signal?.removeEventListener("abort", abort);
|
|
639
|
+
}
|
|
640
|
+
}
|
|
641
|
+
};
|
|
642
|
+
|
|
475
643
|
// src/version.ts
|
|
476
|
-
var SDK_VERSION = "0.1.
|
|
644
|
+
var SDK_VERSION = "0.1.14" ;
|
|
477
645
|
|
|
478
646
|
// src/daemon-binary.ts
|
|
479
647
|
function supported(platform, arch) {
|
|
@@ -731,6 +899,9 @@ function remote(baseUrl, opts = {}) {
|
|
|
731
899
|
// src/client.ts
|
|
732
900
|
var handleByTransport = /* @__PURE__ */ new WeakMap();
|
|
733
901
|
var Mindwire = class _Mindwire {
|
|
902
|
+
/** Workspace registry: saved agent profiles, projects and chat relationships. */
|
|
903
|
+
workspace;
|
|
904
|
+
surfaces;
|
|
734
905
|
http;
|
|
735
906
|
/** The default agent type applied to agent-scoped calls, if set. */
|
|
736
907
|
defaultAgent;
|
|
@@ -764,6 +935,8 @@ var Mindwire = class _Mindwire {
|
|
|
764
935
|
}
|
|
765
936
|
});
|
|
766
937
|
this.defaultAgent = opts.agent;
|
|
938
|
+
this.workspace = new WorkspaceApi(this);
|
|
939
|
+
this.surfaces = new SurfacesApi(this);
|
|
767
940
|
this.auth = new AuthApi(this);
|
|
768
941
|
this.prompts = new PromptsApi(this);
|
|
769
942
|
this.mcp = new McpApi(this);
|
|
@@ -784,6 +957,8 @@ var Mindwire = class _Mindwire {
|
|
|
784
957
|
const clone = Object.create(_Mindwire.prototype);
|
|
785
958
|
clone.http = this.http;
|
|
786
959
|
clone.defaultAgent = agent;
|
|
960
|
+
clone.workspace = new WorkspaceApi(clone);
|
|
961
|
+
clone.surfaces = new SurfacesApi(clone);
|
|
787
962
|
clone.auth = new AuthApi(clone);
|
|
788
963
|
clone.prompts = new PromptsApi(clone);
|
|
789
964
|
clone.mcp = new McpApi(clone);
|
|
@@ -1306,17 +1481,24 @@ function makeEmit(cfg) {
|
|
|
1306
1481
|
}
|
|
1307
1482
|
};
|
|
1308
1483
|
}
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1484
|
+
async function daemonDirectory(host) {
|
|
1485
|
+
const result = await host.exec(["sh", "-lc", 'printf "<<MW_HOME>>%s<<MW_HOME>>" "$HOME"'], { timeoutSeconds: 15 });
|
|
1486
|
+
const runtimeHome = result.stdout?.match(/<<MW_HOME>>([\s\S]*?)<<MW_HOME>>/)?.[1];
|
|
1487
|
+
if (!runtimeHome?.startsWith("/") || runtimeHome.length > 4096 || /[\x00-\x1f\x7f]/.test(runtimeHome)) {
|
|
1488
|
+
throw new MindwireError("mindwire: cannot resolve the runtime user's home directory");
|
|
1489
|
+
}
|
|
1490
|
+
return runtimeHome.replace(/\/+$/, "") + "/.mindwire";
|
|
1491
|
+
}
|
|
1314
1492
|
async function ensureDaemon(host, cfg) {
|
|
1315
1493
|
const emit2 = makeEmit(cfg);
|
|
1316
|
-
|
|
1494
|
+
let token = cfg.token ?? (await import('crypto')).randomBytes(32).toString("hex");
|
|
1317
1495
|
try {
|
|
1318
1496
|
await waitHostReady(host);
|
|
1319
1497
|
emit2({ phase: "connect", message: "runtime ready" });
|
|
1498
|
+
const directory = await daemonDirectory(host);
|
|
1499
|
+
if (!cfg.token) {
|
|
1500
|
+
token = await readWorkspaceToken(host, directory) ?? token;
|
|
1501
|
+
}
|
|
1320
1502
|
const desired = cfg.desiredVersion ?? SDK_VERSION;
|
|
1321
1503
|
const health = await probeHealth(host, cfg.port, token);
|
|
1322
1504
|
if (health.reachable) {
|
|
@@ -1337,37 +1519,54 @@ async function ensureDaemon(host, cfg) {
|
|
|
1337
1519
|
} else {
|
|
1338
1520
|
emit2({ phase: "probe", message: "no daemon reachable; deploying" });
|
|
1339
1521
|
}
|
|
1340
|
-
await deploy(host, cfg, emit2, token);
|
|
1522
|
+
await deploy(host, cfg, emit2, token, directory);
|
|
1523
|
+
if (!cfg.token) token = await readWorkspaceToken(host, directory) ?? token;
|
|
1341
1524
|
return token;
|
|
1342
1525
|
} catch (err) {
|
|
1343
1526
|
emit2({ phase: "error", message: "ensure failed", error: err instanceof Error ? err.message : String(err) });
|
|
1344
1527
|
throw err;
|
|
1345
1528
|
}
|
|
1346
1529
|
}
|
|
1347
|
-
async function
|
|
1348
|
-
const
|
|
1530
|
+
async function readWorkspaceToken(host, directory) {
|
|
1531
|
+
const saved = await host.exec(["sh", "-lc", `cat ${shellQuote(directory + "/daemon.token")} 2>/dev/null || true`], { timeoutSeconds: 15 });
|
|
1532
|
+
const candidate = saved.stdout?.trim();
|
|
1533
|
+
return candidate && candidate.length <= 4096 && /^[\x21-\x7e]+$/.test(candidate) ? candidate : void 0;
|
|
1534
|
+
}
|
|
1535
|
+
async function deploy(host, cfg, emit2, token, directory) {
|
|
1536
|
+
const newPath = directory + "/mindwired.new";
|
|
1537
|
+
const BIN = shellQuote(directory + "/mindwired"), BIN_NEW = shellQuote(newPath);
|
|
1538
|
+
const STATE = shellQuote(directory + "/agent-state.json"), LOG = shellQuote(directory + "/daemon.log");
|
|
1539
|
+
const TOKEN = shellQuote(directory + "/daemon.token");
|
|
1540
|
+
const { platform, arch } = await probePlatform(host);
|
|
1349
1541
|
const desired = cfg.desiredVersion ?? SDK_VERSION;
|
|
1350
1542
|
let acquire = "";
|
|
1543
|
+
let stagedUpload;
|
|
1351
1544
|
if (cfg.daemonBin) {
|
|
1352
|
-
const binPath = await
|
|
1545
|
+
const binPath = await resolveHostDaemon(cfg.daemonBin, platform, arch);
|
|
1353
1546
|
const bytes = await readBytes(binPath);
|
|
1354
|
-
emit2({ phase: "upload", message: `uploading daemon (${arch}, ${formatMiB(bytes.length)})`, arch, bytes: bytes.length });
|
|
1355
|
-
await
|
|
1547
|
+
emit2({ phase: "upload", message: `uploading daemon (${platform}-${arch}, ${formatMiB(bytes.length)})`, platform, arch, bytes: bytes.length });
|
|
1548
|
+
stagedUpload = `${newPath}-${(await import('crypto')).randomUUID()}`;
|
|
1549
|
+
await host.exec(["sh", "-lc", `mkdir -p ${shellQuote(directory)}`], { timeoutSeconds: 15 });
|
|
1550
|
+
await host.putFile(stagedUpload, bytes, { mode: "0755" });
|
|
1551
|
+
acquire = `mv -f ${shellQuote(stagedUpload)} ${BIN_NEW}`;
|
|
1356
1552
|
} else {
|
|
1357
1553
|
if (!/^[0-9]+\.[0-9]+\.[0-9]+(?:-[0-9A-Za-z.-]+)?$/.test(desired)) {
|
|
1358
1554
|
throw new MindwireError(`mindwire: cannot download daemon for non-release SDK version ${desired}`);
|
|
1359
1555
|
}
|
|
1360
|
-
const asset = `mindwired-v${desired}
|
|
1556
|
+
const asset = `mindwired-v${desired}-${platform}-${arch}`;
|
|
1361
1557
|
const release = `https://github.com/oblien/mindwire/releases/download/v${desired}`;
|
|
1362
|
-
emit2({ phase: "download", message: `downloading daemon v${desired} on the destination (${arch})`, arch });
|
|
1558
|
+
emit2({ phase: "download", message: `downloading daemon v${desired} on the destination (${platform}-${arch})`, platform, arch });
|
|
1363
1559
|
acquire = [
|
|
1364
1560
|
`release=${shellQuote(release)}`,
|
|
1365
1561
|
`asset=${shellQuote(asset)}`,
|
|
1562
|
+
"if command -v sha256sum >/dev/null 2>&1; then mw_sha256=(sha256sum);",
|
|
1563
|
+
"elif command -v shasum >/dev/null 2>&1; then mw_sha256=(shasum -a 256);",
|
|
1564
|
+
'else echo "MINDWIRE_FAIL SHA-256 verification requires sha256sum or shasum"; exit 1; fi',
|
|
1366
1565
|
"download() {",
|
|
1367
1566
|
` expected=$(curl -fsSL "$release/checksums.txt" | awk -v asset="$asset" '$2 == asset { print $1; exit }') || return 1`,
|
|
1368
1567
|
' [ -n "$expected" ] || return 1',
|
|
1369
1568
|
` curl -fsSL "$release/$asset" -o ${BIN_NEW} || return 1`,
|
|
1370
|
-
` actual=$(
|
|
1569
|
+
` actual=$("\${mw_sha256[@]}" ${BIN_NEW} | awk '{print $1}') || return 1`,
|
|
1371
1570
|
' [ "$actual" = "$expected" ] || return 2',
|
|
1372
1571
|
"}",
|
|
1373
1572
|
"if download; then :; else",
|
|
@@ -1375,33 +1574,61 @@ async function deploy(host, cfg, emit2, token) {
|
|
|
1375
1574
|
` latest=$(curl -fsSL https://api.github.com/repos/oblien/mindwire/releases/latest | sed -n 's/.*"tag_name"[[:space:]]*:[[:space:]]*"\\([^"]*\\)".*/\\1/p')`,
|
|
1376
1575
|
' case "$latest" in v[0-9]*.[0-9]*.[0-9]*) ;; *) echo "MINDWIRE_FAIL no matching or latest release"; exit 1 ;; esac',
|
|
1377
1576
|
' release="https://github.com/oblien/mindwire/releases/download/$latest"',
|
|
1378
|
-
` asset="mindwired-$latest
|
|
1577
|
+
` asset="mindwired-$latest-${platform}-${arch}"`,
|
|
1379
1578
|
' download || { echo "MINDWIRE_FAIL latest release download failed"; exit 1; }',
|
|
1380
1579
|
"fi",
|
|
1381
1580
|
`chmod +x ${BIN_NEW}`
|
|
1382
1581
|
].join("\n");
|
|
1383
1582
|
}
|
|
1384
1583
|
const script = [
|
|
1385
|
-
"set -
|
|
1386
|
-
`mkdir -p ${
|
|
1584
|
+
"set -eo pipefail",
|
|
1585
|
+
`mkdir -p ${shellQuote(directory)}`,
|
|
1586
|
+
// iOS takes this same workspace lock. Unique staging also protects concurrent local uploads.
|
|
1587
|
+
stagedUpload ? `trap ${shellQuote(`rm -f ${shellQuote(stagedUpload)}`)} EXIT` : "",
|
|
1588
|
+
`exec 9>${shellQuote(directory + "/daemon-install.lock")}`,
|
|
1589
|
+
"if command -v flock >/dev/null 2>&1; then",
|
|
1590
|
+
' flock -w 360 9 || { echo "MINDWIRE_FAIL another daemon update is still running"; exit 1; }',
|
|
1591
|
+
"elif command -v lockf >/dev/null 2>&1; then",
|
|
1592
|
+
' lockf -s -t 360 9 || { echo "MINDWIRE_FAIL another daemon update is still running"; exit 1; }',
|
|
1593
|
+
'else echo "MINDWIRE_FAIL No supported update lock is available (flock on Linux, lockf on macOS)."; exit 1; fi',
|
|
1594
|
+
`mw_token=${shellQuote(token)}`,
|
|
1595
|
+
!cfg.token ? `if [ -s ${TOKEN} ]; then mw_token=$(cat ${TOKEN}); fi` : "",
|
|
1596
|
+
!cfg.forceDeploy ? [
|
|
1597
|
+
`mw_health=$(curl -fsS --max-time 3 -H "Authorization: Bearer $mw_token" http://127.0.0.1:${cfg.port}/healthz 2>/dev/null || true)`,
|
|
1598
|
+
`mw_version=$(printf '%s' "$mw_health" | sed -n 's/.*"version"[[:space:]]*:[[:space:]]*"\\([^" ]*\\)".*/\\1/p')`,
|
|
1599
|
+
`if [ -n "$mw_health" ] && ${cfg.autoUpdate ? `[ "$mw_version" = ${shellQuote(desired)} ]` : "true"}; then echo MINDWIRE_READY; exit 0; fi`
|
|
1600
|
+
].join("\n") : "",
|
|
1601
|
+
// macOS ships POSIX setsid in Perl, but does not include Linux's setsid executable.
|
|
1602
|
+
"if command -v setsid >/dev/null 2>&1; then mw_detach=(setsid);",
|
|
1603
|
+
`elif command -v perl >/dev/null 2>&1; then mw_detach=(perl -MPOSIX -e 'defined(my $sid = POSIX::setsid()) && $sid >= 0 or die "setsid: $!"; exec @ARGV; die "exec: $!";');`,
|
|
1604
|
+
'else echo "MINDWIRE_FAIL Cannot start the Mindwire service: setsid or Perl is required."; exit 1; fi',
|
|
1387
1605
|
acquire,
|
|
1388
1606
|
// Stop a prior daemon by exact process NAME, never `pkill -f <path>`: this whole script (which
|
|
1389
1607
|
// contains `${BIN}` several times) is the argv of the `bash -lc` shell running it, so a full-cmdline
|
|
1390
1608
|
// match would SIGTERM our own deploying shell before the daemon ever launches. `-x mindwired` matches
|
|
1391
1609
|
// only the daemon's comm (`bash`/`pkill` never match), leaving this shell alive.
|
|
1392
|
-
|
|
1610
|
+
"if command -v pkill >/dev/null 2>&1; then",
|
|
1611
|
+
" pkill -x mindwired 2>/dev/null || true",
|
|
1612
|
+
"else",
|
|
1613
|
+
" for mw_proc in /proc/[0-9]*/comm; do",
|
|
1614
|
+
' IFS= read -r mw_name 2>/dev/null < "$mw_proc" || continue',
|
|
1615
|
+
' [ "$mw_name" = mindwired ] || continue',
|
|
1616
|
+
" mw_pid=${mw_proc#/proc/}; mw_pid=${mw_pid%/comm}",
|
|
1617
|
+
' kill "$mw_pid" 2>/dev/null || true',
|
|
1618
|
+
" done",
|
|
1619
|
+
"fi",
|
|
1393
1620
|
"sleep 0.3",
|
|
1394
1621
|
`mv -f ${BIN_NEW} ${BIN}`,
|
|
1395
1622
|
`chmod +x ${BIN}`,
|
|
1396
1623
|
// Detach so the daemon survives this exec's shell exiting. ADDR=":<port>" binds 0.0.0.0.
|
|
1397
|
-
`
|
|
1624
|
+
`"\${mw_detach[@]}" nohup env ADDR=":${cfg.port}" AGENT_TYPE=${shellQuote(cfg.agent)} AGENT_CWD=${shellQuote(cfg.agentCwd)} STATE_PATH=${STATE} DAEMON_TOKEN="$mw_token" ${BIN} > ${LOG} 2>&1 < /dev/null 9>&- &`,
|
|
1398
1625
|
// Health-poll from inside the VM (loopback) and emit a marker — exit codes are unreliable here.
|
|
1399
|
-
`for
|
|
1626
|
+
`for ((mw_attempt=0; mw_attempt<60; mw_attempt++)); do curl -fsS --max-time 2 -H "Authorization: Bearer $mw_token" http://127.0.0.1:${cfg.port}/healthz >/dev/null 2>&1 && { echo MINDWIRE_READY; exit 0; }; sleep 0.25; done`,
|
|
1400
1627
|
"echo MINDWIRE_FAIL",
|
|
1401
1628
|
`tail -n 40 ${LOG} 2>/dev/null || true`
|
|
1402
1629
|
].join("\n");
|
|
1403
1630
|
emit2({ phase: "launch", message: "launching daemon" });
|
|
1404
|
-
const res = await host.exec(["bash", "-lc", script], { timeoutSeconds:
|
|
1631
|
+
const res = await host.exec(["bash", "-lc", script], { timeoutSeconds: 420 });
|
|
1405
1632
|
const out = res.stdout ?? "";
|
|
1406
1633
|
if (!out.includes("MINDWIRE_READY")) {
|
|
1407
1634
|
throw new MindwireError(
|
|
@@ -1422,10 +1649,15 @@ async function probeHealth(host, port, token) {
|
|
|
1422
1649
|
return { reachable: true };
|
|
1423
1650
|
}
|
|
1424
1651
|
}
|
|
1425
|
-
async function
|
|
1426
|
-
const res = await host.exec(["bash", "-lc", `printf '<<ARCH:%s>>' "$(uname -m)"`], { timeoutSeconds: 15 });
|
|
1427
|
-
const
|
|
1428
|
-
|
|
1652
|
+
async function probePlatform(host) {
|
|
1653
|
+
const res = await host.exec(["bash", "-lc", `printf '<<OS:%s>><<ARCH:%s>>' "$(uname -s)" "$(uname -m)"`], { timeoutSeconds: 15 });
|
|
1654
|
+
const os = ((res.stdout ?? "").match(/<<OS:([^>]*)>>/)?.[1] ?? "").trim().toLowerCase();
|
|
1655
|
+
const rawArch = ((res.stdout ?? "").match(/<<ARCH:([^>]*)>>/)?.[1] ?? "").trim();
|
|
1656
|
+
const arch = rawArch === "aarch64" || rawArch === "arm64" ? "arm64" : rawArch === "x86_64" || rawArch === "amd64" ? "amd64" : void 0;
|
|
1657
|
+
if (os !== "linux" && os !== "darwin" || !arch) {
|
|
1658
|
+
throw new MindwireError(`mindwire: unsupported workspace platform ${os || "unknown"}/${rawArch || "unknown"}; expected Linux or macOS on amd64 or arm64`);
|
|
1659
|
+
}
|
|
1660
|
+
return { platform: os, arch };
|
|
1429
1661
|
}
|
|
1430
1662
|
async function waitHostReady(host, timeoutMs = 6e4) {
|
|
1431
1663
|
const deadline = Date.now() + timeoutMs;
|
|
@@ -1445,15 +1677,18 @@ async function waitHostReady(host, timeoutMs = 6e4) {
|
|
|
1445
1677
|
);
|
|
1446
1678
|
}
|
|
1447
1679
|
async function resolveLinuxDaemon(explicit, arch) {
|
|
1680
|
+
return resolveHostDaemon(explicit, "linux", arch);
|
|
1681
|
+
}
|
|
1682
|
+
async function resolveHostDaemon(explicit, platform, arch) {
|
|
1448
1683
|
const fs = await import('fs');
|
|
1449
1684
|
if (explicit) {
|
|
1450
|
-
const resolved = explicit.replaceAll("{arch}", arch);
|
|
1685
|
+
const resolved = explicit.replaceAll("{os}", platform).replaceAll("{arch}", arch);
|
|
1451
1686
|
if (!fs.existsSync(resolved)) {
|
|
1452
1687
|
throw new MindwireError(`mindwire: sandbox daemonBin not found at ${resolved}`);
|
|
1453
1688
|
}
|
|
1454
1689
|
return resolved;
|
|
1455
1690
|
}
|
|
1456
|
-
return ensureDaemonBinary({ platform
|
|
1691
|
+
return ensureDaemonBinary({ platform, arch: arch === "arm64" ? "arm64" : "x64" });
|
|
1457
1692
|
}
|
|
1458
1693
|
async function readBytes(p) {
|
|
1459
1694
|
const fs = await import('fs/promises');
|
|
@@ -2306,6 +2541,6 @@ function clearCatalogCache() {
|
|
|
2306
2541
|
inflight = null;
|
|
2307
2542
|
}
|
|
2308
2543
|
|
|
2309
|
-
export { ApiError, AuthApi, ContainerHost, Http, MODELS_DEV_URL, McpApi, Mindwire, MindwireError, NotifyApi, PromptsApi, ProvidersApi, Run, RunFailedError, SDK_VERSION, TimeoutError, catalogModels, catalogProvider, catalogProviders, clearCatalogCache, docker, ensureDaemon, ensureDaemonBinary, loadCatalog, local, lookupModel, oblien, provisionContainer, provisionDocker, provisionOblien, provisionSsh, provisionSshContainer, remote, resolveLinuxDaemon, ssh, startEmbedded };
|
|
2544
|
+
export { ApiError, AuthApi, ContainerHost, Http, MODELS_DEV_URL, McpApi, Mindwire, MindwireError, NotifyApi, ProjectOperationsApi, PromptsApi, ProvidersApi, Run, RunFailedError, SDK_VERSION, SurfacesApi, TimeoutError, WorkspaceApi, WorkspaceCollection, catalogModels, catalogProvider, catalogProviders, clearCatalogCache, docker, ensureDaemon, ensureDaemonBinary, loadCatalog, local, lookupModel, oblien, provisionContainer, provisionDocker, provisionOblien, provisionSsh, provisionSshContainer, remote, resolveLinuxDaemon, ssh, startEmbedded };
|
|
2310
2545
|
//# sourceMappingURL=index.js.map
|
|
2311
2546
|
//# sourceMappingURL=index.js.map
|