mindwire 0.1.47 → 0.1.50

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.cjs CHANGED
@@ -1248,6 +1248,113 @@ function computerPairingURI(invitation) {
1248
1248
  return `mindwire://${action}?v=1#${encoded}`;
1249
1249
  }
1250
1250
 
1251
+ // src/voice.ts
1252
+ var VoiceApi = class {
1253
+ constructor(client) {
1254
+ this.client = client;
1255
+ }
1256
+ client;
1257
+ status(scoped) {
1258
+ return this.client.http.request("GET", "/voice", {
1259
+ query: { agent: scoped?.agent ?? this.client.defaultAgent }
1260
+ });
1261
+ }
1262
+ /** Start in an idle chat. Keep requestId/clientId when retrying a lost receipt. */
1263
+ async start(input) {
1264
+ const clientId = input.clientId ?? crypto.randomUUID();
1265
+ const data = await this.client.http.request("POST", "/turns", {
1266
+ query: { agent: input.agent ?? this.client.defaultAgent },
1267
+ body: {
1268
+ chatId: input.chatId,
1269
+ message: "",
1270
+ cwd: input.cwd,
1271
+ gitAuth: input.gitAuth,
1272
+ requestId: input.requestId ?? crypto.randomUUID(),
1273
+ options: { voice: { clientId, voice: input.voice } }
1274
+ }
1275
+ });
1276
+ return new VoiceSession(this.client.http, new Run(this.client.http, data), clientId);
1277
+ }
1278
+ };
1279
+ var VoiceSession = class {
1280
+ constructor(http, run, clientId) {
1281
+ this.http = http;
1282
+ this.run = run;
1283
+ this.clientId = clientId;
1284
+ }
1285
+ http;
1286
+ run;
1287
+ clientId;
1288
+ sequence = 0;
1289
+ sending = false;
1290
+ ended = false;
1291
+ streaming = false;
1292
+ get path() {
1293
+ return `/runs/${encodeURIComponent(this.run.id)}/voice`;
1294
+ }
1295
+ /** Await each ordered batch. No automatic audio replay after a network failure. */
1296
+ async appendAudio(audio, signal) {
1297
+ if (this.ended || this.sending) throw new Error("Voice ended or the previous audio batch is still being delivered.");
1298
+ this.sending = true;
1299
+ try {
1300
+ await this.http.request("POST", this.path, { signal, body: {
1301
+ clientId: this.clientId,
1302
+ action: "audio",
1303
+ sequence: ++this.sequence,
1304
+ audio
1305
+ } });
1306
+ } catch (error) {
1307
+ await this.stop().catch(() => {
1308
+ });
1309
+ throw error;
1310
+ } finally {
1311
+ this.sending = false;
1312
+ }
1313
+ }
1314
+ /** Subscribe before sending audio; wait for ready. Breaking the loop ends voice. */
1315
+ async *stream(signal) {
1316
+ if (this.streaming || this.ended) throw new Error("This voice stream is already open or ended.");
1317
+ this.streaming = true;
1318
+ const controller = new AbortController();
1319
+ const abort = () => controller.abort(signal?.reason);
1320
+ signal?.addEventListener("abort", abort, { once: true });
1321
+ if (signal?.aborted) abort();
1322
+ let heartbeatPending = false;
1323
+ const heartbeat = setInterval(() => {
1324
+ if (heartbeatPending || controller.signal.aborted) return;
1325
+ heartbeatPending = true;
1326
+ void this.http.request("POST", this.path, {
1327
+ signal: controller.signal,
1328
+ body: { clientId: this.clientId, action: "heartbeat" }
1329
+ }).catch((error) => controller.abort(error)).finally(() => {
1330
+ heartbeatPending = false;
1331
+ });
1332
+ }, 5e3);
1333
+ try {
1334
+ const response = await this.http.open("GET", `${this.path}/stream`, {
1335
+ query: { clientId: this.clientId },
1336
+ signal: controller.signal
1337
+ });
1338
+ for await (const event of readSSE(response.body, controller.signal)) {
1339
+ if (event.type !== "heartbeat") yield event;
1340
+ if (event.type === "closed" || event.type === "error") break;
1341
+ }
1342
+ } finally {
1343
+ clearInterval(heartbeat);
1344
+ signal?.removeEventListener("abort", abort);
1345
+ controller.abort();
1346
+ this.streaming = false;
1347
+ await this.stop().catch(() => {
1348
+ });
1349
+ }
1350
+ }
1351
+ async stop() {
1352
+ if (this.ended) return;
1353
+ this.ended = true;
1354
+ await this.http.request("POST", this.path, { body: { clientId: this.clientId, action: "stop" } });
1355
+ }
1356
+ };
1357
+
1251
1358
  // src/client.ts
