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/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
|
@@ -383,8 +383,9 @@ var Run = class _Run {
|
|
|
383
383
|
await this.http.request("POST", `/runs/${encodeURIComponent(this.id)}/cancel`);
|
|
384
384
|
}
|
|
385
385
|
/**
|
|
386
|
-
* Answer a
|
|
387
|
-
*
|
|
386
|
+
* Answer a pending permission, question or plan. Message-mode questions remain answerable
|
|
387
|
+
* after completion; the daemon steers or resumes the conversation. Read the chat's latest
|
|
388
|
+
* run after replying to a completed run. Requires the agent's `respond` capability.
|
|
388
389
|
*/
|
|
389
390
|
async respond(input = {}) {
|
|
390
391
|
await this.http.request("POST", `/runs/${encodeURIComponent(this.id)}/respond`, { body: input });
|
|
@@ -548,12 +549,14 @@ var WorkspaceCollection = class {
|
|
|
548
549
|
var WorkspaceApi = class {
|
|
549
550
|
constructor(mw) {
|
|
550
551
|
this.mw = mw;
|
|
552
|
+
this.git = new GitAccessApi(mw);
|
|
551
553
|
this.operations = new ProjectOperationsApi(mw);
|
|
552
554
|
this.agents = new WorkspaceCollection(mw, "agents");
|
|
553
555
|
this.projects = new WorkspaceCollection(mw, "projects");
|
|
554
556
|
this.chats = new WorkspaceCollection(mw, "chats");
|
|
555
557
|
}
|
|
556
558
|
mw;
|
|
559
|
+
git;
|
|
557
560
|
operations;
|
|
558
561
|
agents;
|
|
559
562
|
projects;
|
|
@@ -582,6 +585,75 @@ var WorkspaceApi = class {
|
|
|
582
585
|
return this.mw.http.request("POST", "/workspace/import", { body: records });
|
|
583
586
|
}
|
|
584
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. 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 } }
|
|
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
|
+
};
|
|
585
657
|
|
|
586
658
|
// src/surfaces.ts
|
|
587
659
|
var SurfacesApi = class {
|
|
@@ -643,8 +715,27 @@ var SurfacesApi = class {
|
|
|
643
715
|
}
|
|
644
716
|
};
|
|
645
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
|
+
|
|
646
737
|
// src/version.ts
|
|
647
|
-
var SDK_VERSION = "0.1.
|
|
738
|
+
var SDK_VERSION = "0.1.18" ;
|
|
648
739
|
|
|
649
740
|
// src/daemon-binary.ts
|
|
650
741
|
function supported(platform, arch) {
|
|
@@ -905,6 +996,7 @@ var Mindwire = class _Mindwire {
|
|
|
905
996
|
/** Workspace registry: saved agent profiles, projects and chat relationships. */
|
|
906
997
|
workspace;
|
|
907
998
|
surfaces;
|
|
999
|
+
service;
|
|
908
1000
|
http;
|
|
909
1001
|
/** The default agent type applied to agent-scoped calls, if set. */
|
|
910
1002
|
defaultAgent;
|
|
@@ -940,6 +1032,7 @@ var Mindwire = class _Mindwire {
|
|
|
940
1032
|
this.defaultAgent = opts.agent;
|
|
941
1033
|
this.workspace = new WorkspaceApi(this);
|
|
942
1034
|
this.surfaces = new SurfacesApi(this);
|
|
1035
|
+
this.service = new ServiceApi(this);
|
|
943
1036
|
this.auth = new AuthApi(this);
|
|
944
1037
|
this.prompts = new PromptsApi(this);
|
|
945
1038
|
this.mcp = new McpApi(this);
|
|
@@ -962,6 +1055,7 @@ var Mindwire = class _Mindwire {
|
|
|
962
1055
|
clone.defaultAgent = agent;
|
|
963
1056
|
clone.workspace = new WorkspaceApi(clone);
|
|
964
1057
|
clone.surfaces = new SurfacesApi(clone);
|
|
1058
|
+
clone.service = new ServiceApi(clone);
|
|
965
1059
|
clone.auth = new AuthApi(clone);
|
|
966
1060
|
clone.prompts = new PromptsApi(clone);
|
|
967
1061
|
clone.mcp = new McpApi(clone);
|
|
@@ -1010,6 +1104,12 @@ var Mindwire = class _Mindwire {
|
|
|
1010
1104
|
agent(scoped) {
|
|
1011
1105
|
return this.http.request("GET", "/agent", { query: this.agentParam(scoped) });
|
|
1012
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
|
+
}
|
|
1013
1113
|
/**
|
|
1014
1114
|
* `GET /models` — the models the selected agent can run for the configured account. An empty array
|
|
1015
1115
|
* is valid (no credentials yet / offline). Throws a 400 {@link ApiError} for an agent whose model is
|
|
@@ -1027,7 +1127,7 @@ var Mindwire = class _Mindwire {
|
|
|
1027
1127
|
setup(scoped) {
|
|
1028
1128
|
return this.http.request("POST", "/setup", { query: this.agentParam(scoped) });
|
|
1029
1129
|
}
|
|
1030
|
-
/** `POST /update` —
|
|
1130
|
+
/** `POST /update` — install the catalog's newest tested version compatible with this daemon. */
|
|
1031
1131
|
update(scoped) {
|
|
1032
1132
|
return this.http.request("POST", "/update", { query: this.agentParam(scoped) });
|
|
1033
1133
|
}
|
|
@@ -1124,6 +1224,7 @@ var Mindwire = class _Mindwire {
|
|
|
1124
1224
|
if (input.options !== void 0) body.options = input.options;
|
|
1125
1225
|
if (input.mode !== void 0) body.mode = input.mode;
|
|
1126
1226
|
if (input.resolve !== void 0) body.resolve = input.resolve;
|
|
1227
|
+
if (input.gitAuth !== void 0) body.gitAuth = input.gitAuth;
|
|
1127
1228
|
const data = await this.http.request("POST", "/turns", {
|
|
1128
1229
|
query: this.agentParam(input),
|
|
1129
1230
|
body
|
|
@@ -1227,12 +1328,26 @@ var AuthApi = class {
|
|
|
1227
1328
|
body: input
|
|
1228
1329
|
});
|
|
1229
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
|
+
}
|
|
1230
1339
|
/** `GET /auth/status` — is the agent authenticated, and via which method. */
|
|
1231
1340
|
status(scoped) {
|
|
1232
1341
|
return this.mw.http.request("GET", "/auth/status", {
|
|
1233
1342
|
query: this.mw.agentParam(scoped)
|
|
1234
1343
|
});
|
|
1235
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
|
+
}
|
|
1236
1351
|
};
|
|
1237
1352
|
var PromptsApi = class {
|
|
1238
1353
|
constructor(mw) {
|
|
@@ -1474,6 +1589,23 @@ var NotifyApi = class {
|
|
|
1474
1589
|
};
|
|
1475
1590
|
|
|
1476
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
|
+
}`;
|
|
1477
1609
|
function makeEmit(cfg) {
|
|
1478
1610
|
const target = cfg.target ?? "sandbox";
|
|
1479
1611
|
return (e) => {
|
|
@@ -1510,19 +1642,24 @@ async function ensureDaemon(host, cfg) {
|
|
|
1510
1642
|
message: health.version ? `daemon reachable (v${health.version})` : "daemon reachable (version unknown)",
|
|
1511
1643
|
version: health.version
|
|
1512
1644
|
});
|
|
1513
|
-
const upToDate = health.version
|
|
1645
|
+
const upToDate = versionAtLeast(health.version, desired);
|
|
1514
1646
|
if (!cfg.forceDeploy && (upToDate || !cfg.autoUpdate)) {
|
|
1515
1647
|
emit2({
|
|
1516
1648
|
phase: "skip",
|
|
1517
|
-
message: upToDate ? `daemon already at v${
|
|
1649
|
+
message: upToDate ? `daemon already at v${health.version}` : "keeping the running daemon",
|
|
1518
1650
|
version: health.version
|
|
1519
1651
|
});
|
|
1520
1652
|
return token;
|
|
1521
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
|
+
}
|
|
1522
1659
|
} else {
|
|
1523
1660
|
emit2({ phase: "probe", message: "no daemon reachable; deploying" });
|
|
1524
1661
|
}
|
|
1525
|
-
await deploy(host, cfg, emit2, token, directory);
|
|
1662
|
+
await deploy(host, cfg, emit2, token, directory, health.reachable);
|
|
1526
1663
|
if (!cfg.token) token = await readWorkspaceToken(host, directory) ?? token;
|
|
1527
1664
|
return token;
|
|
1528
1665
|
} catch (err) {
|
|
@@ -1535,7 +1672,7 @@ async function readWorkspaceToken(host, directory) {
|
|
|
1535
1672
|
const candidate = saved.stdout?.trim();
|
|
1536
1673
|
return candidate && candidate.length <= 4096 && /^[\x21-\x7e]+$/.test(candidate) ? candidate : void 0;
|
|
1537
1674
|
}
|
|
1538
|
-
async function deploy(host, cfg, emit2, token, directory) {
|
|
1675
|
+
async function deploy(host, cfg, emit2, token, directory, requireLease) {
|
|
1539
1676
|
const newPath = directory + "/mindwired.new";
|
|
1540
1677
|
const BIN = shellQuote(directory + "/mindwired"), BIN_NEW = shellQuote(newPath);
|
|
1541
1678
|
const STATE = shellQuote(directory + "/agent-state.json"), LOG = shellQuote(directory + "/daemon.log");
|
|
@@ -1577,6 +1714,7 @@ async function deploy(host, cfg, emit2, token, directory) {
|
|
|
1577
1714
|
` latest=$(curl -fsSL https://api.github.com/repos/oblien/mindwire/releases/latest | sed -n 's/.*"tag_name"[[:space:]]*:[[:space:]]*"\\([^"]*\\)".*/\\1/p')`,
|
|
1578
1715
|
' case "$latest" in v[0-9]*.[0-9]*.[0-9]*) ;; *) echo "MINDWIRE_FAIL no matching or latest release"; exit 1 ;; esac',
|
|
1579
1716
|
' release="https://github.com/oblien/mindwire/releases/download/$latest"',
|
|
1717
|
+
' mw_expected_version="${latest#v}"',
|
|
1580
1718
|
` asset="mindwired-$latest-${platform}-${arch}"`,
|
|
1581
1719
|
' download || { echo "MINDWIRE_FAIL latest release download failed"; exit 1; }',
|
|
1582
1720
|
"fi",
|
|
@@ -1596,43 +1734,75 @@ async function deploy(host, cfg, emit2, token, directory) {
|
|
|
1596
1734
|
'else echo "MINDWIRE_FAIL No supported update lock is available (flock on Linux, lockf on macOS)."; exit 1; fi',
|
|
1597
1735
|
`mw_token=${shellQuote(token)}`,
|
|
1598
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)`,
|
|
1599
1749
|
!cfg.forceDeploy ? [
|
|
1600
|
-
`mw_health=$(curl -fsS --max-time 3 -H "Authorization: Bearer $mw_token" http://127.0.0.1:${cfg.port}/healthz 2>/dev/null || true)`,
|
|
1601
1750
|
`mw_version=$(printf '%s' "$mw_health" | sed -n 's/.*"version"[[:space:]]*:[[:space:]]*"\\([^" ]*\\)".*/\\1/p')`,
|
|
1602
|
-
`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`
|
|
1603
1752
|
].join("\n") : "",
|
|
1604
1753
|
// macOS ships POSIX setsid in Perl, but does not include Linux's setsid executable.
|
|
1605
1754
|
"if command -v setsid >/dev/null 2>&1; then mw_detach=(setsid);",
|
|
1606
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: $!";');`,
|
|
1607
1756
|
'else echo "MINDWIRE_FAIL Cannot start the Mindwire service: setsid or Perl is required."; exit 1; fi',
|
|
1608
1757
|
acquire,
|
|
1609
|
-
//
|
|
1610
|
-
//
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
"
|
|
1614
|
-
|
|
1615
|
-
"
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
'
|
|
1619
|
-
"
|
|
1620
|
-
|
|
1621
|
-
"
|
|
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' : "",
|
|
1622
1774
|
"fi",
|
|
1623
1775
|
"sleep 0.3",
|
|
1624
1776
|
`mv -f ${BIN_NEW} ${BIN}`,
|
|
1625
1777
|
`chmod +x ${BIN}`,
|
|
1626
1778
|
// Detach so the daemon survives this exec's shell exiting. ADDR=":<port>" binds 0.0.0.0.
|
|
1627
|
-
|
|
1779
|
+
// Ignore HUP directly: macOS nohup can fail after setsid in a headless workspace.
|
|
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>&- &`,
|
|
1628
1781
|
// Health-poll from inside the VM (loopback) and emit a marker — exit codes are unreliable here.
|
|
1629
|
-
|
|
1630
|
-
"
|
|
1631
|
-
`
|
|
1782
|
+
'mw_pid=$!; mw_exit=""; mw_deadline=$((SECONDS + 30))',
|
|
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',
|
|
1787
|
+
' if [ -z "$mw_exit" ] && ! kill -0 "$mw_pid" 2>/dev/null; then',
|
|
1788
|
+
' mw_exit=0; wait "$mw_pid" || mw_exit=$?; [ "$mw_exit" = 0 ] || break',
|
|
1789
|
+
" fi",
|
|
1790
|
+
" sleep 0.25",
|
|
1791
|
+
"done",
|
|
1792
|
+
`mw_tail=$(tail -n 40 ${LOG} 2>/dev/null || true)`,
|
|
1793
|
+
'if [ -n "$mw_exit" ] && [ "$mw_exit" != 0 ]; then mw_reason="Mindwire exited during startup (status $mw_exit).";',
|
|
1794
|
+
'else mw_reason="The Mindwire service did not become ready at v$mw_expected_version (reported: $mw_version)."; fi',
|
|
1795
|
+
'[ -z "$mw_tail" ] || mw_reason="$mw_reason $mw_tail"',
|
|
1796
|
+
'printf "MINDWIRE_FAIL %s\\n" "$mw_reason"'
|
|
1632
1797
|
].join("\n");
|
|
1633
1798
|
emit2({ phase: "launch", message: "launching daemon" });
|
|
1634
1799
|
const res = await host.exec(["bash", "-lc", script], { timeoutSeconds: 420 });
|
|
1635
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
|
+
}
|
|
1636
1806
|
if (!out.includes("MINDWIRE_READY")) {
|
|
1637
1807
|
throw new MindwireError(
|
|
1638
1808
|
"mindwire: the in-sandbox daemon did not become healthy after deploy.\n" + (out || res.stderr || res.error || "(no output)").trim()
|
|
@@ -1647,7 +1817,11 @@ async function probeHealth(host, port, token) {
|
|
|
1647
1817
|
if (!body) return { reachable: false };
|
|
1648
1818
|
try {
|
|
1649
1819
|
const j = JSON.parse(body);
|
|
1650
|
-
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
|
+
};
|
|
1651
1825
|
} catch {
|
|
1652
1826
|
return { reachable: true };
|
|
1653
1827
|
}
|
|
@@ -1748,7 +1922,7 @@ var ContainerHost = class {
|
|
|
1748
1922
|
base;
|
|
1749
1923
|
containerId;
|
|
1750
1924
|
exec(argv, opts) {
|
|
1751
|
-
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);
|
|
1752
1926
|
}
|
|
1753
1927
|
async putFile(path, data, opts) {
|
|
1754
1928
|
const slash = path.lastIndexOf("/");
|
|
@@ -1848,7 +2022,7 @@ async function runContainer(host, cfg, emit2) {
|
|
|
1848
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."
|
|
1849
2023
|
);
|
|
1850
2024
|
}
|
|
1851
|
-
const runArgs = ["docker", "run", "-d"];
|
|
2025
|
+
const runArgs = ["docker", "run", "-d", "--env", "MINDWIRE_ISOLATION=container"];
|
|
1852
2026
|
if (cfg.name) runArgs.push("--name", cfg.name);
|
|
1853
2027
|
runArgs.push("-p", `127.0.0.1:0:${port}`);
|
|
1854
2028
|
if (cfg.createArgs?.length) runArgs.push(...cfg.createArgs);
|
|
@@ -2175,7 +2349,7 @@ var DockerHost = class {
|
|
|
2175
2349
|
container;
|
|
2176
2350
|
async exec(argv) {
|
|
2177
2351
|
const { Writable } = await import('stream');
|
|
2178
|
-
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 });
|
|
2179
2353
|
const stream = await exec.start({ hijack: true, stdin: false });
|
|
2180
2354
|
const out = [];
|
|
2181
2355
|
const err = [];
|
|
@@ -2229,7 +2403,7 @@ async function provisionDocker(docker2, config = {}, onLog) {
|
|
|
2229
2403
|
// The runtime image starts mindwired itself. Do not replace its command: doing so turns the
|
|
2230
2404
|
// image ENTRYPOINT into `mindwired sleep infinity` and prevents the runtime from starting.
|
|
2231
2405
|
Image: image,
|
|
2232
|
-
Env: [`ADDR=:${port}`, `AGENT_TYPE=${agent}`, `AGENT_CWD=${agentCwd}
|
|
2406
|
+
Env: [`ADDR=:${port}`, `AGENT_TYPE=${agent}`, `AGENT_CWD=${agentCwd}`, "MINDWIRE_ISOLATION=container"],
|
|
2233
2407
|
Tty: false,
|
|
2234
2408
|
ExposedPorts: { [portKey]: {} },
|
|
2235
2409
|
HostConfig: { PortBindings: { [portKey]: [{ HostPort: "0" }] } },
|
|
@@ -2547,6 +2721,7 @@ function clearCatalogCache() {
|
|
|
2547
2721
|
exports.ApiError = ApiError;
|
|
2548
2722
|
exports.AuthApi = AuthApi;
|
|
2549
2723
|
exports.ContainerHost = ContainerHost;
|
|
2724
|
+
exports.GitAccessApi = GitAccessApi;
|
|
2550
2725
|
exports.Http = Http;
|
|
2551
2726
|
exports.MODELS_DEV_URL = MODELS_DEV_URL;
|
|
2552
2727
|
exports.McpApi = McpApi;
|
|
@@ -2559,6 +2734,7 @@ exports.ProvidersApi = ProvidersApi;
|
|
|
2559
2734
|
exports.Run = Run;
|
|
2560
2735
|
exports.RunFailedError = RunFailedError;
|
|
2561
2736
|
exports.SDK_VERSION = SDK_VERSION;
|
|
2737
|
+
exports.ServiceApi = ServiceApi;
|
|
2562
2738
|
exports.SurfacesApi = SurfacesApi;
|
|
2563
2739
|
exports.TimeoutError = TimeoutError;
|
|
2564
2740
|
exports.WorkspaceApi = WorkspaceApi;
|