mindwire 0.1.47 → 0.1.48

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
@@ -8,13 +8,14 @@
8
8
  export { Mindwire, AuthApi, PromptsApi, McpApi, ProvidersApi, NotifyApi } from "./client.js";
9
9
  export type { MindwireOptions, AgentScoped } from "./client.js";
10
10
  export { Run } from "./run.js";
11
+ export { VoiceApi, VoiceSession } from "./voice.js";
11
12
  export type { StreamOptions, WaitResult } from "./run.js";
12
13
  export { Http } from "./http.js";
13
14
  export type { HttpOptions, FetchLike, RequestInitLike, BaseResolver, TokenGetter } from "./http.js";
14
15
  export { startEmbedded } from "./embedded.js";
15
- export type { EmbeddedOptions, EmbeddedDaemon } from "./embedded.js";
16
+ export type { EmbeddedOptions, EmbeddedDaemon, NativeAuthSource } from "./embedded.js";
16
17
  export { local, remote } from "./target/index.js";
17
- export type { Target, TargetHandle, ConnectSpec, RemoteOptions } from "./target/index.js";
18
+ export type { Target, LocalTarget, TargetHandle, ConnectSpec, RemoteOptions } from "./target/index.js";
18
19
  export { ssh, provisionSsh, provisionSshContainer } from "./target/ssh.js";
19
20
  export type { SshOptions, SshProvisionConfig, SshContainerProvisionConfig } from "./target/ssh.js";
20
21
  export { docker, provisionDocker } from "./target/docker.js";
package/dist/index.js CHANGED
@@ -1117,8 +1117,115 @@ function computerPairingURI(invitation) {
1117
1117
  return `mindwire://${action}?v=1#${encoded}`;
1118
1118
  }
1119
1119
 
1120
+ // src/voice.ts
1121
+ var VoiceApi = class {
1122
+ constructor(client) {
1123
+ this.client = client;
1124
+ }
1125
+ client;
1126
+ status(scoped) {
1127
+ return this.client.http.request("GET", "/voice", {
1128
+ query: { agent: scoped?.agent ?? this.client.defaultAgent }
1129
+ });
1130
+ }
1131
+ /** Start in an idle chat. Keep requestId/clientId when retrying a lost receipt. */
1132
+ async start(input) {
1133
+ const clientId = input.clientId ?? crypto.randomUUID();
1134
+ const data = await this.client.http.request("POST", "/turns", {
1135
+ query: { agent: input.agent ?? this.client.defaultAgent },
1136
+ body: {
1137
+ chatId: input.chatId,
1138
+ message: "",
1139
+ cwd: input.cwd,
1140
+ gitAuth: input.gitAuth,
1141
+ requestId: input.requestId ?? crypto.randomUUID(),
1142
+ options: { voice: { clientId, voice: input.voice } }
1143
+ }
1144
+ });
1145
+ return new VoiceSession(this.client.http, new Run(this.client.http, data), clientId);
1146
+ }
1147
+ };
1148
+ var VoiceSession = class {
1149
+ constructor(http, run, clientId) {
1150
+ this.http = http;
1151
+ this.run = run;
1152
+ this.clientId = clientId;
1153
+ }
1154
+ http;
1155
+ run;
1156
+ clientId;
1157
+ sequence = 0;
1158
+ sending = false;
1159
+ ended = false;
1160
+ streaming = false;
1161
+ get path() {
1162
+ return `/runs/${encodeURIComponent(this.run.id)}/voice`;
1163
+ }
1164
+ /** Await each ordered batch. No automatic audio replay after a network failure. */
1165
+ async appendAudio(audio, signal) {
1166
+ if (this.ended || this.sending) throw new Error("Voice ended or the previous audio batch is still being delivered.");
1167
+ this.sending = true;
1168
+ try {
1169
+ await this.http.request("POST", this.path, { signal, body: {
1170
+ clientId: this.clientId,
1171
+ action: "audio",
1172
+ sequence: ++this.sequence,
1173
+ audio
1174
+ } });
1175
+ } catch (error) {
1176
+ await this.stop().catch(() => {
1177
+ });
1178
+ throw error;
1179
+ } finally {
1180
+ this.sending = false;
1181
+ }
1182
+ }
1183
+ /** Subscribe before sending audio; wait for ready. Breaking the loop ends voice. */
1184
+ async *stream(signal) {
1185
+ if (this.streaming || this.ended) throw new Error("This voice stream is already open or ended.");
1186
+ this.streaming = true;
1187
+ const controller = new AbortController();
1188
+ const abort = () => controller.abort(signal?.reason);
1189
+ signal?.addEventListener("abort", abort, { once: true });
1190
+ if (signal?.aborted) abort();
1191
+ let heartbeatPending = false;
1192
+ const heartbeat = setInterval(() => {
1193
+ if (heartbeatPending || controller.signal.aborted) return;
1194
+ heartbeatPending = true;
1195
+ void this.http.request("POST", this.path, {
1196
+ signal: controller.signal,
1197
+ body: { clientId: this.clientId, action: "heartbeat" }
1198
+ }).catch((error) => controller.abort(error)).finally(() => {
1199
+ heartbeatPending = false;
1200
+ });
1201
+ }, 5e3);
1202
+ try {
1203
+ const response = await this.http.open("GET", `${this.path}/stream`, {
1204
+ query: { clientId: this.clientId },
1205
+ signal: controller.signal
1206
+ });
1207
+ for await (const event of readSSE(response.body, controller.signal)) {
1208
+ if (event.type !== "heartbeat") yield event;
1209
+ if (event.type === "closed" || event.type === "error") break;
1210
+ }
1211
+ } finally {
1212
+ clearInterval(heartbeat);
1213
+ signal?.removeEventListener("abort", abort);
1214
+ controller.abort();
1215
+ this.streaming = false;
1216
+ await this.stop().catch(() => {
1217
+ });
1218
+ }
1219
+ }
1220
+ async stop() {
1221
+ if (this.ended) return;
1222
+ this.ended = true;
1223
+ await this.http.request("POST", this.path, { body: { clientId: this.clientId, action: "stop" } });
1224
+ }
1225
+ };
1226
+
1120
1227
  // src/version.ts
