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/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 { WorkspaceApi, WorkspaceCollection, ProjectOperationsApi } from "./workspace.js";
36
- export type { ProjectRequest, ProjectAuth, ProjectOperation, ProjectRemoveRequest } from "./workspace.js";
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, GitIdentity, 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
@@ -546,12 +546,14 @@ var WorkspaceCollection = class {
546
546
  var WorkspaceApi = class {
547
547
  constructor(mw) {
548
548
  this.mw = mw;
549
+ this.git = new GitAccessApi(mw);
549
550
  this.operations = new ProjectOperationsApi(mw);
550
551
  this.agents = new WorkspaceCollection(mw, "agents");
551
552
  this.projects = new WorkspaceCollection(mw, "projects");
552
553
  this.chats = new WorkspaceCollection(mw, "chats");
553
554
  }
554
555
  mw;
556
+ git;
555
557
  operations;
556
558
  agents;
557
559
  projects;
@@ -580,6 +582,75 @@ var WorkspaceApi = class {
580
582
  return this.mw.http.request("POST", "/workspace/import", { body: records });
581
583
  }
582
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 (>= 2 for branch changes). 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, actionsVersion: 2 } }
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
+ };
583
654
 
584
655
  // src/surfaces.ts
585
656
  var SurfacesApi = class {
@@ -641,8 +712,27 @@ var SurfacesApi = class {
641
712
  }
642
713
  };
643
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
+
644
734
  // src/version.ts
645
- var SDK_VERSION = "0.1.15" ;
735
+ var SDK_VERSION = "0.1.19" ;
646
736
 
647
737
  // src/daemon-binary.ts
