mindwire 0.1.15 → 0.1.19
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 +193 -28
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +4 -2
- package/dist/index.js +192 -29
- package/dist/index.js.map +1 -1
- package/dist/run.d.ts +3 -1
- package/dist/service.d.ts +24 -0
- package/dist/surfaces.d.ts +1 -1
- 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 +68 -1
- package/dist/workspace.d.ts +89 -2
- package/package.json +2 -2
package/dist/client.d.ts
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import { Http, type FetchLike } from "./http.js";
|
|
2
2
|
import { Run } from "./run.js";
|
|
3
|
-
import { WorkspaceApi } from "./workspace.js";
|
|
3
|
+
import { WorkspaceApi, type ProjectAuth } from "./workspace.js";
|
|
4
4
|
import { SurfacesApi } from "./surfaces.js";
|
|
5
|
+
import { ServiceApi } from "./service.js";
|
|
5
6
|
import { type Target } from "./target/index.js";
|
|
6
7
|
import type { EnsureEvent } from "./target/host.js";
|
|
7
|
-
import type { AgentInfo, AuthMethod, AuthState, AuthStatus, Catalog, ChatSummary, CustomProvider, DeleteResult, DoctorReport, Health, MCPServer, MemoryDoc, MemoryScope, Message, ModelInfo, Notification, NotifyChannel, NotifyChannelInput, NotifyChannelTestResult, NotifyConfigInput, NotifyConfigStatus, NotifyRule, NotifyRuleInput, ProcessFrame, PromptTemplate, ResolveOptions, SetupStatus, Stats, Subagent, TurnOptions } from "./types.js";
|
|
8
|
+
import type { AgentInfo, AuthMethod, AuthState, AuthStatus, Catalog, ChatSummary, CustomProvider, DeleteResult, DoctorReport, Health, MCPServer, MemoryDoc, MemoryScope, Message, ModelInfo, Notification, NotifyChannel, NotifyChannelInput, NotifyChannelTestResult, NotifyConfigInput, NotifyConfigStatus, NotifyRule, NotifyRuleInput, ProcessFrame, PromptTemplate, ResolveOptions, SetupStatus, HarnessSoftware, Stats, Subagent, TurnOptions } from "./types.js";
|
|
8
9
|
export interface MindwireOptions {
|
|
9
10
|
/**
|
|
10
11
|
* Default agent type for agent-scoped calls (e.g. `"claude-code"`). When omitted, the daemon
|
|
@@ -72,6 +73,7 @@ export declare class Mindwire {
|
|
|
72
73
|
/** Workspace registry: saved agent profiles, projects and chat relationships. */
|
|
73
74
|
readonly workspace: WorkspaceApi;
|
|
74
75
|
readonly surfaces: SurfacesApi;
|
|
76
|
+
readonly service: ServiceApi;
|
|
75
77
|
readonly http: Http;
|
|
76
78
|
/** The default agent type applied to agent-scoped calls, if set. */
|
|
77
79
|
readonly defaultAgent: string | undefined;
|
|
@@ -120,6 +122,10 @@ export declare class Mindwire {
|
|
|
120
122
|
catalog(): Promise<Catalog>;
|
|
121
123
|
/** `GET /agent` — capabilities + settings schema + auth methods/status for the selected agent. */
|
|
122
124
|
agent(scoped?: AgentScoped): Promise<AgentInfo>;
|
|
125
|
+
/** `GET /agent/software` — installed version, compatibility and approved update target. */
|
|
126
|
+
software(opts?: AgentScoped & {
|
|
127
|
+
refresh?: boolean;
|
|
128
|
+
}): Promise<HarnessSoftware>;
|
|
123
129
|
/**
|
|
124
130
|
* `GET /models` — the models the selected agent can run for the configured account. An empty array
|
|
125
131
|
* is valid (no credentials yet / offline). Throws a 400 {@link ApiError} for an agent whose model is
|
|
@@ -130,7 +136,7 @@ export declare class Mindwire {
|
|
|
130
136
|
doctor(scoped?: AgentScoped): Promise<DoctorReport>;
|
|
131
137
|
/** `POST /setup` — start the agent's install toolchain (background; poll {@link setupStatus}). */
|
|
132
138
|
setup(scoped?: AgentScoped): Promise<SetupStatus>;
|
|
133
|
-
/** `POST /update` —
|
|
139
|
+
/** `POST /update` — install the catalog's newest tested version compatible with this daemon. */
|
|
134
140
|
update(scoped?: AgentScoped): Promise<SetupStatus>;
|
|
135
141
|
/** `GET /setup` — current toolchain install progress. */
|
|
136
142
|
setupStatus(scoped?: AgentScoped): Promise<SetupStatus>;
|
|
@@ -189,6 +195,7 @@ export declare class Mindwire {
|
|
|
189
195
|
options?: TurnOptions;
|
|
190
196
|
mode?: "turn" | "resolve";
|
|
191
197
|
resolve?: ResolveOptions;
|
|
198
|
+
gitAuth?: ProjectAuth;
|
|
192
199
|
} & AgentScoped): Promise<Run>;
|
|
193
200
|
/**
|
|
194
201
|
* `POST /turns {mode:"resolve"}` — start a **global-resolve** run: instead of returning after one
|
|
@@ -208,6 +215,7 @@ export declare class Mindwire {
|
|
|
208
215
|
cwd?: string;
|
|
209
216
|
options?: TurnOptions;
|
|
210
217
|
resolve?: ResolveOptions;
|
|
218
|
+
gitAuth?: ProjectAuth;
|
|
211
219
|
} & AgentScoped): Promise<Run>;
|
|
212
220
|
/** `GET /runs/{id}` — fetch an existing run as a {@link Run} handle. */
|
|
213
221
|
run(id: string): Promise<Run>;
|
|
@@ -253,8 +261,14 @@ export declare class AuthApi {
|
|
|
253
261
|
begin(method: string, scoped?: AgentScoped): Promise<AuthState>;
|
|
254
262
|
/** `POST /auth/step` — submit fields, or poll an interactive login to completion. */
|
|
255
263
|
step(input: Record<string, string>, scoped?: AgentScoped): Promise<AuthState>;
|
|
264
|
+
/** Read one interactive attempt, scoped by the returned AuthState.flowId. */
|
|
265
|
+
poll(flowId: string, scoped?: AgentScoped): Promise<AuthState>;
|
|
266
|
+
/** Cancel this native login without affecting a newer sign-in attempt. */
|
|
267
|
+
cancel(flowId: string, scoped?: AgentScoped): Promise<AuthState>;
|
|
256
268
|
/** `GET /auth/status` — is the agent authenticated, and via which method. */
|
|
257
269
|
status(scoped?: AgentScoped): Promise<AuthStatus>;
|
|
270
|
+
/** `POST /auth/logout` — disconnect this harness once its running chats finish. */
|
|
271
|
+
logout(scoped?: AgentScoped): Promise<AuthStatus>;
|
|
258
272
|
}
|
|
259
273
|
/**
|
|
260
274
|
* The persistent prompt/memory surface for a scoped agent — the three layers beneath a turn's
|
package/dist/index.cjs
CHANGED
|
@@ -549,12 +549,14 @@ var WorkspaceCollection = class {
|
|
|
549
549
|
var WorkspaceApi = class {
|
|
550
550
|
constructor(mw) {
|
|
551
551
|
this.mw = mw;
|
|
552
|
+
this.git = new GitAccessApi(mw);
|
|
552
553
|
this.operations = new ProjectOperationsApi(mw);
|
|
553
554
|
this.agents = new WorkspaceCollection(mw, "agents");
|
|
554
555
|
this.projects = new WorkspaceCollection(mw, "projects");
|
|
555
556
|
this.chats = new WorkspaceCollection(mw, "chats");
|
|
556
557
|
}
|
|
557
558
|
mw;
|
|
559
|
+
git;
|
|
558
560
|
operations;
|
|
559
561
|
agents;
|
|
560
562
|
projects;
|
|
@@ -583,6 +585,75 @@ var WorkspaceApi = class {
|
|
|
583
585
|
return this.mw.http.request("POST", "/workspace/import", { body: records });
|
|
584
586
|
}
|
|
585
587
|
};
|
|
588
|
+
var GitAccessApi = class {
|
|
589
|
+
constructor(mw) {
|
|
590
|
+
this.mw = mw;
|
|
591
|
+
}
|
|
592
|
+
mw;
|
|
593
|
+
state() {
|
|
594
|
+
return this.mw.http.request("GET", "/workspace/git");
|
|
595
|
+
}
|
|
596
|
+
setDefault(connection, auth) {
|
|
597
|
+
return this.mw.http.request("PUT", "/workspace/git", { body: { connection, auth } });
|
|
598
|
+
}
|
|
599
|
+
forget(connectionId) {
|
|
600
|
+
return this.mw.http.request("DELETE", `/workspace/git/connections/${encodeURIComponent(connectionId)}`);
|
|
601
|
+
}
|
|
602
|
+
project(projectId) {
|
|
603
|
+
return this.mw.http.request("GET", `/workspace/projects/${encodeURIComponent(projectId)}/git`);
|
|
604
|
+
}
|
|
605
|
+
setProject(projectId, connection, expectedRevision, auth) {
|
|
606
|
+
return this.mw.http.request("PUT", `/workspace/projects/${encodeURIComponent(projectId)}/git`, {
|
|
607
|
+
body: { connection, expectedRevision, auth }
|
|
608
|
+
});
|
|
609
|
+
}
|
|
610
|
+
/** Compatibility call. Prefer start() and operation()/watch() for reconnectable writes. */
|
|
611
|
+
run(projectId, operation, auth) {
|
|
612
|
+
return this.mw.http.request("POST", `/workspace/projects/${encodeURIComponent(projectId)}/git/${operation}`, { body: { auth } });
|
|
613
|
+
}
|
|
614
|
+
/** Requires health.gitOperationsVersion >= 1 (>= 2 for branch changes). Acceptance persists before Git runs;
|
|
615
|
+
* disconnecting only detaches the client. The same ID/intent never runs twice.
|
|
616
|
+
*/
|
|
617
|
+
start(projectId, request) {
|
|
618
|
+
return this.mw.http.request("POST", `/workspace/projects/${encodeURIComponent(projectId)}/git/operations`, { body: request });
|
|
619
|
+
}
|
|
620
|
+
async operations(projectId, activeOnly = false) {
|
|
621
|
+
const result = await this.mw.http.request(
|
|
622
|
+
"GET",
|
|
623
|
+
`/workspace/projects/${encodeURIComponent(projectId)}/git/operations`,
|
|
624
|
+
{ query: { active: activeOnly, actionsVersion: 2 } }
|
|
625
|
+
);
|
|
626
|
+
return result.operations;
|
|
627
|
+
}
|
|
628
|
+
operation(id) {
|
|
629
|
+
return this.mw.http.request("GET", `/workspace/git/operations/${encodeURIComponent(id)}`);
|
|
630
|
+
}
|
|
631
|
+
cancel(id) {
|
|
632
|
+
return this.mw.http.request("POST", `/workspace/git/operations/${encodeURIComponent(id)}/cancel`);
|
|
633
|
+
}
|
|
634
|
+
/** Current snapshot followed by state changes. Aborting observation never cancels the operation. */
|
|
635
|
+
async *watch(id, opts = {}) {
|
|
636
|
+
const controller = new AbortController();
|
|
637
|
+
const abort = () => controller.abort();
|
|
638
|
+
opts.signal?.addEventListener("abort", abort, { once: true });
|
|
639
|
+
if (opts.signal?.aborted) controller.abort();
|
|
640
|
+
try {
|
|
641
|
+
const response = await this.mw.http.open("GET", `/workspace/git/operations/${encodeURIComponent(id)}/stream`, {
|
|
642
|
+
signal: controller.signal
|
|
643
|
+
});
|
|
644
|
+
let sequence = -1;
|
|
645
|
+
for await (const operation of readSSE(response.body, controller.signal)) {
|
|
646
|
+
if (operation.sequence > sequence) {
|
|
647
|
+
sequence = operation.sequence;
|
|
648
|
+
yield operation;
|
|
649
|
+
}
|
|
650
|
+
}
|
|
651
|
+
} finally {
|
|
652
|
+
controller.abort();
|
|
653
|
+
opts.signal?.removeEventListener("abort", abort);
|
|
654
|
+
}
|
|
655
|
+
}
|
|
656
|
+
};
|
|
586
657
|
|
|
587
658
|
// src/surfaces.ts
|
|
588
659
|
var SurfacesApi = class {
|
|
@@ -644,8 +715,27 @@ var SurfacesApi = class {
|
|
|
644
715
|
}
|
|
645
716
|
};
|
|
646
717
|
|
|
718
|
+
// src/service.ts
|
|
719
|
+
var ServiceApi = class {
|
|
720
|
+
constructor(client) {
|
|
721
|
+
this.client = client;
|
|
722
|
+
}
|
|
723
|
+
client;
|
|
724
|
+
updateStatus() {
|
|
725
|
+
return this.client.http.request("GET", "/service/update");
|
|
726
|
+
}
|
|
727
|
+
/** Download first. Acquire immediately before replacement; busy work returns 409.
|
|
728
|
+
* Admission stays closed until release, process exit, or the one-minute expiry. */
|
|
729
|
+
acquireUpdate() {
|
|
730
|
+
return this.client.http.request("POST", "/service/update");
|
|
731
|
+
}
|
|
732
|
+
releaseUpdate(id) {
|
|
733
|
+
return this.client.http.request("DELETE", `/service/update/${encodeURIComponent(id)}`);
|
|
734
|
+
}
|
|
735
|
+
};
|
|
736
|
+
|
|
647
737
|
// src/version.ts
|
|
648
|
-
var SDK_VERSION = "0.1.
|
|
738
|
+
var SDK_VERSION = "0.1.19" ;
|
|
649
739
|
|
|
650
740
|
// src/daemon-binary.ts
|
|
651
741
|
function supported(platform, arch) {
|
|
@@ -906,6 +996,7 @@ var Mindwire = class _Mindwire {
|
|
|
906
996
|
/** Workspace registry: saved agent profiles, projects and chat relationships. */
|
|
907
997
|
workspace;
|
|
908
998
|
surfaces;
|
|
999
|
+
service;
|
|
909
1000
|
http;
|
|
910
1001
|
/** The default agent type applied to agent-scoped calls, if set. */
|
|
911
1002
|
defaultAgent;
|
|
@@ -941,6 +1032,7 @@ var Mindwire = class _Mindwire {
|
|
|
941
1032
|
this.defaultAgent = opts.agent;
|
|
942
1033
|
this.workspace = new WorkspaceApi(this);
|
|
943
1034
|
this.surfaces = new SurfacesApi(this);
|
|
1035
|
+
this.service = new ServiceApi(this);
|
|
944
1036
|
this.auth = new AuthApi(this);
|
|
945
1037
|
this.prompts = new PromptsApi(this);
|
|
946
1038
|
this.mcp = new McpApi(this);
|
|
@@ -963,6 +1055,7 @@ var Mindwire = class _Mindwire {
|
|
|
963
1055
|
clone.defaultAgent = agent;
|
|
964
1056
|
clone.workspace = new WorkspaceApi(clone);
|
|
965
1057
|
clone.surfaces = new SurfacesApi(clone);
|
|
1058
|
+
clone.service = new ServiceApi(clone);
|
|
966
1059
|
clone.auth = new AuthApi(clone);
|
|
967
1060
|
clone.prompts = new PromptsApi(clone);
|
|
968
1061
|
clone.mcp = new McpApi(clone);
|
|
@@ -1011,6 +1104,12 @@ var Mindwire = class _Mindwire {
|
|
|
1011
1104
|
agent(scoped) {
|
|
1012
1105
|
return this.http.request("GET", "/agent", { query: this.agentParam(scoped) });
|
|
1013
1106
|
}
|
|
1107
|
+
/** `GET /agent/software` — installed version, compatibility and approved update target. */
|
|
1108
|
+
software(opts = {}) {
|
|
1109
|
+
return this.http.request("GET", "/agent/software", {
|
|
1110
|
+
query: { ...this.agentParam(opts), ...opts.refresh ? { refresh: "true" } : {} }
|
|
1111
|
+
});
|
|
1112
|
+
}
|
|
1014
1113
|
/**
|
|
1015
1114
|
* `GET /models` — the models the selected agent can run for the configured account. An empty array
|
|
1016
1115
|
* is valid (no credentials yet / offline). Throws a 400 {@link ApiError} for an agent whose model is
|
|
@@ -1028,7 +1127,7 @@ var Mindwire = class _Mindwire {
|
|
|
1028
1127
|
setup(scoped) {
|
|
1029
1128
|
return this.http.request("POST", "/setup", { query: this.agentParam(scoped) });
|
|
1030
1129
|
}
|
|
1031
|
-
/** `POST /update` —
|
|
1130
|
+
/** `POST /update` — install the catalog's newest tested version compatible with this daemon. */
|
|
1032
1131
|
update(scoped) {
|
|
1033
1132
|
return this.http.request("POST", "/update", { query: this.agentParam(scoped) });
|
|
1034
1133
|
}
|
|
@@ -1125,6 +1224,7 @@ var Mindwire = class _Mindwire {
|
|
|
1125
1224
|
if (input.options !== void 0) body.options = input.options;
|
|
1126
1225
|
if (input.mode !== void 0) body.mode = input.mode;
|
|
1127
1226
|
if (input.resolve !== void 0) body.resolve = input.resolve;
|
|
1227
|
+
if (input.gitAuth !== void 0) body.gitAuth = input.gitAuth;
|
|
1128
1228
|
const data = await this.http.request("POST", "/turns", {
|
|
1129
1229
|
query: this.agentParam(input),
|
|
1130
1230
|
body
|
|
@@ -1228,12 +1328,26 @@ var AuthApi = class {
|
|
|
1228
1328
|
body: input
|
|
1229
1329
|
});
|
|
1230
1330
|
}
|
|
1331
|
+
/** Read one interactive attempt, scoped by the returned AuthState.flowId. */
|
|
1332
|
+
poll(flowId, scoped) {
|
|
1333
|
+
return this.step({ _flowId: flowId }, scoped);
|
|
1334
|
+
}
|
|
1335
|
+
/** Cancel this native login without affecting a newer sign-in attempt. */
|
|
1336
|
+
cancel(flowId, scoped) {
|
|
1337
|
+
return this.step({ _flowId: flowId, _action: "cancel" }, scoped);
|
|
1338
|
+
}
|
|
1231
1339
|
/** `GET /auth/status` — is the agent authenticated, and via which method. */
|
|
1232
1340
|
status(scoped) {
|
|
1233
1341
|
return this.mw.http.request("GET", "/auth/status", {
|
|
1234
1342
|
query: this.mw.agentParam(scoped)
|
|
1235
1343
|
});
|
|
1236
1344
|
}
|
|
1345
|
+
/** `POST /auth/logout` — disconnect this harness once its running chats finish. */
|
|
1346
|
+
logout(scoped) {
|
|
1347
|
+
return this.mw.http.request("POST", "/auth/logout", {
|
|
1348
|
+
query: this.mw.agentParam(scoped)
|
|
1349
|
+
});
|
|
1350
|
+
}
|
|
1237
1351
|
};
|
|
1238
1352
|
var PromptsApi = class {
|
|
1239
1353
|
constructor(mw) {
|
|
@@ -1475,6 +1589,23 @@ var NotifyApi = class {
|
|
|
1475
1589
|
};
|
|
1476
1590
|
|
|
1477
1591
|
// src/target/host.ts
|
|
1592
|
+
function versionAtLeast(actual, desired) {
|
|
1593
|
+
if (actual === desired) return true;
|
|
1594
|
+
if (!actual || !/^\d+\.\d+\.\d+$/.test(actual) || !/^\d+\.\d+\.\d+$/.test(desired)) return false;
|
|
1595
|
+
const a = actual.split(".").map(Number), b = desired.split(".").map(Number);
|
|
1596
|
+
for (let i = 0; i < 3; i++) {
|
|
1597
|
+
if (a[i] !== b[i]) return a[i] > b[i];
|
|
1598
|
+
}
|
|
1599
|
+
return true;
|
|
1600
|
+
}
|
|
1601
|
+
var versionCheckScript = String.raw`version_at_least() {
|
|
1602
|
+
[ "$1" != "$2" ] || return 0
|
|
1603
|
+
[[ "$1" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]] && [[ "$2" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]] || return 1
|
|
1604
|
+
local a b c x y z
|
|
1605
|
+
IFS=. read -r a b c <<< "$1"
|
|
1606
|
+
IFS=. read -r x y z <<< "$2"
|
|
1607
|
+
(( 10#$a > 10#$x || (10#$a == 10#$x && 10#$b > 10#$y) || (10#$a == 10#$x && 10#$b == 10#$y && 10#$c >= 10#$z) ))
|
|
1608
|
+
}`;
|
|
1478
1609
|
function makeEmit(cfg) {
|
|
1479
1610
|
const target = cfg.target ?? "sandbox";
|
|
1480
1611
|
return (e) => {
|
|
@@ -1511,19 +1642,24 @@ async function ensureDaemon(host, cfg) {
|
|
|
1511
1642
|
message: health.version ? `daemon reachable (v${health.version})` : "daemon reachable (version unknown)",
|
|
1512
1643
|
version: health.version
|
|
1513
1644
|
});
|
|
1514
|
-
const upToDate = health.version
|
|
1645
|
+
const upToDate = versionAtLeast(health.version, desired);
|
|
1515
1646
|
if (!cfg.forceDeploy && (upToDate || !cfg.autoUpdate)) {
|
|
1516
1647
|
emit2({
|
|
1517
1648
|
phase: "skip",
|
|
1518
|
-
message: upToDate ? `daemon already at v${
|
|
1649
|
+
message: upToDate ? `daemon already at v${health.version}` : "keeping the running daemon",
|
|
1519
1650
|
version: health.version
|
|
1520
1651
|
});
|
|
1521
1652
|
return token;
|
|
1522
1653
|
}
|
|
1654
|
+
if ((health.serviceUpdateVersion ?? 0) < 1) {
|
|
1655
|
+
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.");
|
|
1656
|
+
emit2({ phase: "skip", message: "automatic update deferred: upgrade this legacy service explicitly to enable idle updates", version: health.version });
|
|
1657
|
+
return token;
|
|
1658
|
+
}
|
|
1523
1659
|
} else {
|
|
1524
1660
|
emit2({ phase: "probe", message: "no daemon reachable; deploying" });
|
|
1525
1661
|
}
|
|
1526
|
-
await deploy(host, cfg, emit2, token, directory);
|
|
1662
|
+
await deploy(host, cfg, emit2, token, directory, health.reachable);
|
|
1527
1663
|
if (!cfg.token) token = await readWorkspaceToken(host, directory) ?? token;
|
|
1528
1664
|
return token;
|
|
1529
1665
|
} catch (err) {
|
|
@@ -1536,7 +1672,7 @@ async function readWorkspaceToken(host, directory) {
|
|
|
1536
1672
|
const candidate = saved.stdout?.trim();
|
|
1537
1673
|
return candidate && candidate.length <= 4096 && /^[\x21-\x7e]+$/.test(candidate) ? candidate : void 0;
|
|
1538
1674
|
}
|
|
1539
|
-
async function deploy(host, cfg, emit2, token, directory) {
|
|
1675
|
+
async function deploy(host, cfg, emit2, token, directory, requireLease) {
|
|
1540
1676
|
const newPath = directory + "/mindwired.new";
|
|
1541
1677
|
const BIN = shellQuote(directory + "/mindwired"), BIN_NEW = shellQuote(newPath);
|
|
1542
1678
|
const STATE = shellQuote(directory + "/agent-state.json"), LOG = shellQuote(directory + "/daemon.log");
|
|
@@ -1578,6 +1714,7 @@ async function deploy(host, cfg, emit2, token, directory) {
|
|
|
1578
1714
|
` latest=$(curl -fsSL https://api.github.com/repos/oblien/mindwire/releases/latest | sed -n 's/.*"tag_name"[[:space:]]*:[[:space:]]*"\\([^"]*\\)".*/\\1/p')`,
|
|
1579
1715
|
' case "$latest" in v[0-9]*.[0-9]*.[0-9]*) ;; *) echo "MINDWIRE_FAIL no matching or latest release"; exit 1 ;; esac',
|
|
1580
1716
|
' release="https://github.com/oblien/mindwire/releases/download/$latest"',
|
|
1717
|
+
' mw_expected_version="${latest#v}"',
|
|
1581
1718
|
` asset="mindwired-$latest-${platform}-${arch}"`,
|
|
1582
1719
|
' download || { echo "MINDWIRE_FAIL latest release download failed"; exit 1; }',
|
|
1583
1720
|
"fi",
|
|
@@ -1597,29 +1734,43 @@ async function deploy(host, cfg, emit2, token, directory) {
|
|
|
1597
1734
|
'else echo "MINDWIRE_FAIL No supported update lock is available (flock on Linux, lockf on macOS)."; exit 1; fi',
|
|
1598
1735
|
`mw_token=${shellQuote(token)}`,
|
|
1599
1736
|
!cfg.token ? `if [ -s ${TOKEN} ]; then mw_token=$(cat ${TOKEN}); fi` : "",
|
|
1737
|
+
'mw_lease=""',
|
|
1738
|
+
`mw_lease_file=${shellQuote(directory)}/update-lease-$$.json`,
|
|
1739
|
+
"cleanup_update() {",
|
|
1740
|
+
` 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`,
|
|
1741
|
+
` rm -f ${BIN_NEW} "$mw_lease_file"${stagedUpload ? " " + shellQuote(stagedUpload) : ""}`,
|
|
1742
|
+
"}",
|
|
1743
|
+
// Shared staging is only removed while this process owns the workspace lock.
|
|
1744
|
+
"trap cleanup_update EXIT",
|
|
1745
|
+
versionCheckScript,
|
|
1746
|
+
`mw_desired=${shellQuote(desired)}`,
|
|
1747
|
+
'mw_expected_version="$mw_desired"',
|
|
1748
|
+
`mw_health=$(curl -fsS --max-time 3 -H "Authorization: Bearer $mw_token" http://127.0.0.1:${cfg.port}/healthz 2>/dev/null || true)`,
|
|
1600
1749
|
!cfg.forceDeploy ? [
|
|
1601
|
-
`mw_health=$(curl -fsS --max-time 3 -H "Authorization: Bearer $mw_token" http://127.0.0.1:${cfg.port}/healthz 2>/dev/null || true)`,
|
|
1602
1750
|
`mw_version=$(printf '%s' "$mw_health" | sed -n 's/.*"version"[[:space:]]*:[[:space:]]*"\\([^" ]*\\)".*/\\1/p')`,
|
|
1603
|
-
`if [ -n "$mw_health" ] && ${cfg.autoUpdate ?
|
|
1751
|
+
`if [ -n "$mw_health" ] && ${cfg.autoUpdate ? 'version_at_least "$mw_version" "$mw_desired"' : "true"}; then echo MINDWIRE_READY; exit 0; fi`
|
|
1604
1752
|
].join("\n") : "",
|
|
1605
1753
|
// macOS ships POSIX setsid in Perl, but does not include Linux's setsid executable.
|
|
1606
1754
|
"if command -v setsid >/dev/null 2>&1; then mw_detach=(setsid);",
|
|
1607
1755
|
`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: $!";');`,
|
|
1608
1756
|
'else echo "MINDWIRE_FAIL Cannot start the Mindwire service: setsid or Perl is required."; exit 1; fi',
|
|
1609
1757
|
acquire,
|
|
1610
|
-
//
|
|
1611
|
-
//
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
"
|
|
1615
|
-
|
|
1616
|
-
"
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
'
|
|
1620
|
-
"
|
|
1621
|
-
|
|
1622
|
-
"
|
|
1758
|
+
// The service decides idleness after transfer. Never stop arbitrary processes
|
|
1759
|
+
// or interrupt a chat that began while the SDK was downloading its binary.
|
|
1760
|
+
`mw_health=$(curl -fsS --max-time 3 -H "Authorization: Bearer $mw_token" http://127.0.0.1:${cfg.port}/healthz 2>/dev/null || true)`,
|
|
1761
|
+
'if [ -n "$mw_health" ]; then',
|
|
1762
|
+
` 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`,
|
|
1763
|
+
' case "$mw_status" in',
|
|
1764
|
+
" 201)",
|
|
1765
|
+
` mw_lease=$(sed -n 's/.*"id"[[:space:]]*:[[:space:]]*"\\([[:alnum:]]*\\)".*/\\1/p' "$mw_lease_file")`,
|
|
1766
|
+
` mw_service_pid=$(sed -n 's/.*"pid"[[:space:]]*:[[:space:]]*\\([0-9][0-9]*\\).*/\\1/p' "$mw_lease_file")`,
|
|
1767
|
+
' [ -n "$mw_lease" ] && [[ "$mw_service_pid" =~ ^[0-9]+$ ]] && [ "$mw_service_pid" -gt 1 ] || { echo "MINDWIRE_FAIL invalid service update lease"; exit 1; }',
|
|
1768
|
+
' kill "$mw_service_pid" ;;',
|
|
1769
|
+
" 409) echo MINDWIRE_UPDATE_DEFERRED; exit 0 ;;",
|
|
1770
|
+
' 404) echo "MINDWIRE_FAIL this legacy service requires an explicit upgrade before idle updates"; exit 1 ;;',
|
|
1771
|
+
' *) echo "MINDWIRE_FAIL could not reserve an idle service update"; exit 1 ;;',
|
|
1772
|
+
" esac",
|
|
1773
|
+
requireLease ? 'else echo "MINDWIRE_FAIL the service went offline before its idle update could be reserved"; exit 1' : "",
|
|
1623
1774
|
"fi",
|
|
1624
1775
|
"sleep 0.3",
|
|
1625
1776
|
`mv -f ${BIN_NEW} ${BIN}`,
|
|
@@ -1629,7 +1780,10 @@ async function deploy(host, cfg, emit2, token, directory) {
|
|
|
1629
1780
|
`"\${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>&- &`,
|
|
1630
1781
|
// Health-poll from inside the VM (loopback) and emit a marker — exit codes are unreliable here.
|
|
1631
1782
|
'mw_pid=$!; mw_exit=""; mw_deadline=$((SECONDS + 30))',
|
|
1632
|
-
|
|
1783
|
+
"while (( SECONDS < mw_deadline )); do",
|
|
1784
|
+
` 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)`,
|
|
1785
|
+
` mw_version=$(printf '%s' "$mw_health" | sed -n 's/.*"version"[[:space:]]*:[[:space:]]*"\\([^" ]*\\)".*/\\1/p')`,
|
|
1786
|
+
' if version_at_least "$mw_version" "$mw_expected_version"; then echo MINDWIRE_READY; exit 0; fi',
|
|
1633
1787
|
' if [ -z "$mw_exit" ] && ! kill -0 "$mw_pid" 2>/dev/null; then',
|
|
1634
1788
|
' mw_exit=0; wait "$mw_pid" || mw_exit=$?; [ "$mw_exit" = 0 ] || break',
|
|
1635
1789
|
" fi",
|
|
@@ -1637,13 +1791,18 @@ async function deploy(host, cfg, emit2, token, directory) {
|
|
|
1637
1791
|
"done",
|
|
1638
1792
|
`mw_tail=$(tail -n 40 ${LOG} 2>/dev/null || true)`,
|
|
1639
1793
|
'if [ -n "$mw_exit" ] && [ "$mw_exit" != 0 ]; then mw_reason="Mindwire exited during startup (status $mw_exit).";',
|
|
1640
|
-
'else mw_reason="The Mindwire service did not become ready."; fi',
|
|
1794
|
+
'else mw_reason="The Mindwire service did not become ready at v$mw_expected_version (reported: $mw_version)."; fi',
|
|
1641
1795
|
'[ -z "$mw_tail" ] || mw_reason="$mw_reason $mw_tail"',
|
|
1642
1796
|
'printf "MINDWIRE_FAIL %s\\n" "$mw_reason"'
|
|
1643
1797
|
].join("\n");
|
|
1644
1798
|
emit2({ phase: "launch", message: "launching daemon" });
|
|
1645
1799
|
const res = await host.exec(["bash", "-lc", script], { timeoutSeconds: 420 });
|
|
1646
1800
|
const out = res.stdout ?? "";
|
|
1801
|
+
if (out.includes("MINDWIRE_UPDATE_DEFERRED")) {
|
|
1802
|
+
if (cfg.forceDeploy) throw new MindwireError("The workspace is busy. Wait for its operations to finish before deploying the service.");
|
|
1803
|
+
emit2({ phase: "skip", message: "service update deferred while workspace operations are running" });
|
|
1804
|
+
return;
|
|
1805
|
+
}
|
|
1647
1806
|
if (!out.includes("MINDWIRE_READY")) {
|
|
1648
1807
|
throw new MindwireError(
|
|
1649
1808
|
"mindwire: the in-sandbox daemon did not become healthy after deploy.\n" + (out || res.stderr || res.error || "(no output)").trim()
|
|
@@ -1658,7 +1817,11 @@ async function probeHealth(host, port, token) {
|
|
|
1658
1817
|
if (!body) return { reachable: false };
|
|
1659
1818
|
try {
|
|
1660
1819
|
const j = JSON.parse(body);
|
|
1661
|
-
return {
|
|
1820
|
+
return {
|
|
1821
|
+
reachable: true,
|
|
1822
|
+
version: typeof j.version === "string" ? j.version : void 0,
|
|
1823
|
+
serviceUpdateVersion: typeof j.serviceUpdateVersion === "number" ? j.serviceUpdateVersion : void 0
|
|
1824
|
+
};
|
|
1662
1825
|
} catch {
|
|
1663
1826
|
return { reachable: true };
|
|
1664
1827
|
}
|
|
@@ -1759,7 +1922,7 @@ var ContainerHost = class {
|
|
|
1759
1922
|
base;
|
|
1760
1923
|
containerId;
|
|
1761
1924
|
exec(argv, opts) {
|
|
1762
|
-
return this.base.exec(["docker", "exec", this.containerId, ...argv], opts);
|
|
1925
|
+
return this.base.exec(["docker", "exec", "--env", "MINDWIRE_ISOLATION=container", this.containerId, ...argv], opts);
|
|
1763
1926
|
}
|
|
1764
1927
|
async putFile(path, data, opts) {
|
|
1765
1928
|
const slash = path.lastIndexOf("/");
|
|
@@ -1859,7 +2022,7 @@ async function runContainer(host, cfg, emit2) {
|
|
|
1859
2022
|
"mindwire: running the daemon in a container needs an image (docker.image) to create one, or an existing container (docker.container) to attach to."
|
|
1860
2023
|
);
|
|
1861
2024
|
}
|
|
1862
|
-
const runArgs = ["docker", "run", "-d"];
|
|
2025
|
+
const runArgs = ["docker", "run", "-d", "--env", "MINDWIRE_ISOLATION=container"];
|
|
1863
2026
|
if (cfg.name) runArgs.push("--name", cfg.name);
|
|
1864
2027
|
runArgs.push("-p", `127.0.0.1:0:${port}`);
|
|
1865
2028
|
if (cfg.createArgs?.length) runArgs.push(...cfg.createArgs);
|
|
@@ -2186,7 +2349,7 @@ var DockerHost = class {
|
|
|
2186
2349
|
container;
|
|
2187
2350
|
async exec(argv) {
|
|
2188
2351
|
const { Writable } = await import('stream');
|
|
2189
|
-
const exec = await this.container.exec({ Cmd: argv, AttachStdout: true, AttachStderr: true });
|
|
2352
|
+
const exec = await this.container.exec({ Cmd: argv, Env: ["MINDWIRE_ISOLATION=container"], AttachStdout: true, AttachStderr: true });
|
|
2190
2353
|
const stream = await exec.start({ hijack: true, stdin: false });
|
|
2191
2354
|
const out = [];
|
|
2192
2355
|
const err = [];
|
|
@@ -2240,7 +2403,7 @@ async function provisionDocker(docker2, config = {}, onLog) {
|
|
|
2240
2403
|
// The runtime image starts mindwired itself. Do not replace its command: doing so turns the
|
|
2241
2404
|
// image ENTRYPOINT into `mindwired sleep infinity` and prevents the runtime from starting.
|
|
2242
2405
|
Image: image,
|
|
2243
|
-
Env: [`ADDR=:${port}`, `AGENT_TYPE=${agent}`, `AGENT_CWD=${agentCwd}
|
|
2406
|
+
Env: [`ADDR=:${port}`, `AGENT_TYPE=${agent}`, `AGENT_CWD=${agentCwd}`, "MINDWIRE_ISOLATION=container"],
|
|
2244
2407
|
Tty: false,
|
|
2245
2408
|
ExposedPorts: { [portKey]: {} },
|
|
2246
2409
|
HostConfig: { PortBindings: { [portKey]: [{ HostPort: "0" }] } },
|
|
@@ -2558,6 +2721,7 @@ function clearCatalogCache() {
|
|
|
2558
2721
|
exports.ApiError = ApiError;
|
|
2559
2722
|
exports.AuthApi = AuthApi;
|
|
2560
2723
|
exports.ContainerHost = ContainerHost;
|
|
2724
|
+
exports.GitAccessApi = GitAccessApi;
|
|
2561
2725
|
exports.Http = Http;
|
|
2562
2726
|
exports.MODELS_DEV_URL = MODELS_DEV_URL;
|
|
2563
2727
|
exports.McpApi = McpApi;
|
|
@@ -2570,6 +2734,7 @@ exports.ProvidersApi = ProvidersApi;
|
|
|
2570
2734
|
exports.Run = Run;
|
|
2571
2735
|
exports.RunFailedError = RunFailedError;
|
|
2572
2736
|
exports.SDK_VERSION = SDK_VERSION;
|
|
2737
|
+
exports.ServiceApi = ServiceApi;
|
|
2573
2738
|
exports.SurfacesApi = SurfacesApi;
|
|
2574
2739
|
exports.TimeoutError = TimeoutError;
|
|
2575
2740
|
exports.WorkspaceApi = WorkspaceApi;
|