mindwire 0.1.14 → 0.1.18
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/client.d.ts +17 -3
- package/dist/index.cjs +208 -32
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +4 -2
- package/dist/index.js +207 -33
- package/dist/index.js.map +1 -1
- package/dist/run.d.ts +6 -3
- package/dist/service.d.ts +24 -0
- package/dist/surfaces.d.ts +4 -0
- package/dist/target/docker.d.ts +1 -1
- package/dist/target/host.d.ts +1 -1
- package/dist/target/oblien.d.ts +2 -2
- package/dist/target/ssh.d.ts +1 -1
- package/dist/types.d.ts +73 -1
- package/dist/workspace.d.ts +80 -2
- package/package.json +2 -2
package/dist/index.d.ts
CHANGED
|
@@ -32,7 +32,9 @@ 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 {
|
|
36
|
-
export type {
|
|
35
|
+
export { ServiceApi } from "./service.js";
|
|
36
|
+
export type { ServiceUpdateState, ServiceUpdateLease } from "./service.js";
|
|
37
|
+
export { WorkspaceApi, WorkspaceCollection, ProjectOperationsApi, GitAccessApi } from "./workspace.js";
|
|
38
|
+
export type { ProjectRequest, ProjectAuth, ProjectOperation, ProjectRemoveRequest, GitConnection, GitAccessState, ProjectGitState, GitAction, GitOperationRequest, GitOperation } from "./workspace.js";
|
|
37
39
|
export type { WorkspaceRecord, WorkspaceAgent, WorkspaceProject, WorkspaceChat, WorkspaceKind, WorkspaceInput, WorkspaceImport, WorkspaceSnapshot } from "./workspace.js";
|
|
38
40
|
export * from "./surfaces.js";
|
package/dist/index.js
CHANGED
|
@@ -380,8 +380,9 @@ var Run = class _Run {
|
|
|
380
380
|
await this.http.request("POST", `/runs/${encodeURIComponent(this.id)}/cancel`);
|
|
381
381
|
}
|
|
382
382
|
/**
|
|
383
|
-
* Answer a
|
|
384
|
-
*
|
|
383
|
+
* Answer a pending permission, question or plan. Message-mode questions remain answerable
|
|
384
|
+
* after completion; the daemon steers or resumes the conversation. Read the chat's latest
|
|
385
|
+
* run after replying to a completed run. Requires the agent's `respond` capability.
|
|
385
386
|
*/
|
|
386
387
|
async respond(input = {}) {
|
|
387
388
|
await this.http.request("POST", `/runs/${encodeURIComponent(this.id)}/respond`, { body: input });
|
|
@@ -545,12 +546,14 @@ var WorkspaceCollection = class {
|
|
|
545
546
|
var WorkspaceApi = class {
|
|
546
547
|
constructor(mw) {
|
|
547
548
|
this.mw = mw;
|
|
549
|
+
this.git = new GitAccessApi(mw);
|
|
548
550
|
this.operations = new ProjectOperationsApi(mw);
|
|
549
551
|
this.agents = new WorkspaceCollection(mw, "agents");
|
|
550
552
|
this.projects = new WorkspaceCollection(mw, "projects");
|
|
551
553
|
this.chats = new WorkspaceCollection(mw, "chats");
|
|
552
554
|
}
|
|
553
555
|
mw;
|
|
556
|
+
git;
|
|
554
557
|
operations;
|
|
555
558
|
agents;
|
|
556
559
|
projects;
|
|
@@ -579,6 +582,75 @@ var WorkspaceApi = class {
|
|
|
579
582
|
return this.mw.http.request("POST", "/workspace/import", { body: records });
|
|
580
583
|
}
|
|
581
584
|
};
|
|
585
|
+
var GitAccessApi = class {
|
|
586
|
+
constructor(mw) {
|
|
587
|
+
this.mw = mw;
|
|
588
|
+
}
|
|
589
|
+
mw;
|
|
590
|
+
state() {
|
|
591
|
+
return this.mw.http.request("GET", "/workspace/git");
|
|
592
|
+
}
|
|
593
|
+
setDefault(connection, auth) {
|
|
594
|
+
return this.mw.http.request("PUT", "/workspace/git", { body: { connection, auth } });
|
|
595
|
+
}
|
|
596
|
+
forget(connectionId) {
|
|
597
|
+
return this.mw.http.request("DELETE", `/workspace/git/connections/${encodeURIComponent(connectionId)}`);
|
|
598
|
+
}
|
|
599
|
+
project(projectId) {
|
|
600
|
+
return this.mw.http.request("GET", `/workspace/projects/${encodeURIComponent(projectId)}/git`);
|
|
601
|
+
}
|
|
602
|
+
setProject(projectId, connection, expectedRevision, auth) {
|
|
603
|
+
return this.mw.http.request("PUT", `/workspace/projects/${encodeURIComponent(projectId)}/git`, {
|
|
604
|
+
body: { connection, expectedRevision, auth }
|
|
605
|
+
});
|
|
606
|
+
}
|
|
607
|
+
/** Compatibility call. Prefer start() and operation()/watch() for reconnectable writes. */
|
|
608
|
+
run(projectId, operation, auth) {
|
|
609
|
+
return this.mw.http.request("POST", `/workspace/projects/${encodeURIComponent(projectId)}/git/${operation}`, { body: { auth } });
|
|
610
|
+
}
|
|
611
|
+
/** Requires health.gitOperationsVersion >= 1. Acceptance persists before Git runs;
|
|
612
|
+
* disconnecting only detaches the client. The same ID/intent never runs twice.
|
|
613
|
+
*/
|
|
614
|
+
start(projectId, request) {
|
|
615
|
+
return this.mw.http.request("POST", `/workspace/projects/${encodeURIComponent(projectId)}/git/operations`, { body: request });
|
|
616
|
+
}
|
|
617
|
+
async operations(projectId, activeOnly = false) {
|
|
618
|
+
const result = await this.mw.http.request(
|
|
619
|
+
"GET",
|
|
620
|
+
`/workspace/projects/${encodeURIComponent(projectId)}/git/operations`,
|
|
621
|
+
{ query: { active: activeOnly } }
|
|
622
|
+
);
|
|
623
|
+
return result.operations;
|
|
624
|
+
}
|
|
625
|
+
operation(id) {
|
|
626
|
+
return this.mw.http.request("GET", `/workspace/git/operations/${encodeURIComponent(id)}`);
|
|
627
|
+
}
|
|
628
|
+
cancel(id) {
|
|
629
|
+
return this.mw.http.request("POST", `/workspace/git/operations/${encodeURIComponent(id)}/cancel`);
|
|
630
|
+
}
|
|
631
|
+
/** Current snapshot followed by state changes. Aborting observation never cancels the operation. */
|
|
632
|
+
async *watch(id, opts = {}) {
|
|
633
|
+
const controller = new AbortController();
|
|
634
|
+
const abort = () => controller.abort();
|
|
635
|
+
opts.signal?.addEventListener("abort", abort, { once: true });
|
|
636
|
+
if (opts.signal?.aborted) controller.abort();
|
|
637
|
+
try {
|
|
638
|
+
const response = await this.mw.http.open("GET", `/workspace/git/operations/${encodeURIComponent(id)}/stream`, {
|
|
639
|
+
signal: controller.signal
|
|
640
|
+
});
|
|
641
|
+
let sequence = -1;
|
|
642
|
+
for await (const operation of readSSE(response.body, controller.signal)) {
|
|
643
|
+
if (operation.sequence > sequence) {
|
|
644
|
+
sequence = operation.sequence;
|
|
645
|
+
yield operation;
|
|
646
|
+
}
|
|
647
|
+
}
|
|
648
|
+
} finally {
|
|
649
|
+
controller.abort();
|
|
650
|
+
opts.signal?.removeEventListener("abort", abort);
|
|
651
|
+
}
|
|
652
|
+
}
|
|
653
|
+
};
|
|
582
654
|
|
|
583
655
|
// src/surfaces.ts
|
|
584
656
|
var SurfacesApi = class {
|
|
@@ -640,8 +712,27 @@ var SurfacesApi = class {
|
|
|
640
712
|
}
|
|
641
713
|
};
|
|
642
714
|
|
|
715
|
+
// src/service.ts
|
|
716
|
+
var ServiceApi = class {
|
|
717
|
+
constructor(client) {
|
|
718
|
+
this.client = client;
|
|
719
|
+
}
|
|
720
|
+
client;
|
|
721
|
+
updateStatus() {
|
|
722
|
+
return this.client.http.request("GET", "/service/update");
|
|
723
|
+
}
|
|
724
|
+
/** Download first. Acquire immediately before replacement; busy work returns 409.
|
|
725
|
+
* Admission stays closed until release, process exit, or the one-minute expiry. */
|
|
726
|
+
acquireUpdate() {
|
|
727
|
+
return this.client.http.request("POST", "/service/update");
|
|
728
|
+
}
|
|
729
|
+
releaseUpdate(id) {
|
|
730
|
+
return this.client.http.request("DELETE", `/service/update/${encodeURIComponent(id)}`);
|
|
731
|
+
}
|
|
732
|
+
};
|
|
733
|
+
|
|
643
734
|
// src/version.ts
|
|
644
|
-
var SDK_VERSION = "0.1.
|
|
735
|
+
var SDK_VERSION = "0.1.18" ;
|
|
645
736
|
|
|
646
737
|
// src/daemon-binary.ts
|
|
647
738
|
function supported(platform, arch) {
|
|
@@ -902,6 +993,7 @@ var Mindwire = class _Mindwire {
|
|
|
902
993
|
/** Workspace registry: saved agent profiles, projects and chat relationships. */
|
|
903
994
|
workspace;
|
|
904
995
|
surfaces;
|
|
996
|
+
service;
|
|
905
997
|
http;
|
|
906
998
|
/** The default agent type applied to agent-scoped calls, if set. */
|
|
907
999
|
defaultAgent;
|
|
@@ -937,6 +1029,7 @@ var Mindwire = class _Mindwire {
|
|
|
937
1029
|
this.defaultAgent = opts.agent;
|
|
938
1030
|
this.workspace = new WorkspaceApi(this);
|
|
939
1031
|
this.surfaces = new SurfacesApi(this);
|
|
1032
|
+
this.service = new ServiceApi(this);
|
|
940
1033
|
this.auth = new AuthApi(this);
|
|
941
1034
|
this.prompts = new PromptsApi(this);
|
|
942
1035
|
this.mcp = new McpApi(this);
|
|
@@ -959,6 +1052,7 @@ var Mindwire = class _Mindwire {
|
|
|
959
1052
|
clone.defaultAgent = agent;
|
|
960
1053
|
clone.workspace = new WorkspaceApi(clone);
|
|
961
1054
|
clone.surfaces = new SurfacesApi(clone);
|
|
1055
|
+
clone.service = new ServiceApi(clone);
|
|
962
1056
|
clone.auth = new AuthApi(clone);
|
|
963
1057
|
clone.prompts = new PromptsApi(clone);
|
|
964
1058
|
clone.mcp = new McpApi(clone);
|
|
@@ -1007,6 +1101,12 @@ var Mindwire = class _Mindwire {
|
|
|
1007
1101
|
agent(scoped) {
|
|
1008
1102
|
return this.http.request("GET", "/agent", { query: this.agentParam(scoped) });
|
|
1009
1103
|
}
|
|
1104
|
+
/** `GET /agent/software` — installed version, compatibility and approved update target. */
|
|
1105
|
+
software(opts = {}) {
|
|
1106
|
+
return this.http.request("GET", "/agent/software", {
|
|
1107
|
+
query: { ...this.agentParam(opts), ...opts.refresh ? { refresh: "true" } : {} }
|
|
1108
|
+
});
|
|
1109
|
+
}
|
|
1010
1110
|
/**
|
|
1011
1111
|
* `GET /models` — the models the selected agent can run for the configured account. An empty array
|
|
1012
1112
|
* is valid (no credentials yet / offline). Throws a 400 {@link ApiError} for an agent whose model is
|
|
@@ -1024,7 +1124,7 @@ var Mindwire = class _Mindwire {
|
|
|
1024
1124
|
setup(scoped) {
|
|
1025
1125
|
return this.http.request("POST", "/setup", { query: this.agentParam(scoped) });
|
|
1026
1126
|
}
|
|
1027
|
-
/** `POST /update` —
|
|
1127
|
+
/** `POST /update` — install the catalog's newest tested version compatible with this daemon. */
|
|
1028
1128
|
update(scoped) {
|
|
1029
1129
|
return this.http.request("POST", "/update", { query: this.agentParam(scoped) });
|
|
1030
1130
|
}
|
|
@@ -1121,6 +1221,7 @@ var Mindwire = class _Mindwire {
|
|
|
1121
1221
|
if (input.options !== void 0) body.options = input.options;
|
|
1122
1222
|
if (input.mode !== void 0) body.mode = input.mode;
|
|
1123
1223
|
if (input.resolve !== void 0) body.resolve = input.resolve;
|
|
1224
|
+
if (input.gitAuth !== void 0) body.gitAuth = input.gitAuth;
|
|
1124
1225
|
const data = await this.http.request("POST", "/turns", {
|
|
1125
1226
|
query: this.agentParam(input),
|
|
1126
1227
|
body
|
|
@@ -1224,12 +1325,26 @@ var AuthApi = class {
|
|
|
1224
1325
|
body: input
|
|
1225
1326
|
});
|
|
1226
1327
|
}
|
|
1328
|
+
/** Read one interactive attempt, scoped by the returned AuthState.flowId. */
|
|
1329
|
+
poll(flowId, scoped) {
|
|
1330
|
+
return this.step({ _flowId: flowId }, scoped);
|
|
1331
|
+
}
|
|
1332
|
+
/** Cancel this native login without affecting a newer sign-in attempt. */
|
|
1333
|
+
cancel(flowId, scoped) {
|
|
1334
|
+
return this.step({ _flowId: flowId, _action: "cancel" }, scoped);
|
|
1335
|
+
}
|
|
1227
1336
|
/** `GET /auth/status` — is the agent authenticated, and via which method. */
|
|
1228
1337
|
status(scoped) {
|
|
1229
1338
|
return this.mw.http.request("GET", "/auth/status", {
|
|
1230
1339
|
query: this.mw.agentParam(scoped)
|
|
1231
1340
|
});
|
|
1232
1341
|
}
|
|
1342
|
+
/** `POST /auth/logout` — disconnect this harness once its running chats finish. */
|
|
1343
|
+
logout(scoped) {
|
|
1344
|
+
return this.mw.http.request("POST", "/auth/logout", {
|
|
1345
|
+
query: this.mw.agentParam(scoped)
|
|
1346
|
+
});
|
|
1347
|
+
}
|
|
1233
1348
|
};
|
|
1234
1349
|
var PromptsApi = class {
|
|
1235
1350
|
constructor(mw) {
|
|
@@ -1471,6 +1586,23 @@ var NotifyApi = class {
|
|
|
1471
1586
|
};
|
|
1472
1587
|
|
|
1473
1588
|
// src/target/host.ts
|
|
1589
|
+
function versionAtLeast(actual, desired) {
|
|
1590
|
+
if (actual === desired) return true;
|
|
1591
|
+
if (!actual || !/^\d+\.\d+\.\d+$/.test(actual) || !/^\d+\.\d+\.\d+$/.test(desired)) return false;
|
|
1592
|
+
const a = actual.split(".").map(Number), b = desired.split(".").map(Number);
|
|
1593
|
+
for (let i = 0; i < 3; i++) {
|
|
1594
|
+
if (a[i] !== b[i]) return a[i] > b[i];
|
|
1595
|
+
}
|
|
1596
|
+
return true;
|
|
1597
|
+
}
|
|
1598
|
+
var versionCheckScript = String.raw`version_at_least() {
|
|
1599
|
+
[ "$1" != "$2" ] || return 0
|
|
1600
|
+
[[ "$1" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]] && [[ "$2" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]] || return 1
|
|
1601
|
+
local a b c x y z
|
|
1602
|
+
IFS=. read -r a b c <<< "$1"
|
|
1603
|
+
IFS=. read -r x y z <<< "$2"
|
|
1604
|
+
(( 10#$a > 10#$x || (10#$a == 10#$x && 10#$b > 10#$y) || (10#$a == 10#$x && 10#$b == 10#$y && 10#$c >= 10#$z) ))
|
|
1605
|
+
}`;
|
|
1474
1606
|
function makeEmit(cfg) {
|
|
1475
1607
|
const target = cfg.target ?? "sandbox";
|
|
1476
1608
|
return (e) => {
|
|
@@ -1507,19 +1639,24 @@ async function ensureDaemon(host, cfg) {
|
|
|
1507
1639
|
message: health.version ? `daemon reachable (v${health.version})` : "daemon reachable (version unknown)",
|
|
1508
1640
|
version: health.version
|
|
1509
1641
|
});
|
|
1510
|
-
const upToDate = health.version
|
|
1642
|
+
const upToDate = versionAtLeast(health.version, desired);
|
|
1511
1643
|
if (!cfg.forceDeploy && (upToDate || !cfg.autoUpdate)) {
|
|
1512
1644
|
emit2({
|
|
1513
1645
|
phase: "skip",
|
|
1514
|
-
message: upToDate ? `daemon already at v${
|
|
1646
|
+
message: upToDate ? `daemon already at v${health.version}` : "keeping the running daemon",
|
|
1515
1647
|
version: health.version
|
|
1516
1648
|
});
|
|
1517
1649
|
return token;
|
|
1518
1650
|
}
|
|
1651
|
+
if ((health.serviceUpdateVersion ?? 0) < 1) {
|
|
1652
|
+
if (cfg.forceDeploy) throw new MindwireError("This legacy service cannot reserve an idle update. Upgrade it explicitly or stop it after its work finishes before deploying.");
|
|
1653
|
+
emit2({ phase: "skip", message: "automatic update deferred: upgrade this legacy service explicitly to enable idle updates", version: health.version });
|
|
1654
|
+
return token;
|
|
1655
|
+
}
|
|
1519
1656
|
} else {
|
|
1520
1657
|
emit2({ phase: "probe", message: "no daemon reachable; deploying" });
|
|
1521
1658
|
}
|
|
1522
|
-
await deploy(host, cfg, emit2, token, directory);
|
|
1659
|
+
await deploy(host, cfg, emit2, token, directory, health.reachable);
|
|
1523
1660
|
if (!cfg.token) token = await readWorkspaceToken(host, directory) ?? token;
|
|
1524
1661
|
return token;
|
|
1525
1662
|
} catch (err) {
|
|
@@ -1532,7 +1669,7 @@ async function readWorkspaceToken(host, directory) {
|
|
|
1532
1669
|
const candidate = saved.stdout?.trim();
|
|
1533
1670
|
return candidate && candidate.length <= 4096 && /^[\x21-\x7e]+$/.test(candidate) ? candidate : void 0;
|
|
1534
1671
|
}
|
|
1535
|
-
async function deploy(host, cfg, emit2, token, directory) {
|
|
1672
|
+
async function deploy(host, cfg, emit2, token, directory, requireLease) {
|
|
1536
1673
|
const newPath = directory + "/mindwired.new";
|
|
1537
1674
|
const BIN = shellQuote(directory + "/mindwired"), BIN_NEW = shellQuote(newPath);
|
|
1538
1675
|
const STATE = shellQuote(directory + "/agent-state.json"), LOG = shellQuote(directory + "/daemon.log");
|
|
@@ -1574,6 +1711,7 @@ async function deploy(host, cfg, emit2, token, directory) {
|
|
|
1574
1711
|
` latest=$(curl -fsSL https://api.github.com/repos/oblien/mindwire/releases/latest | sed -n 's/.*"tag_name"[[:space:]]*:[[:space:]]*"\\([^"]*\\)".*/\\1/p')`,
|
|
1575
1712
|
' case "$latest" in v[0-9]*.[0-9]*.[0-9]*) ;; *) echo "MINDWIRE_FAIL no matching or latest release"; exit 1 ;; esac',
|
|
1576
1713
|
' release="https://github.com/oblien/mindwire/releases/download/$latest"',
|
|
1714
|
+
' mw_expected_version="${latest#v}"',
|
|
1577
1715
|
` asset="mindwired-$latest-${platform}-${arch}"`,
|
|
1578
1716
|
' download || { echo "MINDWIRE_FAIL latest release download failed"; exit 1; }',
|
|
1579
1717
|
"fi",
|
|
@@ -1593,43 +1731,75 @@ async function deploy(host, cfg, emit2, token, directory) {
|
|
|
1593
1731
|
'else echo "MINDWIRE_FAIL No supported update lock is available (flock on Linux, lockf on macOS)."; exit 1; fi',
|
|
1594
1732
|
`mw_token=${shellQuote(token)}`,
|
|
1595
1733
|
!cfg.token ? `if [ -s ${TOKEN} ]; then mw_token=$(cat ${TOKEN}); fi` : "",
|
|
1734
|
+
'mw_lease=""',
|
|
1735
|
+
`mw_lease_file=${shellQuote(directory)}/update-lease-$$.json`,
|
|
1736
|
+
"cleanup_update() {",
|
|
1737
|
+
` if [ -n "$mw_lease" ]; then curl -s --connect-timeout 2 --max-time 3 -X DELETE -H "Authorization: Bearer $mw_token" "http://127.0.0.1:${cfg.port}/service/update/$mw_lease" >/dev/null 2>&1 || true; fi`,
|
|
1738
|
+
` rm -f ${BIN_NEW} "$mw_lease_file"${stagedUpload ? " " + shellQuote(stagedUpload) : ""}`,
|
|
1739
|
+
"}",
|
|
1740
|
+
// Shared staging is only removed while this process owns the workspace lock.
|
|
1741
|
+
"trap cleanup_update EXIT",
|
|
1742
|
+
versionCheckScript,
|
|
1743
|
+
`mw_desired=${shellQuote(desired)}`,
|
|
1744
|
+
'mw_expected_version="$mw_desired"',
|
|
1745
|
+
`mw_health=$(curl -fsS --max-time 3 -H "Authorization: Bearer $mw_token" http://127.0.0.1:${cfg.port}/healthz 2>/dev/null || true)`,
|
|
1596
1746
|
!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
1747
|
`mw_version=$(printf '%s' "$mw_health" | sed -n 's/.*"version"[[:space:]]*:[[:space:]]*"\\([^" ]*\\)".*/\\1/p')`,
|
|
1599
|
-
`if [ -n "$mw_health" ] && ${cfg.autoUpdate ?
|
|
1748
|
+
`if [ -n "$mw_health" ] && ${cfg.autoUpdate ? 'version_at_least "$mw_version" "$mw_desired"' : "true"}; then echo MINDWIRE_READY; exit 0; fi`
|
|
1600
1749
|
].join("\n") : "",
|
|
1601
1750
|
// macOS ships POSIX setsid in Perl, but does not include Linux's setsid executable.
|
|
1602
1751
|
"if command -v setsid >/dev/null 2>&1; then mw_detach=(setsid);",
|
|
1603
1752
|
`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
1753
|
'else echo "MINDWIRE_FAIL Cannot start the Mindwire service: setsid or Perl is required."; exit 1; fi',
|
|
1605
1754
|
acquire,
|
|
1606
|
-
//
|
|
1607
|
-
//
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
"
|
|
1611
|
-
|
|
1612
|
-
"
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
'
|
|
1616
|
-
"
|
|
1617
|
-
|
|
1618
|
-
"
|
|
1755
|
+
// The service decides idleness after transfer. Never stop arbitrary processes
|
|
1756
|
+
// or interrupt a chat that began while the SDK was downloading its binary.
|
|
1757
|
+
`mw_health=$(curl -fsS --max-time 3 -H "Authorization: Bearer $mw_token" http://127.0.0.1:${cfg.port}/healthz 2>/dev/null || true)`,
|
|
1758
|
+
'if [ -n "$mw_health" ]; then',
|
|
1759
|
+
` mw_status=$(curl -s --connect-timeout 2 --max-time 5 -X POST -H "Authorization: Bearer $mw_token" -o "$mw_lease_file" -w '%{http_code}' http://127.0.0.1:${cfg.port}/service/update) || mw_status=000`,
|
|
1760
|
+
' case "$mw_status" in',
|
|
1761
|
+
" 201)",
|
|
1762
|
+
` mw_lease=$(sed -n 's/.*"id"[[:space:]]*:[[:space:]]*"\\([[:alnum:]]*\\)".*/\\1/p' "$mw_lease_file")`,
|
|
1763
|
+
` mw_service_pid=$(sed -n 's/.*"pid"[[:space:]]*:[[:space:]]*\\([0-9][0-9]*\\).*/\\1/p' "$mw_lease_file")`,
|
|
1764
|
+
' [ -n "$mw_lease" ] && [[ "$mw_service_pid" =~ ^[0-9]+$ ]] && [ "$mw_service_pid" -gt 1 ] || { echo "MINDWIRE_FAIL invalid service update lease"; exit 1; }',
|
|
1765
|
+
' kill "$mw_service_pid" ;;',
|
|
1766
|
+
" 409) echo MINDWIRE_UPDATE_DEFERRED; exit 0 ;;",
|
|
1767
|
+
' 404) echo "MINDWIRE_FAIL this legacy service requires an explicit upgrade before idle updates"; exit 1 ;;',
|
|
1768
|
+
' *) echo "MINDWIRE_FAIL could not reserve an idle service update"; exit 1 ;;',
|
|
1769
|
+
" esac",
|
|
1770
|
+
requireLease ? 'else echo "MINDWIRE_FAIL the service went offline before its idle update could be reserved"; exit 1' : "",
|
|
1619
1771
|
"fi",
|
|
1620
1772
|
"sleep 0.3",
|
|
1621
1773
|
`mv -f ${BIN_NEW} ${BIN}`,
|
|
1622
1774
|
`chmod +x ${BIN}`,
|
|
1623
1775
|
// Detach so the daemon survives this exec's shell exiting. ADDR=":<port>" binds 0.0.0.0.
|
|
1624
|
-
|
|
1776
|
+
// Ignore HUP directly: macOS nohup can fail after setsid in a headless workspace.
|
|
1777
|
+
`"\${mw_detach[@]}" /bin/sh -c 'trap "" HUP; exec "$@"' mindwire-service 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>&- &`,
|
|
1625
1778
|
// Health-poll from inside the VM (loopback) and emit a marker — exit codes are unreliable here.
|
|
1626
|
-
|
|
1627
|
-
"
|
|
1628
|
-
`
|
|
1779
|
+
'mw_pid=$!; mw_exit=""; mw_deadline=$((SECONDS + 30))',
|
|
1780
|
+
"while (( SECONDS < mw_deadline )); do",
|
|
1781
|
+
` mw_health=$(curl -fsS --connect-timeout 2 --max-time 3 -H "Authorization: Bearer $mw_token" http://127.0.0.1:${cfg.port}/healthz 2>/dev/null || true)`,
|
|
1782
|
+
` mw_version=$(printf '%s' "$mw_health" | sed -n 's/.*"version"[[:space:]]*:[[:space:]]*"\\([^" ]*\\)".*/\\1/p')`,
|
|
1783
|
+
' if version_at_least "$mw_version" "$mw_expected_version"; then echo MINDWIRE_READY; exit 0; fi',
|
|
1784
|
+
' if [ -z "$mw_exit" ] && ! kill -0 "$mw_pid" 2>/dev/null; then',
|
|
1785
|
+
' mw_exit=0; wait "$mw_pid" || mw_exit=$?; [ "$mw_exit" = 0 ] || break',
|
|
1786
|
+
" fi",
|
|
1787
|
+
" sleep 0.25",
|
|
1788
|
+
"done",
|
|
1789
|
+
`mw_tail=$(tail -n 40 ${LOG} 2>/dev/null || true)`,
|
|
1790
|
+
'if [ -n "$mw_exit" ] && [ "$mw_exit" != 0 ]; then mw_reason="Mindwire exited during startup (status $mw_exit).";',
|
|
1791
|
+
'else mw_reason="The Mindwire service did not become ready at v$mw_expected_version (reported: $mw_version)."; fi',
|
|
1792
|
+
'[ -z "$mw_tail" ] || mw_reason="$mw_reason $mw_tail"',
|
|
1793
|
+
'printf "MINDWIRE_FAIL %s\\n" "$mw_reason"'
|
|
1629
1794
|
].join("\n");
|
|
1630
1795
|
emit2({ phase: "launch", message: "launching daemon" });
|
|
1631
1796
|
const res = await host.exec(["bash", "-lc", script], { timeoutSeconds: 420 });
|
|
1632
1797
|
const out = res.stdout ?? "";
|
|
1798
|
+
if (out.includes("MINDWIRE_UPDATE_DEFERRED")) {
|
|
1799
|
+
if (cfg.forceDeploy) throw new MindwireError("The workspace is busy. Wait for its operations to finish before deploying the service.");
|
|
1800
|
+
emit2({ phase: "skip", message: "service update deferred while workspace operations are running" });
|
|
1801
|
+
return;
|
|
1802
|
+
}
|
|
1633
1803
|
if (!out.includes("MINDWIRE_READY")) {
|
|
1634
1804
|
throw new MindwireError(
|
|
1635
1805
|
"mindwire: the in-sandbox daemon did not become healthy after deploy.\n" + (out || res.stderr || res.error || "(no output)").trim()
|
|
@@ -1644,7 +1814,11 @@ async function probeHealth(host, port, token) {
|
|
|
1644
1814
|
if (!body) return { reachable: false };
|
|
1645
1815
|
try {
|
|
1646
1816
|
const j = JSON.parse(body);
|
|
1647
|
-
return {
|
|
1817
|
+
return {
|
|
1818
|
+
reachable: true,
|
|
1819
|
+
version: typeof j.version === "string" ? j.version : void 0,
|
|
1820
|
+
serviceUpdateVersion: typeof j.serviceUpdateVersion === "number" ? j.serviceUpdateVersion : void 0
|
|
1821
|
+
};
|
|
1648
1822
|
} catch {
|
|
1649
1823
|
return { reachable: true };
|
|
1650
1824
|
}
|
|
@@ -1745,7 +1919,7 @@ var ContainerHost = class {
|
|
|
1745
1919
|
base;
|
|
1746
1920
|
containerId;
|
|
1747
1921
|
exec(argv, opts) {
|
|
1748
|
-
return this.base.exec(["docker", "exec", this.containerId, ...argv], opts);
|
|
1922
|
+
return this.base.exec(["docker", "exec", "--env", "MINDWIRE_ISOLATION=container", this.containerId, ...argv], opts);
|
|
1749
1923
|
}
|
|
1750
1924
|
async putFile(path, data, opts) {
|
|
1751
1925
|
const slash = path.lastIndexOf("/");
|
|
@@ -1845,7 +2019,7 @@ async function runContainer(host, cfg, emit2) {
|
|
|
1845
2019
|
"mindwire: running the daemon in a container needs an image (docker.image) to create one, or an existing container (docker.container) to attach to."
|
|
1846
2020
|
);
|
|
1847
2021
|
}
|
|
1848
|
-
const runArgs = ["docker", "run", "-d"];
|
|
2022
|
+
const runArgs = ["docker", "run", "-d", "--env", "MINDWIRE_ISOLATION=container"];
|
|
1849
2023
|
if (cfg.name) runArgs.push("--name", cfg.name);
|
|
1850
2024
|
runArgs.push("-p", `127.0.0.1:0:${port}`);
|
|
1851
2025
|
if (cfg.createArgs?.length) runArgs.push(...cfg.createArgs);
|
|
@@ -2172,7 +2346,7 @@ var DockerHost = class {
|
|
|
2172
2346
|
container;
|
|
2173
2347
|
async exec(argv) {
|
|
2174
2348
|
const { Writable } = await import('stream');
|
|
2175
|
-
const exec = await this.container.exec({ Cmd: argv, AttachStdout: true, AttachStderr: true });
|
|
2349
|
+
const exec = await this.container.exec({ Cmd: argv, Env: ["MINDWIRE_ISOLATION=container"], AttachStdout: true, AttachStderr: true });
|
|
2176
2350
|
const stream = await exec.start({ hijack: true, stdin: false });
|
|
2177
2351
|
const out = [];
|
|
2178
2352
|
const err = [];
|
|
@@ -2226,7 +2400,7 @@ async function provisionDocker(docker2, config = {}, onLog) {
|
|
|
2226
2400
|
// The runtime image starts mindwired itself. Do not replace its command: doing so turns the
|
|
2227
2401
|
// image ENTRYPOINT into `mindwired sleep infinity` and prevents the runtime from starting.
|
|
2228
2402
|
Image: image,
|
|
2229
|
-
Env: [`ADDR=:${port}`, `AGENT_TYPE=${agent}`, `AGENT_CWD=${agentCwd}
|
|
2403
|
+
Env: [`ADDR=:${port}`, `AGENT_TYPE=${agent}`, `AGENT_CWD=${agentCwd}`, "MINDWIRE_ISOLATION=container"],
|
|
2230
2404
|
Tty: false,
|
|
2231
2405
|
ExposedPorts: { [portKey]: {} },
|
|
2232
2406
|
HostConfig: { PortBindings: { [portKey]: [{ HostPort: "0" }] } },
|
|
@@ -2541,6 +2715,6 @@ function clearCatalogCache() {
|
|
|
2541
2715
|
inflight = null;
|
|
2542
2716
|
}
|
|
2543
2717
|
|
|
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 };
|
|
2718
|
+
export { ApiError, AuthApi, ContainerHost, GitAccessApi, Http, MODELS_DEV_URL, McpApi, Mindwire, MindwireError, NotifyApi, ProjectOperationsApi, PromptsApi, ProvidersApi, Run, RunFailedError, SDK_VERSION, ServiceApi, SurfacesApi, TimeoutError, WorkspaceApi, WorkspaceCollection, catalogModels, catalogProvider, catalogProviders, clearCatalogCache, docker, ensureDaemon, ensureDaemonBinary, loadCatalog, local, lookupModel, oblien, provisionContainer, provisionDocker, provisionOblien, provisionSsh, provisionSshContainer, remote, resolveLinuxDaemon, ssh, startEmbedded };
|
|
2545
2719
|
//# sourceMappingURL=index.js.map
|
|
2546
2720
|
//# sourceMappingURL=index.js.map
|