648
738
  function supported(platform, arch) {
@@ -903,6 +993,7 @@ var Mindwire = class _Mindwire {
903
993
  /** Workspace registry: saved agent profiles, projects and chat relationships. */
904
994
  workspace;
905
995
  surfaces;
996
+ service;
906
997
  http;
907
998
  /** The default agent type applied to agent-scoped calls, if set. */
908
999
  defaultAgent;
@@ -938,6 +1029,7 @@ var Mindwire = class _Mindwire {
938
1029
  this.defaultAgent = opts.agent;
939
1030
  this.workspace = new WorkspaceApi(this);
940
1031
  this.surfaces = new SurfacesApi(this);
1032
+ this.service = new ServiceApi(this);
941
1033
  this.auth = new AuthApi(this);
942
1034
  this.prompts = new PromptsApi(this);
943
1035
  this.mcp = new McpApi(this);
@@ -960,6 +1052,7 @@ var Mindwire = class _Mindwire {
960
1052
  clone.defaultAgent = agent;
961
1053
  clone.workspace = new WorkspaceApi(clone);
962
1054
  clone.surfaces = new SurfacesApi(clone);
1055
+ clone.service = new ServiceApi(clone);
963
1056
  clone.auth = new AuthApi(clone);
964
1057
  clone.prompts = new PromptsApi(clone);
965
1058
  clone.mcp = new McpApi(clone);
@@ -1008,6 +1101,12 @@ var Mindwire = class _Mindwire {
1008
1101
  agent(scoped) {
1009
1102
  return this.http.request("GET", "/agent", { query: this.agentParam(scoped) });
1010
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
+ }
1011
1110
  /**
1012
1111
  * `GET /models` — the models the selected agent can run for the configured account. An empty array
1013
1112
  * is valid (no credentials yet / offline). Throws a 400 {@link ApiError} for an agent whose model is
@@ -1025,7 +1124,7 @@ var Mindwire = class _Mindwire {
1025
1124
  setup(scoped) {
1026
1125
  return this.http.request("POST", "/setup", { query: this.agentParam(scoped) });
1027
1126
  }
1028
- /** `POST /update` — re-run the toolchain, forcing reinstall of installable steps. */
1127
+ /** `POST /update` — install the catalog's newest tested version compatible with this daemon. */
1029
1128
  update(scoped) {
1030
1129
  return this.http.request("POST", "/update", { query: this.agentParam(scoped) });
1031
1130
  }
@@ -1122,6 +1221,7 @@ var Mindwire = class _Mindwire {
1122
1221
  if (input.options !== void 0) body.options = input.options;
1123
1222
  if (input.mode !== void 0) body.mode = input.mode;
1124
1223
  if (input.resolve !== void 0) body.resolve = input.resolve;
1224
+ if (input.gitAuth !== void 0) body.gitAuth = input.gitAuth;
1125
1225
  const data = await this.http.request("POST", "/turns", {
1126
1226
  query: this.agentParam(input),
1127
1227
  body
@@ -1225,12 +1325,26 @@ var AuthApi = class {
1225
1325
  body: input
1226
1326
  });
1227
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
+ }
1228
1336
  /** `GET /auth/status` — is the agent authenticated, and via which method. */
1229
1337
  status(scoped) {
1230
1338
  return this.mw.http.request("GET", "/auth/status", {
1231
1339
  query: this.mw.agentParam(scoped)
1232
1340
  });
1233
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
+ }
1234
1348
  };
1235
1349
  var PromptsApi = class {
1236
1350
  constructor(mw) {
@@ -1472,6 +1586,23 @@ var NotifyApi = class {
1472
1586
  };
1473
1587
 
1474
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
+ }`;
1475
1606
  function makeEmit(cfg) {
1476
1607
  const target = cfg.target ?? "sandbox";
1477
1608
  return (e) => {
@@ -1508,19 +1639,24 @@ async function ensureDaemon(host, cfg) {
1508
1639
  message: health.version ? `daemon reachable (v${health.version})` : "daemon reachable (version unknown)",
1509
1640
  version: health.version
1510
1641
  });
1511
- const upToDate = health.version !== void 0 && health.version === desired;
1642
+ const upToDate = versionAtLeast(health.version, desired);
1512
1643
  if (!cfg.forceDeploy && (upToDate || !cfg.autoUpdate)) {
1513
1644
  emit2({
1514
1645
  phase: "skip",
1515
- message: upToDate ? `daemon already at v${desired}` : "keeping the running daemon",
1646
+ message: upToDate ? `daemon already at v${health.version}` : "keeping the running daemon",
1516
1647
  version: health.version
1517
1648
  });
1518
1649
  return token;
1519
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
+ }
1520
1656
  } else {
1521
1657
  emit2({ phase: "probe", message: "no daemon reachable; deploying" });
1522
1658
  }
1523
- await deploy(host, cfg, emit2, token, directory);
1659
+ await deploy(host, cfg, emit2, token, directory, health.reachable);
1524
1660
  if (!cfg.token) token = await readWorkspaceToken(host, directory) ?? token;
1525
1661
  return token;
1526
1662
  } catch (err) {
@@ -1533,7 +1669,7 @@ async function readWorkspaceToken(host, directory) {
1533
1669
  const candidate = saved.stdout?.trim();
1534
1670
  return candidate && candidate.length <= 4096 && /^[\x21-\x7e]+$/.test(candidate) ? candidate : void 0;
1535
1671
  }
1536
- async function deploy(host, cfg, emit2, token, directory) {
1672
+ async function deploy(host, cfg, emit2, token, directory, requireLease) {
1537
1673
  const newPath = directory + "/mindwired.new";
1538
1674
  const BIN = shellQuote(directory + "/mindwired"), BIN_NEW = shellQuote(newPath);
1539
1675
  const STATE = shellQuote(directory + "/agent-state.json"), LOG = shellQuote(directory + "/daemon.log");
@@ -1575,6 +1711,7 @@ async function deploy(host, cfg, emit2, token, directory) {
1575
1711
  ` latest=$(curl -fsSL https://api.github.com/repos/oblien/mindwire/releases/latest | sed -n 's/.*"tag_name"[[:space:]]*:[[:space:]]*"\\([^"]*\\)".*/\\1/p')`,
1576
1712
  ' case "$latest" in v[0-9]*.[0-9]*.[0-9]*) ;; *) echo "MINDWIRE_FAIL no matching or latest release"; exit 1 ;; esac',
1577
1713
  ' release="https://github.com/oblien/mindwire/releases/download/$latest"',
1714
+ ' mw_expected_version="${latest#v}"',
1578
1715
  ` asset="mindwired-$latest-${platform}-${arch}"`,
1579
1716
  ' download || { echo "MINDWIRE_FAIL latest release download failed"; exit 1; }',
1580
1717
  "fi",
@@ -1594,29 +1731,43 @@ async function deploy(host, cfg, emit2, token, directory) {
1594
1731
  'else echo "MINDWIRE_FAIL No supported update lock is available (flock on Linux, lockf on macOS)."; exit 1; fi',
1595
1732
  `mw_token=${shellQuote(token)}`,
1596
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)`,
1597
1746
  !cfg.forceDeploy ? [
1598
- `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
1747
  `mw_version=$(printf '%s' "$mw_health" | sed -n 's/.*"version"[[:space:]]*:[[:space:]]*"\\([^" ]*\\)".*/\\1/p')`,
1600
- `if [ -n "$mw_health" ] && ${cfg.autoUpdate ? `[ "$mw_version" = ${shellQuote(desired)} ]` : "true"}; then echo MINDWIRE_READY; exit 0; fi`
1748
+ `if [ -n "$mw_health" ] && ${cfg.autoUpdate ? 'version_at_least "$mw_version" "$mw_desired"' : "true"}; then echo MINDWIRE_READY; exit 0; fi`
1601
1749
  ].join("\n") : "",
1602
1750
  // macOS ships POSIX setsid in Perl, but does not include Linux's setsid executable.
1603
1751
  "if command -v setsid >/dev/null 2>&1; then mw_detach=(setsid);",
1604
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: $!";');`,
1605
1753
  'else echo "MINDWIRE_FAIL Cannot start the Mindwire service: setsid or Perl is required."; exit 1; fi',
1606
1754
  acquire,
1607
- // Stop a prior daemon by exact process NAME, never `pkill -f <path>`: this whole script (which
1608
- // contains `${BIN}` several times) is the argv of the `bash -lc` shell running it, so a full-cmdline
1609
- // match would SIGTERM our own deploying shell before the daemon ever launches. `-x mindwired` matches
1610
- // only the daemon's comm (`bash`/`pkill` never match), leaving this shell alive.
1611
- "if command -v pkill >/dev/null 2>&1; then",
1612
- " pkill -x mindwired 2>/dev/null || true",
1613
- "else",
1614
- " for mw_proc in /proc/[0-9]*/comm; do",
1615
- ' IFS= read -r mw_name 2>/dev/null < "$mw_proc" || continue',
1616
- ' [ "$mw_name" = mindwired ] || continue',
1617
- " mw_pid=${mw_proc#/proc/}; mw_pid=${mw_pid%/comm}",
1618
- ' kill "$mw_pid" 2>/dev/null || true',
1619
- " done",
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' : "",
1620
1771
  "fi",
1621
1772
  "sleep 0.3",
1622
1773
  `mv -f ${BIN_NEW} ${BIN}`,
@@ -1626,7 +1777,10 @@ async function deploy(host, cfg, emit2, token, directory) {
1626
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>&- &`,
1627
1778
  // Health-poll from inside the VM (loopback) and emit a marker — exit codes are unreliable here.
1628
1779
  'mw_pid=$!; mw_exit=""; mw_deadline=$((SECONDS + 30))',
1629
- `while (( SECONDS < mw_deadline )); do curl -fsS --connect-timeout 2 --max-time 3 -H "Authorization: Bearer $mw_token" http://127.0.0.1:${cfg.port}/healthz >/dev/null 2>&1 && { echo MINDWIRE_READY; exit 0; };`,
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',
1630
1784
  ' if [ -z "$mw_exit" ] && ! kill -0 "$mw_pid" 2>/dev/null; then',
1631
1785
  ' mw_exit=0; wait "$mw_pid" || mw_exit=$?; [ "$mw_exit" = 0 ] || break',
1632
1786
  " fi",
@@ -1634,13 +1788,18 @@ async function deploy(host, cfg, emit2, token, directory) {
1634
1788
  "done",
1635
1789
  `mw_tail=$(tail -n 40 ${LOG} 2>/dev/null || true)`,
1636
1790
  'if [ -n "$mw_exit" ] && [ "$mw_exit" != 0 ]; then mw_reason="Mindwire exited during startup (status $mw_exit).";',
1637
- 'else mw_reason="The Mindwire service did not become ready."; fi',
1791
+ 'else mw_reason="The Mindwire service did not become ready at v$mw_expected_version (reported: $mw_version)."; fi',
1638
1792
  '[ -z "$mw_tail" ] || mw_reason="$mw_reason $mw_tail"',
1639
1793
  'printf "MINDWIRE_FAIL %s\\n" "$mw_reason"'
1640
1794
  ].join("\n");
1641
1795
  emit2({ phase: "launch", message: "launching daemon" });
1642
1796
  const res = await host.exec(["bash", "-lc", script], { timeoutSeconds: 420 });
1643
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
+ }
1644
1803
  if (!out.includes("MINDWIRE_READY")) {
1645
1804
  throw new MindwireError(
1646
1805
  "mindwire: the in-sandbox daemon did not become healthy after deploy.\n" + (out || res.stderr || res.error || "(no output)").trim()
@@ -1655,7 +1814,11 @@ async function probeHealth(host, port, token) {
1655
1814
  if (!body) return { reachable: false };
1656
1815
  try {
1657
1816
  const j = JSON.parse(body);
1658
- return { reachable: true, version: typeof j.version === "string" ? j.version : void 0 };
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
+ };
1659
1822
  } catch {
1660
1823
  return { reachable: true };
1661
1824
  }
@@ -1756,7 +1919,7 @@ var ContainerHost = class {
1756
1919
  base;
1757
1920
  containerId;
1758
1921
  exec(argv, opts) {
1759
- 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);
1760
1923
  }
1761
1924
  async putFile(path, data, opts) {
1762
1925
  const slash = path.lastIndexOf("/");
@@ -1856,7 +2019,7 @@ async function runContainer(host, cfg, emit2) {
1856
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."
1857
2020
  );
1858
2021
  }
1859
- const runArgs = ["docker", "run", "-d"];
2022
+ const runArgs = ["docker", "run", "-d", "--env", "MINDWIRE_ISOLATION=container"];
1860
2023
  if (cfg.name) runArgs.push("--name", cfg.name);
1861
2024
  runArgs.push("-p", `127.0.0.1:0:${port}`);
1862
2025
  if (cfg.createArgs?.length) runArgs.push(...cfg.createArgs);
@@ -2183,7 +2346,7 @@ var DockerHost = class {
2183
2346
  container;
2184
2347
  async exec(argv) {
2185
2348
  const { Writable } = await import('stream');
2186
- 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 });
2187
2350
  const stream = await exec.start({ hijack: true, stdin: false });
2188
2351
  const out = [];
2189
2352
  const err = [];
@@ -2237,7 +2400,7 @@ async function provisionDocker(docker2, config = {}, onLog) {
2237
2400
  // The runtime image starts mindwired itself. Do not replace its command: doing so turns the
2238
2401
  // image ENTRYPOINT into `mindwired sleep infinity` and prevents the runtime from starting.
2239
2402
  Image: image,
2240
- Env: [`ADDR=:${port}`, `AGENT_TYPE=${agent}`, `AGENT_CWD=${agentCwd}`],
2403
+ Env: [`ADDR=:${port}`, `AGENT_TYPE=${agent}`, `AGENT_CWD=${agentCwd}`, "MINDWIRE_ISOLATION=container"],
2241
2404
  Tty: false,
2242
2405
  ExposedPorts: { [portKey]: {} },
2243
2406
  HostConfig: { PortBindings: { [portKey]: [{ HostPort: "0" }] } },
@@ -2552,6 +2715,6 @@ function clearCatalogCache() {
2552
2715
  inflight = null;
2553
2716
  }
2554
2717
 
2555
- 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 };
2556
2719
  //# sourceMappingURL=index.js.map
2557
2720
  //# sourceMappingURL=index.js.map