1252
1359
  init_binary_cache();
1253
1360
 
@@ -1258,7 +1365,7 @@ init_errors();
1258
1365
  init_errors();
1259
1366
 
1260
1367
  // src/version.ts
1261
- var SDK_VERSION = "0.1.47" ;
1368
+ var SDK_VERSION = "0.1.50" ;
1262
1369
  function versionAtLeast(actual, desired) {
1263
1370
  if (actual === desired) return true;
1264
1371
  if (!actual || !/^\d+\.\d+\.\d+$/.test(actual) || !/^\d+\.\d+\.\d+$/.test(desired)) return false;
@@ -1330,14 +1437,25 @@ async function ensureDaemonBinary(opts = {}) {
1330
1437
  // src/embedded.ts
1331
1438
  var shared = /* @__PURE__ */ new Map();
1332
1439
  function configKey(opts) {
1333
- return JSON.stringify([opts.cwd ?? "", opts.statePath ?? "", opts.bin ?? ""]);
1440
+ const entries = (env3) => env3 && Object.entries(env3).sort(([a], [b]) => a.localeCompare(b));
1441
+ return JSON.stringify([
1442
+ opts.cwd ?? "",
1443
+ opts.statePath ?? "",
1444
+ opts.bin ?? "",
1445
+ opts.exactVersion,
1446
+ entries(opts.environment),
1447
+ opts.authSource === "host" ? ["host", entries(serverRuntime().process?.env)] : opts.authSource && [opts.authSource.home, opts.authSource.reuse ?? "automatic", entries(opts.authSource.environment)]
1448
+ ]);
1334
1449
  }
1335
1450
  function startEmbedded(opts = {}) {
1451
+ if (opts.shared === false) return spawnDaemon(opts);
1336
1452
  const key = configKey(opts);
1337
1453
  let daemon = shared.get(key);
1338
1454
  if (!daemon) {
1339
- daemon = spawnDaemon(opts).catch((err) => {
1340
- shared.delete(key);
1455
+ daemon = spawnDaemon(opts, () => {
1456
+ if (shared.get(key) === daemon) shared.delete(key);
1457
+ }).catch((err) => {
1458
+ if (shared.get(key) === daemon) shared.delete(key);
1341
1459
  throw err;
1342
1460
  });
1343
1461
  shared.set(key, daemon);
@@ -1351,7 +1469,8 @@ function isServerRuntime() {
1351
1469
  const g = serverRuntime();
1352
1470
  return typeof g.process !== "undefined" && !!g.process.versions?.node || typeof g.Bun !== "undefined" || typeof g.Deno !== "undefined";
1353
1471
  }
1354
- async function spawnDaemon(opts) {
1472
+ async function spawnDaemon(opts, onStop = () => {
1473
+ }) {
1355
1474
  if (!isServerRuntime()) {
1356
1475
  throw new exports.MindwireError(
1357
1476
  "MindWire embedded mode needs a server runtime (Node/Bun/Deno). In the browser or an edge runtime, pass { baseUrl } to connect to a running daemon."
@@ -1361,40 +1480,78 @@ async function spawnDaemon(opts) {
1361
1480
  const net = await import('net');
1362
1481
  const proc = serverRuntime().process;
1363
1482
  const { randomBytes } = await import('crypto');
1364
- const resolved = await resolveBinary(opts.bin);
1483
+ const resolved = await resolveBinary(opts.bin, opts.exactVersion);
1365
1484
  const bin = resolved.bin;
1366
1485
  const port = await freePort(net);
1367
1486
  const baseUrl = `http://127.0.0.1:${port}`;
1368
1487
  const token = randomBytes(32).toString("hex");
1488
+ const authSource = opts.authSource === "host" ? { home: (await import('os')).homedir(), environment: proc.env } : opts.authSource;
1369
1489
  const child = spawn(bin, [], {
1370
1490
  env: {
1371
- ...proc.env,
1491
+ ...opts.environment ?? proc.env,
1372
1492
  ADDR: `127.0.0.1:${port}`,
1373
1493
  AGENT_CWD: opts.cwd ?? proc.cwd(),
1374
1494
  STATE_PATH: opts.statePath ?? ".mindwire-state.json",
1375
- DAEMON_TOKEN: token
1495
+ DAEMON_TOKEN: token,
1496
+ // The daemon consumes and unsets this before starting any native process.
1497
+ ...authSource ? { MINDWIRE_AUTH_SOURCE: JSON.stringify(authSource) } : {}
1376
1498
  },
1499
+ cwd: opts.cwd ?? proc.cwd(),
1377
1500
  stdio: "ignore"
1378
1501
  });
1379
1502
  let spawnError = null;
1380
- child.on("error", (e) => {
1381
- spawnError = e;
1382
- });
1383
- const stop = () => {
1503
+ let exited = false;
1504
+ let stopping;
1505
+ const isAlive = () => !exited && !stopping && !spawnError;
1506
+ const killOnExit = () => {
1384
1507
  try {
1385
1508
  child.kill();
1386
1509
  } catch {
1387
1510
  }
1388
1511
  };
1389
- proc.once?.("exit", stop);
1390
- proc.once?.("SIGINT", () => {
1391
- stop();
1392
- proc.exit?.(130);
1512
+ const cleanup = () => {
1513
+ exited = true;
1514
+ proc.off?.("exit", killOnExit);
1515
+ onStop();
1516
+ };
1517
+ child.once("exit", cleanup);
1518
+ child.on("error", (e) => {
1519
+ spawnError = e;
1520
+ cleanup();
1393
1521
  });
1394
- await waitHealthy(baseUrl, token, () => spawnError, resolved);
1395
- return { baseUrl, token, stop };
1522
+ const stop = () => {
1523
+ return stopping ??= new Promise((resolve) => {
1524
+ if (exited) {
1525
+ resolve();
1526
+ return;
1527
+ }
1528
+ const timer = setTimeout(() => {
1529
+ try {
1530
+ child.kill("SIGKILL");
1531
+ } catch {
1532
+ }
1533
+ }, 2e3);
1534
+ child.once("exit", () => {
1535
+ clearTimeout(timer);
1536
+ resolve();
1537
+ });
1538
+ child.once("error", () => {
1539
+ clearTimeout(timer);
1540
+ resolve();
1541
+ });
1542
+ killOnExit();
1543
+ });
1544
+ };
1545
+ proc.once?.("exit", killOnExit);
1546
+ try {
1547
+ await waitHealthy(baseUrl, token, () => spawnError ?? (exited ? new Error("daemon exited during startup") : null), resolved, 15e3, !!authSource);
1548
+ return { baseUrl, token, isAlive, stop };
1549
+ } catch (error) {
1550
+ await stop();
1551
+ throw error;
1552
+ }
1396
1553
  }
1397
- async function resolveBinary(explicit) {
1554
+ async function resolveBinary(explicit, exactVersion) {
1398
1555
  const fs = await import('fs');
1399
1556
  const path = await import('path');
1400
1557
  const proc = serverRuntime().process;
@@ -1415,7 +1572,7 @@ async function resolveBinary(explicit) {
1415
1572
  } catch {
1416
1573
  }
1417
1574
  }
1418
- const downloaded = await ensureDaemonBinary({ platform: proc.platform, arch: proc.arch });
1575
+ const downloaded = await ensureDaemonBinary({ platform: proc.platform, arch: proc.arch, exactVersion });
1419
1576
  return { bin: downloaded, found: true, checked: [...candidates, downloaded] };
1420
1577
  }
1421
1578
  function daemonStartError(resolved, cause) {
@@ -1444,7 +1601,7 @@ function freePort(net) {
1444
1601
  });
1445
1602
  });
1446
1603
  }
1447
- async function waitHealthy(baseUrl, token, getError, resolved, timeoutMs = 15e3) {
1604
+ async function waitHealthy(baseUrl, token, getError, resolved, timeoutMs = 15e3, authSource = false) {
1448
1605
  const deadline = Date.now() + timeoutMs;
1449
1606
  while (Date.now() < deadline) {
1450
1607
  const err = getError();
@@ -1452,9 +1609,14 @@ async function waitHealthy(baseUrl, token, getError, resolved, timeoutMs = 15e3)
1452
1609
  throw new exports.MindwireError(daemonStartError(resolved, err), { cause: err });
1453
1610
  }
1454
1611
  try {
1455
- const res = await fetch(`${baseUrl}/healthz`, { headers: { Authorization: `Bearer ${token}` } });
1456
- if (res.ok) return;
1457
- } catch {
1612
+ const res = await fetch(`${baseUrl}/healthz`, { headers: { Authorization: `Bearer ${token}` }, signal: AbortSignal.timeout(1e3) });
1613
+ if (res.ok) {
1614
+ if (authSource && (await res.json()).nativeAuthSource !== true)
1615
+ throw new exports.MindwireError("This daemon does not support native auth sources. Update Mindwire or build the matching daemon before using authSource.");
1616
+ return;
1617
+ }
1618
+ } catch (error) {
1619
+ if (error instanceof exports.MindwireError) throw error;
1458
1620
  }
1459
1621
  await new Promise((r) => setTimeout(r, 150));
1460
1622
  }
@@ -1470,17 +1632,27 @@ function emit(spec, e) {
1470
1632
  }
1471
1633
  }
1472
1634
  function local(opts = {}) {
1635
+ let daemon;
1636
+ let pending;
1473
1637
  return {
1474
1638
  name: "local",
1639
+ profileVersion: 1,
1475
1640
  async connect(spec) {
1476
- const d = await startEmbedded(opts);
1641
+ if (daemon && !daemon.isAlive()) {
1642
+ daemon = void 0;
1643
+ pending = void 0;
1644
+ }
1645
+ const d = await (pending ??= startEmbedded(opts).then((value) => daemon = value).catch((error) => {
1646
+ pending = void 0;
1647
+ throw error;
1648
+ }));
1477
1649
  emit(spec, { target: "local", phase: "ready", message: `embedded daemon on ${d.baseUrl}` });
1478
1650
  return {
1479
1651
  id: d.baseUrl,
1480
1652
  baseUrl: d.baseUrl,
1481
1653
  ...d.token !== void 0 ? { token: d.token } : {},
1482
- // No-op: the embedded daemon is shared (keyed memoization) and reaped on process exit.
1483
- stop: async () => {
1654
+ isAlive: d.isAlive,
1655
+ stop: opts.shared === false ? d.stop : async () => {
1484
1656
  }
1485
1657
  };
1486
1658
  }
@@ -1513,6 +1685,7 @@ var Mindwire = class _Mindwire {
1513
1685
  service;
1514
1686
  execution;
1515
1687
  computer;
1688
+ voice;
1516
1689
  http;
1517
1690
  /** The default agent type applied to agent-scoped calls, if set. */
1518
1691
  defaultAgent;
@@ -1551,6 +1724,7 @@ var Mindwire = class _Mindwire {
1551
1724
  this.service = new ServiceApi(this);
1552
1725
  this.execution = new ExecutionApi(this);
1553
1726
  this.computer = new ComputerApi(this);
1727
+ this.voice = new VoiceApi(this);
1554
1728
  this.auth = new AuthApi(this);
1555
1729
  this.prompts = new PromptsApi(this);
1556
1730
  this.mcp = new McpApi(this);
@@ -1576,6 +1750,7 @@ var Mindwire = class _Mindwire {
1576
1750
  clone.service = new ServiceApi(clone);
1577
1751
  clone.execution = new ExecutionApi(clone);
1578
1752
  clone.computer = new ComputerApi(clone);
1753
+ clone.voice = new VoiceApi(clone);
1579
1754
  clone.auth = new AuthApi(clone);
1580
1755
  clone.prompts = new PromptsApi(clone);
1581
1756
  clone.mcp = new McpApi(clone);
@@ -1592,8 +1767,8 @@ var Mindwire = class _Mindwire {
1592
1767
  * Release target-owned resources by calling the {@link TargetHandle}'s `stop()` — once, even across
1593
1768
  * {@link withAgent} clones that share the transport. For an `ssh`/`docker`/`oblien` target this reaps
1594
1769
  * the box (tear down the tunnel, stop or delete the container/workspace, per `stopOnExit`); a no-op
1595
- * for `local`/`remote` (embedded self-cleans on process exit; remote is not ours to stop). Safe to
1596
- * call before the target has connected (nothing to reap yet).
1770
+ * for shared local and remote targets. With `local({ shared: false })`, closes the owned daemon.
1771
+ * Safe to call before the target has connected (nothing to reap yet).
1597
1772
  */
1598
1773
  async close() {
1599
1774
  const handle = handleByTransport.get(this.http);
@@ -1638,6 +1813,12 @@ var Mindwire = class _Mindwire {
1638
1813
  models(scoped) {
1639
1814
  return this.http.request("GET", "/models", { query: this.agentParam(scoped) });
1640
1815
  }
1816
+ /** Discover native commands for a project without starting a model turn. */
1817
+ commands(opts = {}) {
1818
+ return this.http.request("GET", "/commands", {
1819
+ query: { ...this.agentParam(opts), dir: opts.directory, refresh: opts.refresh }
1820
+ });
1821
+ }
1641
1822
  /** `GET /doctor` — daemon-level health plus the selected agent's own checks. */
1642
1823
  doctor(scoped) {
1643
1824
  return this.http.request("GET", "/doctor", { query: this.agentParam(scoped) });
@@ -1775,7 +1956,8 @@ var Mindwire = class _Mindwire {
1775
1956
  limit,
1776
1957
  before: opts.before,
1777
1958
  paged: "true",
1778
- maxBytes: opts.maxBytes ?? 1048576
1959
+ maxBytes: opts.maxBytes ?? 1048576,
1960
+ ifRevision: opts.before ? void 0 : opts.ifRevision
1779
1961
  }
1780
1962
  }
1781
1963
  );
@@ -1854,7 +2036,7 @@ var Mindwire = class _Mindwire {
1854
2036
  `/chats/${encodeURIComponent(chatId)}/compact`,
1855
2037
  {
1856
2038
  query: this.agentParam(opts),
1857
- body: opts.instructions ? { instructions: opts.instructions } : {}
2039
+ body: { instructions: opts.instructions, requestId: opts.requestId }
1858
2040
  }
1859
2041
  );
1860
2042
  return new Run(this.http, data);
@@ -3328,6 +3510,8 @@ exports.SDK_VERSION = SDK_VERSION;
3328
3510
  exports.ServiceApi = ServiceApi;
3329
3511
  exports.SurfacesApi = SurfacesApi;
3330
3512
  exports.TerminalsApi = TerminalsApi;
3513
+ exports.VoiceApi = VoiceApi;
3514
+ exports.VoiceSession = VoiceSession;
3331
3515
  exports.WorkspaceApi = WorkspaceApi;
3332
3516
  exports.WorkspaceCollection = WorkspaceCollection;
3333
3517
  exports.catalogModels = catalogModels;