1121
- var SDK_VERSION = "0.1.47" ;
1228
+ var SDK_VERSION = "0.1.48" ;
1122
1229
  function versionAtLeast(actual, desired) {
1123
1230
  if (actual === desired) return true;
1124
1231
  if (!actual || !/^\d+\.\d+\.\d+$/.test(actual) || !/^\d+\.\d+\.\d+$/.test(desired)) return false;
@@ -1190,14 +1297,25 @@ async function ensureDaemonBinary(opts = {}) {
1190
1297
  // src/embedded.ts
1191
1298
  var shared = /* @__PURE__ */ new Map();
1192
1299
  function configKey(opts) {
1193
- return JSON.stringify([opts.cwd ?? "", opts.statePath ?? "", opts.bin ?? ""]);
1300
+ const entries = (env3) => env3 && Object.entries(env3).sort(([a], [b]) => a.localeCompare(b));
1301
+ return JSON.stringify([
1302
+ opts.cwd ?? "",
1303
+ opts.statePath ?? "",
1304
+ opts.bin ?? "",
1305
+ opts.exactVersion,
1306
+ entries(opts.environment),
1307
+ opts.authSource === "host" ? ["host", entries(serverRuntime().process?.env)] : opts.authSource && [opts.authSource.home, opts.authSource.reuse ?? "automatic", entries(opts.authSource.environment)]
1308
+ ]);
1194
1309
  }
1195
1310
  function startEmbedded(opts = {}) {
1311
+ if (opts.shared === false) return spawnDaemon(opts);
1196
1312
  const key = configKey(opts);
1197
1313
  let daemon = shared.get(key);
1198
1314
  if (!daemon) {
1199
- daemon = spawnDaemon(opts).catch((err) => {
1200
- shared.delete(key);
1315
+ daemon = spawnDaemon(opts, () => {
1316
+ if (shared.get(key) === daemon) shared.delete(key);
1317
+ }).catch((err) => {
1318
+ if (shared.get(key) === daemon) shared.delete(key);
1201
1319
  throw err;
1202
1320
  });
1203
1321
  shared.set(key, daemon);
@@ -1211,7 +1329,8 @@ function isServerRuntime() {
1211
1329
  const g = serverRuntime();
1212
1330
  return typeof g.process !== "undefined" && !!g.process.versions?.node || typeof g.Bun !== "undefined" || typeof g.Deno !== "undefined";
1213
1331
  }
1214
- async function spawnDaemon(opts) {
1332
+ async function spawnDaemon(opts, onStop = () => {
1333
+ }) {
1215
1334
  if (!isServerRuntime()) {
1216
1335
  throw new MindwireError(
1217
1336
  "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."
@@ -1221,40 +1340,78 @@ async function spawnDaemon(opts) {
1221
1340
  const net = await import('net');
1222
1341
  const proc = serverRuntime().process;
1223
1342
  const { randomBytes } = await import('crypto');
1224
- const resolved = await resolveBinary(opts.bin);
1343
+ const resolved = await resolveBinary(opts.bin, opts.exactVersion);
1225
1344
  const bin = resolved.bin;
1226
1345
  const port = await freePort(net);
1227
1346
  const baseUrl = `http://127.0.0.1:${port}`;
1228
1347
  const token = randomBytes(32).toString("hex");
1348
+ const authSource = opts.authSource === "host" ? { home: (await import('os')).homedir(), environment: proc.env } : opts.authSource;
1229
1349
  const child = spawn(bin, [], {
1230
1350
  env: {
1231
- ...proc.env,
1351
+ ...opts.environment ?? proc.env,
1232
1352
  ADDR: `127.0.0.1:${port}`,
1233
1353
  AGENT_CWD: opts.cwd ?? proc.cwd(),
1234
1354
  STATE_PATH: opts.statePath ?? ".mindwire-state.json",
1235
- DAEMON_TOKEN: token
1355
+ DAEMON_TOKEN: token,
1356
+ // The daemon consumes and unsets this before starting any native process.
1357
+ ...authSource ? { MINDWIRE_AUTH_SOURCE: JSON.stringify(authSource) } : {}
1236
1358
  },
1359
+ cwd: opts.cwd ?? proc.cwd(),
1237
1360
  stdio: "ignore"
1238
1361
  });
1239
1362
  let spawnError = null;
1240
- child.on("error", (e) => {
1241
- spawnError = e;
1242
- });
1243
- const stop = () => {
1363
+ let exited = false;
1364
+ let stopping;
1365
+ const isAlive = () => !exited && !stopping && !spawnError;
1366
+ const killOnExit = () => {
1244
1367
  try {
1245
1368
  child.kill();
1246
1369
  } catch {
1247
1370
  }
1248
1371
  };
1249
- proc.once?.("exit", stop);
1250
- proc.once?.("SIGINT", () => {
1251
- stop();
1252
- proc.exit?.(130);
1372
+ const cleanup = () => {
1373
+ exited = true;
1374
+ proc.off?.("exit", killOnExit);
1375
+ onStop();
1376
+ };
1377
+ child.once("exit", cleanup);
1378
+ child.on("error", (e) => {
1379
+ spawnError = e;
1380
+ cleanup();
1253
1381
  });
1254
- await waitHealthy(baseUrl, token, () => spawnError, resolved);
1255
- return { baseUrl, token, stop };
1382
+ const stop = () => {
1383
+ return stopping ??= new Promise((resolve) => {
1384
+ if (exited) {
1385
+ resolve();
1386
+ return;
1387
+ }
1388
+ const timer = setTimeout(() => {
1389
+ try {
1390
+ child.kill("SIGKILL");
1391
+ } catch {
1392
+ }
1393
+ }, 2e3);
1394
+ child.once("exit", () => {
1395
+ clearTimeout(timer);
1396
+ resolve();
1397
+ });
1398
+ child.once("error", () => {
1399
+ clearTimeout(timer);
1400
+ resolve();
1401
+ });
1402
+ killOnExit();
1403
+ });
1404
+ };
1405
+ proc.once?.("exit", killOnExit);
1406
+ try {
1407
+ await waitHealthy(baseUrl, token, () => spawnError ?? (exited ? new Error("daemon exited during startup") : null), resolved, 15e3, !!authSource);
1408
+ return { baseUrl, token, isAlive, stop };
1409
+ } catch (error) {
1410
+ await stop();
1411
+ throw error;
1412
+ }
1256
1413
  }
1257
- async function resolveBinary(explicit) {
1414
+ async function resolveBinary(explicit, exactVersion) {
1258
1415
  const fs = await import('fs');
1259
1416
  const path = await import('path');
1260
1417
  const proc = serverRuntime().process;
@@ -1275,7 +1432,7 @@ async function resolveBinary(explicit) {
1275
1432
  } catch {
1276
1433
  }
1277
1434
  }
1278
- const downloaded = await ensureDaemonBinary({ platform: proc.platform, arch: proc.arch });
1435
+ const downloaded = await ensureDaemonBinary({ platform: proc.platform, arch: proc.arch, exactVersion });
1279
1436
  return { bin: downloaded, found: true, checked: [...candidates, downloaded] };
1280
1437
  }
1281
1438
  function daemonStartError(resolved, cause) {
@@ -1304,7 +1461,7 @@ function freePort(net) {
1304
1461
  });
1305
1462
  });
1306
1463
  }
1307
- async function waitHealthy(baseUrl, token, getError, resolved, timeoutMs = 15e3) {
1464
+ async function waitHealthy(baseUrl, token, getError, resolved, timeoutMs = 15e3, authSource = false) {
1308
1465
  const deadline = Date.now() + timeoutMs;
1309
1466
  while (Date.now() < deadline) {
1310
1467
  const err = getError();
@@ -1312,9 +1469,14 @@ async function waitHealthy(baseUrl, token, getError, resolved, timeoutMs = 15e3)
1312
1469
  throw new MindwireError(daemonStartError(resolved, err), { cause: err });
1313
1470
  }
1314
1471
  try {
1315
- const res = await fetch(`${baseUrl}/healthz`, { headers: { Authorization: `Bearer ${token}` } });
1316
- if (res.ok) return;
1317
- } catch {
1472
+ const res = await fetch(`${baseUrl}/healthz`, { headers: { Authorization: `Bearer ${token}` }, signal: AbortSignal.timeout(1e3) });
1473
+ if (res.ok) {
1474
+ if (authSource && (await res.json()).nativeAuthSource !== true)
1475
+ throw new MindwireError("This daemon does not support native auth sources. Update Mindwire or build the matching daemon before using authSource.");
1476
+ return;
1477
+ }
1478
+ } catch (error) {
1479
+ if (error instanceof MindwireError) throw error;
1318
1480
  }
1319
1481
  await new Promise((r) => setTimeout(r, 150));
1320
1482
  }
@@ -1330,17 +1492,27 @@ function emit(spec, e) {
1330
1492
  }
1331
1493
  }
1332
1494
  function local(opts = {}) {
1495
+ let daemon;
1496
+ let pending;
1333
1497
  return {
1334
1498
  name: "local",
1499
+ profileVersion: 1,
1335
1500
  async connect(spec) {
1336
- const d = await startEmbedded(opts);
1501
+ if (daemon && !daemon.isAlive()) {
1502
+ daemon = void 0;
1503
+ pending = void 0;
1504
+ }
1505
+ const d = await (pending ??= startEmbedded(opts).then((value) => daemon = value).catch((error) => {
1506
+ pending = void 0;
1507
+ throw error;
1508
+ }));
1337
1509
  emit(spec, { target: "local", phase: "ready", message: `embedded daemon on ${d.baseUrl}` });
1338
1510
  return {
1339
1511
  id: d.baseUrl,
1340
1512
  baseUrl: d.baseUrl,
1341
1513
  ...d.token !== void 0 ? { token: d.token } : {},
1342
- // No-op: the embedded daemon is shared (keyed memoization) and reaped on process exit.
1343
- stop: async () => {
1514
+ isAlive: d.isAlive,
1515
+ stop: opts.shared === false ? d.stop : async () => {
1344
1516
  }
1345
1517
  };
1346
1518
  }
@@ -1373,6 +1545,7 @@ var Mindwire = class _Mindwire {
1373
1545
  service;
1374
1546
  execution;
1375
1547
  computer;
1548
+ voice;
1376
1549
  http;
1377
1550
  /** The default agent type applied to agent-scoped calls, if set. */
1378
1551
  defaultAgent;
@@ -1411,6 +1584,7 @@ var Mindwire = class _Mindwire {
1411
1584
  this.service = new ServiceApi(this);
1412
1585
  this.execution = new ExecutionApi(this);
1413
1586
  this.computer = new ComputerApi(this);
1587
+ this.voice = new VoiceApi(this);
1414
1588
  this.auth = new AuthApi(this);
1415
1589
  this.prompts = new PromptsApi(this);
1416
1590
  this.mcp = new McpApi(this);
@@ -1436,6 +1610,7 @@ var Mindwire = class _Mindwire {
1436
1610
  clone.service = new ServiceApi(clone);
1437
1611
  clone.execution = new ExecutionApi(clone);
1438
1612
  clone.computer = new ComputerApi(clone);
1613
+ clone.voice = new VoiceApi(clone);
1439
1614
  clone.auth = new AuthApi(clone);
1440
1615
  clone.prompts = new PromptsApi(clone);
1441
1616
  clone.mcp = new McpApi(clone);
@@ -1452,8 +1627,8 @@ var Mindwire = class _Mindwire {
1452
1627
  * Release target-owned resources by calling the {@link TargetHandle}'s `stop()` — once, even across
1453
1628
  * {@link withAgent} clones that share the transport. For an `ssh`/`docker`/`oblien` target this reaps
1454
1629
  * the box (tear down the tunnel, stop or delete the container/workspace, per `stopOnExit`); a no-op
1455
- * for `local`/`remote` (embedded self-cleans on process exit; remote is not ours to stop). Safe to
1456
- * call before the target has connected (nothing to reap yet).
1630
+ * for shared local and remote targets. With `local({ shared: false })`, closes the owned daemon.
1631
+ * Safe to call before the target has connected (nothing to reap yet).
1457
1632
  */
1458
1633
  async close() {
1459
1634
  const handle = handleByTransport.get(this.http);
@@ -1498,6 +1673,12 @@ var Mindwire = class _Mindwire {
1498
1673
  models(scoped) {
1499
1674
  return this.http.request("GET", "/models", { query: this.agentParam(scoped) });
1500
1675
  }
1676
+ /** Discover native commands for a project without starting a model turn. */
1677
+ commands(opts = {}) {
1678
+ return this.http.request("GET", "/commands", {
1679
+ query: { ...this.agentParam(opts), dir: opts.directory, refresh: opts.refresh }
1680
+ });
1681
+ }
1501
1682
  /** `GET /doctor` — daemon-level health plus the selected agent's own checks. */
1502
1683
  doctor(scoped) {
1503
1684
  return this.http.request("GET", "/doctor", { query: this.agentParam(scoped) });
@@ -1635,7 +1816,8 @@ var Mindwire = class _Mindwire {
1635
1816
  limit,
1636
1817
  before: opts.before,
1637
1818
  paged: "true",
1638
- maxBytes: opts.maxBytes ?? 1048576
1819
+ maxBytes: opts.maxBytes ?? 1048576,
1820
+ ifRevision: opts.before ? void 0 : opts.ifRevision
1639
1821
  }
1640
1822
  }
1641
1823
  );
@@ -1714,7 +1896,7 @@ var Mindwire = class _Mindwire {
1714
1896
  `/chats/${encodeURIComponent(chatId)}/compact`,
1715
1897
  {
1716
1898
  query: this.agentParam(opts),
1717
- body: opts.instructions ? { instructions: opts.instructions } : {}
1899
+ body: { instructions: opts.instructions, requestId: opts.requestId }
1718
1900
  }
1719
1901
  );
1720
1902
  return new Run(this.http, data);
@@ -3157,6 +3339,6 @@ function clearCatalogCache() {
3157
3339
  inflight = null;
3158
3340
  }
3159
3341
 
3160
- export { AuthApi, ComputerApi, ContainerHost, ConversationsApi, ExecutionApi, GitAccessApi, Http, MODELS_DEV_URL, McpApi, Mindwire, NotifyApi, ProjectOperationsApi, ProjectSyncApi, ProjectSyncError, PromptsApi, ProvidersApi, Run, SDK_VERSION, ServiceApi, SurfacesApi, TerminalsApi, WorkspaceApi, WorkspaceCollection, catalogModels, catalogProvider, catalogProviders, clearCatalogCache, computerPairingQRData, computerPairingURI, docker, ensureDaemon, ensureDaemonBinary, loadCatalog, local, lookupModel, oblien, provisionContainer, provisionDocker, provisionOblien, provisionSsh, provisionSshContainer, remote, resolveLinuxDaemon, ssh, startEmbedded };
3342
+ export { AuthApi, ComputerApi, ContainerHost, ConversationsApi, ExecutionApi, GitAccessApi, Http, MODELS_DEV_URL, McpApi, Mindwire, NotifyApi, ProjectOperationsApi, ProjectSyncApi, ProjectSyncError, PromptsApi, ProvidersApi, Run, SDK_VERSION, ServiceApi, SurfacesApi, TerminalsApi, VoiceApi, VoiceSession, WorkspaceApi, WorkspaceCollection, catalogModels, catalogProvider, catalogProviders, clearCatalogCache, computerPairingQRData, computerPairingURI, docker, ensureDaemon, ensureDaemonBinary, loadCatalog, local, lookupModel, oblien, provisionContainer, provisionDocker, provisionOblien, provisionSsh, provisionSshContainer, remote, resolveLinuxDaemon, ssh, startEmbedded };
3161
3343
  //# sourceMappingURL=index.js.map
3162
3344
  //# sourceMappingURL=index.js.map