@pollen-robotics/reachy-mini-sdk 1.9.0 → 1.10.0-dev.0.main.60dddbe

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.
@@ -1,4 +1,4 @@
1
- var $ = Object.defineProperty, F = (r, e, t) => e in r ? $(r, e, { enumerable: !0, configurable: !0, writable: !0, value: t }) : r[e] = t, k = (r, e, t) => (F(r, typeof e != "symbol" ? e + "" : e, t), t), x = "https://huggingface.co";
1
+ var H = Object.defineProperty, $ = (r, e, t) => e in r ? H(r, e, { enumerable: !0, configurable: !0, writable: !0, value: t }) : r[e] = t, k = (r, e, t) => ($(r, typeof e != "symbol" ? e + "" : e, t), t), D = "https://huggingface.co";
2
2
  async function C(r, e) {
3
3
  var t, s;
4
4
  const i = new O(r.url, r.status, (t = r.headers.get("X-Request-Id")) != null ? t : void 0);
@@ -18,7 +18,7 @@ var O = class extends Error {
18
18
  };
19
19
  new Promise((r) => {
20
20
  });
21
- function H(r) {
21
+ function x(r) {
22
22
  if (globalThis.Buffer)
23
23
  return globalThis.Buffer.from(r).toString("base64");
24
24
  {
@@ -40,28 +40,28 @@ async function M(r) {
40
40
  throw new Error("Missing oauth code from query parameters in redirected URL");
41
41
  if (!a)
42
42
  throw new Error("Missing oauth nonce from localStorage");
43
- const c = localStorage.getItem("huggingface.co:oauth:code_verifier");
44
- if (!c)
45
- throw new Error("Missing oauth code_verifier from localStorage");
46
- const l = s.get("state");
43
+ const l = localStorage.getItem("huggingface.co:oauth:code_verifier");
47
44
  if (!l)
45
+ throw new Error("Missing oauth code_verifier from localStorage");
46
+ const c = s.get("state");
47
+ if (!c)
48
48
  throw new Error("Missing oauth state from query parameters in redirected URL");
49
49
  let h;
50
50
  try {
51
- h = JSON.parse(l);
51
+ h = JSON.parse(c);
52
52
  } catch {
53
- throw new Error("Invalid oauth state in redirected URL, unable to parse JSON: " + l);
53
+ throw new Error("Invalid oauth state in redirected URL, unable to parse JSON: " + c);
54
54
  }
55
55
  if (h.nonce !== a)
56
56
  throw new Error("Invalid oauth state in redirected URL");
57
- const d = x, m = `${new URL(d).origin}/.well-known/openid-configuration`, f = await fetch(m, {
57
+ const d = D, m = `${new URL(d).origin}/.well-known/openid-configuration`, f = await fetch(m, {
58
58
  headers: {
59
59
  Accept: "application/json"
60
60
  }
61
61
  });
62
62
  if (!f.ok)
63
63
  throw await C(f);
64
- const p = await f.json(), v = await fetch(p.token_endpoint, {
64
+ const p = await f.json(), y = await fetch(p.token_endpoint, {
65
65
  method: "POST",
66
66
  headers: {
67
67
  "Content-Type": "application/x-www-form-urlencoded"
@@ -70,12 +70,12 @@ async function M(r) {
70
70
  grant_type: "authorization_code",
71
71
  code: n,
72
72
  redirect_uri: h.redirectUri,
73
- code_verifier: c
73
+ code_verifier: l
74
74
  }).toString()
75
75
  });
76
- if (localStorage.removeItem("huggingface.co:oauth:code_verifier"), localStorage.removeItem("huggingface.co:oauth:nonce"), !v.ok)
77
- throw await C(v);
78
- const g = await v.json(), w = new Date(Date.now() + g.expires_in * 1e3), S = await fetch(p.userinfo_endpoint, {
76
+ if (localStorage.removeItem("huggingface.co:oauth:code_verifier"), localStorage.removeItem("huggingface.co:oauth:nonce"), !y.ok)
77
+ throw await C(y);
78
+ const g = await y.json(), w = new Date(Date.now() + g.expires_in * 1e3), S = await fetch(p.userinfo_endpoint, {
79
79
  headers: {
80
80
  Authorization: `Bearer ${g.access_token}`
81
81
  }
@@ -109,7 +109,7 @@ async function M(r) {
109
109
  scope: g.scope
110
110
  };
111
111
  }
112
- async function N(r) {
112
+ async function F(r) {
113
113
  if (typeof window > "u")
114
114
  throw new Error("oauthHandleRedirect is only available in the browser");
115
115
  const e = new URLSearchParams(window.location.search);
@@ -121,30 +121,30 @@ async function V(r) {
121
121
  var e, t;
122
122
  if (typeof window > "u")
123
123
  throw new Error("oauthLogin is only available in the browser");
124
- const s = r?.hubUrl || x, i = `${new URL(s).origin}/.well-known/openid-configuration`, o = await fetch(i, {
124
+ const s = r?.hubUrl || D, i = `${new URL(s).origin}/.well-known/openid-configuration`, o = await fetch(i, {
125
125
  headers: {
126
126
  Accept: "application/json"
127
127
  }
128
128
  });
129
129
  if (!o.ok)
130
130
  throw await C(o);
131
- const n = await o.json(), a = globalThis.crypto.randomUUID(), c = globalThis.crypto.randomUUID() + globalThis.crypto.randomUUID();
132
- localStorage.setItem("huggingface.co:oauth:nonce", a), localStorage.setItem("huggingface.co:oauth:code_verifier", c);
133
- const l = r?.redirectUrl || window.location.href, h = JSON.stringify({
131
+ const n = await o.json(), a = globalThis.crypto.randomUUID(), l = globalThis.crypto.randomUUID() + globalThis.crypto.randomUUID();
132
+ localStorage.setItem("huggingface.co:oauth:nonce", a), localStorage.setItem("huggingface.co:oauth:code_verifier", l);
133
+ const c = r?.redirectUrl || window.location.href, h = JSON.stringify({
134
134
  nonce: a,
135
- redirectUri: l,
135
+ redirectUri: c,
136
136
  state: r?.state
137
137
  }), d = (t = (e = window?.huggingface) == null ? void 0 : e.variables) != null ? t : null, m = r?.clientId || d?.OAUTH_CLIENT_ID;
138
138
  if (!m)
139
139
  throw d ? new Error("Missing clientId, please add hf_oauth: true to the README.md's metadata in your static Space") : new Error("Missing clientId");
140
- const f = H(
141
- new Uint8Array(await globalThis.crypto.subtle.digest("SHA-256", new TextEncoder().encode(c)))
140
+ const f = x(
141
+ new Uint8Array(await globalThis.crypto.subtle.digest("SHA-256", new TextEncoder().encode(l)))
142
142
  ).replace(/[+]/g, "-").replace(/[/]/g, "_").replace(/=/g, "");
143
143
  return `${n.authorization_endpoint}?${new URLSearchParams({
144
144
  client_id: m,
145
145
  scope: r?.scopes || d?.OAUTH_SCOPES || "openid profile",
146
146
  response_type: "code",
147
- redirect_uri: l,
147
+ redirect_uri: c,
148
148
  state: h,
149
149
  code_challenge: f,
150
150
  code_challenge_method: "S256"
@@ -154,11 +154,11 @@ function R(r) {
154
154
  return r * Math.PI / 180;
155
155
  }
156
156
  function B(r, e, t) {
157
- const s = R(r), i = R(e), o = R(t), n = Math.cos(o), a = Math.sin(o), c = Math.cos(i), l = Math.sin(i), h = Math.cos(s), d = Math.sin(s);
157
+ const s = R(r), i = R(e), o = R(t), n = Math.cos(o), a = Math.sin(o), l = Math.cos(i), c = Math.sin(i), h = Math.cos(s), d = Math.sin(s);
158
158
  return [
159
- [n * c, n * l * d - a * h, n * l * h + a * d, 0],
160
- [a * c, a * l * d + n * h, a * l * h - n * d, 0],
161
- [-l, c * d, c * h, 0],
159
+ [n * l, n * c * d - a * h, n * c * h + a * d, 0],
160
+ [a * l, a * c * d + n * h, a * c * h - n * d, 0],
161
+ [-c, l * d, l * h, 0],
162
162
  [0, 0, 0, 1]
163
163
  ];
164
164
  }
@@ -176,8 +176,8 @@ function G() {
176
176
  const s = e.get("hf_username") || "user", i = e.get("hf_token_expires"), o = i && !Number.isNaN(new Date(i).getTime()) ? i : new Date(Date.now() + 365 * 24 * 60 * 60 * 1e3).toISOString();
177
177
  try {
178
178
  sessionStorage.setItem("hf_token", t), sessionStorage.setItem("hf_username", s), sessionStorage.setItem("hf_token_expires", o);
179
- } catch (c) {
180
- console.warn("[reachy-mini] could not persist pre-seeded HF credentials:", c);
179
+ } catch (l) {
180
+ console.warn("[reachy-mini] could not persist pre-seeded HF credentials:", l);
181
181
  }
182
182
  e.delete("hf_token"), e.delete("hf_username"), e.delete("hf_token_expires");
183
183
  const n = e.toString(), a = window.location.pathname + window.location.search + (n ? "#" + n : "");
@@ -212,14 +212,14 @@ function W(r) {
212
212
  if (s.startsWith("m=audio") ? t = !0 : s.startsWith("m=") && (t = !1), t && s === "a=sendrecv") return !0;
213
213
  return !1;
214
214
  }
215
- const y = 12 * 1024, A = 1 * 1024 * 1024, z = 512 * 1024;
215
+ const v = 12 * 1024, E = 1 * 1024 * 1024, z = 512 * 1024;
216
216
  function J() {
217
217
  return typeof CompressionStream < "u";
218
218
  }
219
219
  function U() {
220
220
  return "u" + Math.random().toString(36).slice(2, 11) + Date.now().toString(36);
221
221
  }
222
- function E(r) {
222
+ function T(r) {
223
223
  let e = "";
224
224
  for (let s = 0; s < r.length; s += 32768)
225
225
  e += String.fromCharCode.apply(
@@ -232,7 +232,7 @@ async function X(r) {
232
232
  const e = new TextEncoder().encode(r), t = await new Response(
233
233
  new Blob([e]).stream().pipeThrough(new CompressionStream("gzip"))
234
234
  ).arrayBuffer();
235
- return E(new Uint8Array(t));
235
+ return T(new Uint8Array(t));
236
236
  }
237
237
  function L(r) {
238
238
  const e = Math.round(Number(r) || 0);
@@ -261,8 +261,8 @@ function P(r) {
261
261
  return "wav-base64";
262
262
  }
263
263
  }
264
- const j = 3e3, D = 1e3, Y = 6e4;
265
- class K extends EventTarget {
264
+ const j = 3e3, N = 1e3, Y = 6e4, K = 4e3;
265
+ class Q extends EventTarget {
266
266
  // ─── Config ──────────────────────────────────────────────────────────
267
267
  _signalingUrl;
268
268
  _clientId;
@@ -301,6 +301,8 @@ class K extends EventTarget {
301
301
  _volumeResolve = null;
302
302
  _micVolumeResolve = null;
303
303
  _trackedFaceResolve = null;
304
+ _robotNameResolve = null;
305
+ _deleteHfTokenResolve = null;
304
306
  // applyAudioConfig() / readAudioParameter() share the same single-slot
305
307
  // pattern as the volume helpers. Separate slots so the two can be
306
308
  // in-flight concurrently without collision.
@@ -309,6 +311,13 @@ class K extends EventTarget {
309
311
  // ─── Log subscribers ─────────────────────────────────────────────────
310
312
  _logSubscribers = /* @__PURE__ */ new Set();
311
313
  _updateProgressSubscribers = /* @__PURE__ */ new Set();
314
+ // ─── JSON-RPC app control (over the same DataChannel) ────────────────
315
+ // rpcCall() sends {jsonrpc,id,method,params} and awaits the matching
316
+ // response; onNotification() subscribes to one-way events (no id) the
317
+ // robot/app pushes (conversation.phase/turn/transcript, ...).
318
+ _rpcCounter = 0;
319
+ _pendingRpc = /* @__PURE__ */ new Map();
320
+ _rpcListeners = /* @__PURE__ */ new Map();
312
321
  // ─── Broadcast waiters (playMove / playUploadedAudio) ────────────────
313
322
  _broadcastWaiters = [];
314
323
  // ─── Active upload ids for no-arg cancels ────────────────────────────
@@ -393,7 +402,7 @@ class K extends EventTarget {
393
402
  async authenticate() {
394
403
  try {
395
404
  G();
396
- const e = await N();
405
+ const e = await F();
397
406
  if (e)
398
407
  return this._username = e.userInfo.preferred_username || e.userInfo.name || null, this._token = e.accessToken, this._tokenExpires = e.accessTokenExpiresAt, sessionStorage.setItem("hf_token", this._token), sessionStorage.setItem("hf_username", this._username ?? ""), sessionStorage.setItem(
399
408
  "hf_token_expires",
@@ -434,16 +443,16 @@ class K extends EventTarget {
434
443
  return new Promise((s, i) => {
435
444
  let o = !1;
436
445
  const n = t.body.getReader(), a = new TextDecoder();
437
- let c = "";
446
+ let l = "";
438
447
  (async () => {
439
448
  try {
440
449
  for (; ; ) {
441
450
  const { done: h, value: d } = await n.read();
442
451
  if (h) break;
443
- c += a.decode(d, { stream: !0 });
444
- const m = c.split(`
452
+ l += a.decode(d, { stream: !0 });
453
+ const m = l.split(`
445
454
  `);
446
- c = m.pop() ?? "";
455
+ l = m.pop() ?? "";
447
456
  for (const f of m)
448
457
  if (f.startsWith("data:"))
449
458
  try {
@@ -475,10 +484,10 @@ class K extends EventTarget {
475
484
  wakeOnConnect: n = !0
476
485
  } = e;
477
486
  if (this._state === "streaming") {
478
- const c = this._robots?.find((l) => l.id === this._selectedRobotId);
487
+ const l = this._robots?.find((c) => c.id === this._selectedRobotId);
479
488
  return {
480
489
  robotId: this._selectedRobotId,
481
- robotName: c?.meta?.name ?? null,
490
+ robotName: l?.meta?.name ?? null,
482
491
  isEmbedded: this.isEmbedded,
483
492
  alreadyStreaming: !0
484
493
  };
@@ -491,36 +500,36 @@ class K extends EventTarget {
491
500
  else if (!this._token && !await this.authenticate())
492
501
  throw new Error("Not authenticated — call login() or pass a token");
493
502
  this._state === "disconnected" && await this.connect();
494
- let c, l = null;
503
+ let l, c = null;
495
504
  if (this.isEmbedded) {
496
- c = this._preselectedRobotId;
505
+ l = this._preselectedRobotId;
497
506
  try {
498
- await this._waitForRobotInList(c, 5e3);
507
+ await this._waitForRobotInList(l, 5e3);
499
508
  } catch {
500
509
  }
501
- l = this._robots?.find((d) => d.id === c)?.meta?.name ?? null;
510
+ c = this._robots?.find((d) => d.id === l)?.meta?.name ?? null;
502
511
  } else {
503
512
  const h = await this._fetchOwnedRobots({ filterBusy: o });
504
513
  if (h.length === 0)
505
514
  throw new Error("No reachable robots");
506
515
  if (i && h.length === 1 && !h[0].busy)
507
- c = h[0].id, l = h[0].name;
516
+ l = h[0].id, c = h[0].name;
508
517
  else if (s) {
509
518
  const d = await s(h);
510
519
  if (!d) throw new Error("Robot selection cancelled");
511
- c = d, l = h.find((m) => m.id === d)?.name ?? null;
520
+ l = d, c = h.find((m) => m.id === d)?.name ?? null;
512
521
  } else
513
522
  throw new Error(
514
523
  "Multiple robots available — pass a pickRobot callback to autoConnect()"
515
524
  );
516
525
  }
517
- if (await this.startSession(c), n && typeof this.ensureAwake == "function")
526
+ if (await this.startSession(l), n && typeof this.ensureAwake == "function")
518
527
  try {
519
528
  await this.ensureAwake();
520
529
  } catch (h) {
521
530
  console.warn("[reachy-mini] autoConnect: ensureAwake failed:", h);
522
531
  }
523
- return { robotId: c, robotName: l, isEmbedded: this.isEmbedded };
532
+ return { robotId: l, robotName: c, isEmbedded: this.isEmbedded };
524
533
  } finally {
525
534
  this._autoStartFromUrl = a;
526
535
  }
@@ -576,10 +585,10 @@ class K extends EventTarget {
576
585
  if (!s) throw new Error("AudioContext not supported");
577
586
  const i = new s(), o = i.createMediaStreamDestination(), n = i.createOscillator(), a = i.createGain();
578
587
  a.gain.value = 0, n.connect(a).connect(o), n.start();
579
- const c = o.stream;
580
- c.getAudioTracks().forEach((l) => {
581
- l.enabled = !1;
582
- }), this._micStream = c, this._micMuted = !0;
588
+ const l = o.stream;
589
+ l.getAudioTracks().forEach((c) => {
590
+ c.enabled = !1;
591
+ }), this._micStream = l, this._micMuted = !0;
583
592
  } catch (t) {
584
593
  console.warn("Audio sender placeholder setup failed:", t), this._micStream = null;
585
594
  }
@@ -621,7 +630,7 @@ class K extends EventTarget {
621
630
  return;
622
631
  }
623
632
  if (i === "failed") {
624
- this._scheduleIceGrace(D, "failed");
633
+ this._scheduleIceGrace(N, "failed");
625
634
  return;
626
635
  }
627
636
  }
@@ -648,10 +657,10 @@ class K extends EventTarget {
648
657
  }
649
658
  }
650
659
  async stopSession() {
651
- this._versionResolve && (this._versionResolve(null), this._versionResolve = null), this._hardwareIdResolve && (this._hardwareIdResolve(null), this._hardwareIdResolve = null), this._volumeResolve && (this._volumeResolve(null), this._volumeResolve = null), this._micVolumeResolve && (this._micVolumeResolve(null), this._micVolumeResolve = null), this._trackedFaceResolve && (this._trackedFaceResolve(null), this._trackedFaceResolve = null), this._applyAudioConfigResolve && (this._applyAudioConfigResolve(!1), this._applyAudioConfigResolve = null), this._readAudioParameterResolve && (this._readAudioParameterResolve(null), this._readAudioParameterResolve = null), this._logSubscribers.clear(), this._updateProgressSubscribers.clear(), this._rejectPendingMotionCompletions(new Error("Session stopped")), this._clearIceGrace(), this._uninstallNetworkListeners(), this._sessionReject && (this._sessionReject(new Error("Session stopped")), this._sessionResolve = null, this._sessionReject = null), this._stateRefreshInterval && (clearInterval(this._stateRefreshInterval), this._stateRefreshInterval = null), this._latencyMonitorId && (clearInterval(this._latencyMonitorId), this._latencyMonitorId = null), this._sessionId && await this._sendToServer({ type: "endSession", sessionId: this._sessionId }), this._micStream && (this._micStream.getTracks().forEach((t) => t.stop()), this._micStream = null), this._micMuted = !0, this._micSupported = !1, this._pc && (this._pc.close(), this._pc = null), this._dc && (this._dc.close(), this._dc = null), this._sessionId = null, this._iceConnected = !1, this._dcOpen = !1, this._state === "streaming" && (this._state = "connected", this._emit("sessionStopped", { reason: "user" }));
660
+ this._versionResolve && (this._versionResolve(null), this._versionResolve = null), this._hardwareIdResolve && (this._hardwareIdResolve(null), this._hardwareIdResolve = null), this._volumeResolve && (this._volumeResolve(null), this._volumeResolve = null), this._micVolumeResolve && (this._micVolumeResolve(null), this._micVolumeResolve = null), this._trackedFaceResolve && (this._trackedFaceResolve(null), this._trackedFaceResolve = null), this._applyAudioConfigResolve && (this._applyAudioConfigResolve(!1), this._applyAudioConfigResolve = null), this._readAudioParameterResolve && (this._readAudioParameterResolve(null), this._readAudioParameterResolve = null), this._robotNameResolve && (this._robotNameResolve(null), this._robotNameResolve = null), this._deleteHfTokenResolve && (this._deleteHfTokenResolve(null), this._deleteHfTokenResolve = null), this._logSubscribers.clear(), this._updateProgressSubscribers.clear(), this._rejectPendingMotionCompletions(new Error("Session stopped")), this._rejectPendingRpc(new Error("Session stopped")), this._clearIceGrace(), this._uninstallNetworkListeners(), this._sessionReject && (this._sessionReject(new Error("Session stopped")), this._sessionResolve = null, this._sessionReject = null), this._stateRefreshInterval && (clearInterval(this._stateRefreshInterval), this._stateRefreshInterval = null), this._latencyMonitorId && (clearInterval(this._latencyMonitorId), this._latencyMonitorId = null), this._sessionId && await this._sendToServer({ type: "endSession", sessionId: this._sessionId }), this._micStream && (this._micStream.getTracks().forEach((t) => t.stop()), this._micStream = null), this._micMuted = !0, this._micSupported = !1, this._pc && (this._pc.close(), this._pc = null), this._dc && (this._dc.close(), this._dc = null), this._sessionId = null, this._iceConnected = !1, this._dcOpen = !1, this._state === "streaming" && (this._state = "connected", this._emit("sessionStopped", { reason: "user" }));
652
661
  }
653
662
  disconnect() {
654
- this._sseAbortController && (this._sseAbortController.abort(), this._sseAbortController = null), this._versionResolve && (this._versionResolve(null), this._versionResolve = null), this._hardwareIdResolve && (this._hardwareIdResolve(null), this._hardwareIdResolve = null), this._volumeResolve && (this._volumeResolve(null), this._volumeResolve = null), this._micVolumeResolve && (this._micVolumeResolve(null), this._micVolumeResolve = null), this._trackedFaceResolve && (this._trackedFaceResolve(null), this._trackedFaceResolve = null), this._applyAudioConfigResolve && (this._applyAudioConfigResolve(!1), this._applyAudioConfigResolve = null), this._readAudioParameterResolve && (this._readAudioParameterResolve(null), this._readAudioParameterResolve = null), this._logSubscribers.clear(), this._updateProgressSubscribers.clear(), this._rejectPendingMotionCompletions(new Error("Disconnected")), this._clearIceGrace(), this._uninstallNetworkListeners(), this._sessionReject && (this._sessionReject(new Error("Disconnected")), this._sessionResolve = null, this._sessionReject = null), this._stateRefreshInterval && (clearInterval(this._stateRefreshInterval), this._stateRefreshInterval = null), this._latencyMonitorId && (clearInterval(this._latencyMonitorId), this._latencyMonitorId = null), this._sessionId && this._token && this._sendToServer({ type: "endSession", sessionId: this._sessionId }), this._micStream && (this._micStream.getTracks().forEach((e) => e.stop()), this._micStream = null), this._pc && (this._pc.close(), this._pc = null), this._dc && (this._dc.close(), this._dc = null), this._sessionId = null, this._micMuted = !0, this._micSupported = !1, this._iceConnected = !1, this._dcOpen = !1, this._robots = [], this._state = "disconnected", this._emit("disconnected", { reason: "user" });
663
+ this._sseAbortController && (this._sseAbortController.abort(), this._sseAbortController = null), this._versionResolve && (this._versionResolve(null), this._versionResolve = null), this._hardwareIdResolve && (this._hardwareIdResolve(null), this._hardwareIdResolve = null), this._volumeResolve && (this._volumeResolve(null), this._volumeResolve = null), this._micVolumeResolve && (this._micVolumeResolve(null), this._micVolumeResolve = null), this._trackedFaceResolve && (this._trackedFaceResolve(null), this._trackedFaceResolve = null), this._applyAudioConfigResolve && (this._applyAudioConfigResolve(!1), this._applyAudioConfigResolve = null), this._readAudioParameterResolve && (this._readAudioParameterResolve(null), this._readAudioParameterResolve = null), this._robotNameResolve && (this._robotNameResolve(null), this._robotNameResolve = null), this._deleteHfTokenResolve && (this._deleteHfTokenResolve(null), this._deleteHfTokenResolve = null), this._logSubscribers.clear(), this._updateProgressSubscribers.clear(), this._rejectPendingMotionCompletions(new Error("Disconnected")), this._clearIceGrace(), this._uninstallNetworkListeners(), this._sessionReject && (this._sessionReject(new Error("Disconnected")), this._sessionResolve = null, this._sessionReject = null), this._stateRefreshInterval && (clearInterval(this._stateRefreshInterval), this._stateRefreshInterval = null), this._latencyMonitorId && (clearInterval(this._latencyMonitorId), this._latencyMonitorId = null), this._sessionId && this._token && this._sendToServer({ type: "endSession", sessionId: this._sessionId }), this._micStream && (this._micStream.getTracks().forEach((e) => e.stop()), this._micStream = null), this._pc && (this._pc.close(), this._pc = null), this._dc && (this._dc.close(), this._dc = null), this._sessionId = null, this._micMuted = !0, this._micSupported = !1, this._iceConnected = !1, this._dcOpen = !1, this._robots = [], this._state = "disconnected", this._emit("disconnected", { reason: "user" });
655
664
  }
656
665
  // ─── Resilience: ICE-blip debounce + network awareness ───────────────
657
666
  //
@@ -724,7 +733,7 @@ class K extends EventTarget {
724
733
  return;
725
734
  }
726
735
  if (s === "failed") {
727
- this._scheduleIceGrace(D, "failed");
736
+ this._scheduleIceGrace(N, "failed");
728
737
  return;
729
738
  }
730
739
  this._scheduleIceGrace(j, "disconnected");
@@ -1026,6 +1035,61 @@ class K extends EventTarget {
1026
1035
  { type: "set_microphone_volume", volume: L(e) }
1027
1036
  );
1028
1037
  }
1038
+ /**
1039
+ * Query the persisted robot display name. Resolves the stored name,
1040
+ * `null` when none is set / the channel isn't open / the daemon predates
1041
+ * the `get_robot_name` command.
1042
+ */
1043
+ getRobotName() {
1044
+ return this._slotRoundtrip(
1045
+ () => this._robotNameResolve,
1046
+ (e) => {
1047
+ this._robotNameResolve = e;
1048
+ },
1049
+ { type: "get_robot_name" }
1050
+ );
1051
+ }
1052
+ /**
1053
+ * Set and persist the robot display name on the robot. Resolves with the
1054
+ * stored (trimmed) name, or `null` on error / channel-closed. Applied live
1055
+ * by the daemon (status + central relay + mDNS), so it takes effect right
1056
+ * away without a restart; the persisted name also overrides --robot-name
1057
+ * on the next start.
1058
+ */
1059
+ setRobotName(e) {
1060
+ return this._slotRoundtrip(
1061
+ () => this._robotNameResolve,
1062
+ (t) => {
1063
+ this._robotNameResolve = t;
1064
+ },
1065
+ { type: "set_robot_name", name: e }
1066
+ );
1067
+ }
1068
+ /**
1069
+ * Sign this robot out of Hugging Face: asks the daemon to delete its
1070
+ * stored HF token, which de-registers the robot from the central
1071
+ * signaling relay (it disappears from its owner's robot list until it
1072
+ * is set up again). Works over the WebRTC data channel, so it reaches
1073
+ * the robot remotely (no LAN HTTP path required).
1074
+ *
1075
+ * Resolves `true` when the daemon acked success, `false` on a daemon
1076
+ * error, or `null` when no ack arrives before the timeout (e.g. a
1077
+ * daemon that predates the `delete_hf_token` command silently drops
1078
+ * it). Rejects if the data channel isn't open. Note the sign-out
1079
+ * drops the central relay, so the session may tear down right after
1080
+ * the ack - callers should treat a post-call session drop as expected,
1081
+ * and a successful sign-out may surface as `null` if teardown races
1082
+ * ahead of the ack.
1083
+ */
1084
+ signOut() {
1085
+ return this._slotRoundtrip(
1086
+ () => this._deleteHfTokenResolve,
1087
+ (e) => {
1088
+ this._deleteHfTokenResolve = e;
1089
+ },
1090
+ { type: "delete_hf_token" }
1091
+ );
1092
+ }
1029
1093
  applyAudioConfig(e, { verify: t = !0 } = {}) {
1030
1094
  return this._slotRoundtrip(
1031
1095
  () => this._applyAudioConfigResolve,
@@ -1065,7 +1129,12 @@ class K extends EventTarget {
1065
1129
  return;
1066
1130
  }
1067
1131
  const n = e();
1068
- n && n(null), t(i), this._sendCommand(s);
1132
+ n && n(null);
1133
+ let a;
1134
+ const l = (c) => {
1135
+ a !== void 0 && (clearTimeout(a), a = void 0), e() === l && t(null), i(c);
1136
+ };
1137
+ t(l), a = setTimeout(() => l(null), K), this._sendCommand(s);
1069
1138
  });
1070
1139
  }
1071
1140
  sendRaw(e) {
@@ -1114,8 +1183,8 @@ class K extends EventTarget {
1114
1183
  encoding: o = "gzip+base64",
1115
1184
  playFrequency: n = 100,
1116
1185
  initialGotoDuration: a = 0,
1117
- startTimeoutMs: c = 8e3,
1118
- onProgress: l = () => {
1186
+ startTimeoutMs: l = 8e3,
1187
+ onProgress: c = () => {
1119
1188
  },
1120
1189
  onStarted: h = () => {
1121
1190
  }
@@ -1131,35 +1200,35 @@ class K extends EventTarget {
1131
1200
  time: e.time,
1132
1201
  set_target_data: e.set_target_data
1133
1202
  }, f = JSON.stringify(m);
1134
- let p, v;
1135
- o === "gzip+base64" && J() ? (p = await X(f), v = "gzip+base64") : (p = f, v = "json");
1136
- const g = Math.ceil(p.length / y) || 1;
1137
- l({
1203
+ let p, y;
1204
+ o === "gzip+base64" && J() ? (p = await X(f), y = "gzip+base64") : (p = f, y = "json");
1205
+ const g = Math.ceil(p.length / v) || 1;
1206
+ c({
1138
1207
  phase: "starting",
1139
1208
  sent: 0,
1140
1209
  total: g,
1141
1210
  bytes: p.length,
1142
- encoding: v
1211
+ encoding: y
1143
1212
  }), this._sendCommand({
1144
1213
  type: "upload_move_start",
1145
1214
  upload_id: d,
1146
1215
  total_chunks: g,
1147
1216
  description: i,
1148
- encoding: v
1217
+ encoding: y
1149
1218
  });
1150
1219
  for (let u = 0; u < g; u++) {
1151
- this._dc.bufferedAmount > A && await this._awaitDataChannelDrain();
1152
- const _ = u * y;
1220
+ this._dc.bufferedAmount > E && await this._awaitDataChannelDrain();
1221
+ const _ = u * v;
1153
1222
  this._sendCommand({
1154
1223
  type: "upload_move_chunk",
1155
1224
  upload_id: d,
1156
1225
  chunk_index: u,
1157
- chunk: p.slice(_, _ + y)
1158
- }), l({ phase: "upload", sent: u + 1, total: g });
1226
+ chunk: p.slice(_, _ + v)
1227
+ }), c({ phase: "upload", sent: u + 1, total: g });
1159
1228
  }
1160
- if (this._sendCommand({ type: "upload_move_finish", upload_id: d }), l({ phase: "uploaded", sent: g, total: g }), t) {
1161
- const u = new Uint8Array(await t.arrayBuffer()), _ = E(u), b = Math.ceil(_.length / y) || 1;
1162
- l({
1229
+ if (this._sendCommand({ type: "upload_move_finish", upload_id: d }), c({ phase: "uploaded", sent: g, total: g }), t) {
1230
+ const u = new Uint8Array(await t.arrayBuffer()), _ = T(u), b = Math.ceil(_.length / v) || 1;
1231
+ c({
1163
1232
  phase: "audio-starting",
1164
1233
  sent: 0,
1165
1234
  total: b,
@@ -1172,16 +1241,16 @@ class K extends EventTarget {
1172
1241
  description: i
1173
1242
  });
1174
1243
  for (let I = 0; I < b; I++) {
1175
- this._dc.bufferedAmount > A && await this._awaitDataChannelDrain();
1176
- const T = I * y;
1244
+ this._dc.bufferedAmount > E && await this._awaitDataChannelDrain();
1245
+ const A = I * v;
1177
1246
  this._sendCommand({
1178
1247
  type: "upload_audio_chunk",
1179
1248
  upload_id: d,
1180
1249
  chunk_index: I,
1181
- chunk: _.slice(T, T + y)
1182
- }), l({ phase: "audio-upload", sent: I + 1, total: b });
1250
+ chunk: _.slice(A, A + v)
1251
+ }), c({ phase: "audio-upload", sent: I + 1, total: b });
1183
1252
  }
1184
- this._sendCommand({ type: "upload_audio_finish", upload_id: d }), l({ phase: "audio-uploaded", sent: b, total: b });
1253
+ this._sendCommand({ type: "upload_audio_finish", upload_id: d }), c({ phase: "audio-uploaded", sent: b, total: b });
1185
1254
  }
1186
1255
  this._sendCommand({
1187
1256
  type: "play_uploaded_move",
@@ -1194,7 +1263,7 @@ class K extends EventTarget {
1194
1263
  try {
1195
1264
  w = await this._waitForBroadcast(
1196
1265
  (u) => u?.type === "play_uploaded_move" && u?.upload_id === d && (u.started === !0 || typeof u.error == "string"),
1197
- { timeoutMs: c, debugLabel: "play_uploaded_move started" }
1266
+ { timeoutMs: l, debugLabel: "play_uploaded_move started" }
1198
1267
  );
1199
1268
  } catch (u) {
1200
1269
  throw new Error(
@@ -1211,7 +1280,7 @@ class K extends EventTarget {
1211
1280
  } catch (u) {
1212
1281
  console.warn("playMove.onStarted threw:", u);
1213
1282
  }
1214
- l({ phase: "playing", duration_s: w.duration_s });
1283
+ c({ phase: "playing", duration_s: w.duration_s });
1215
1284
  const S = await this._waitForBroadcast(
1216
1285
  (u) => u?.type === "play_uploaded_move" && u?.upload_id === d && (u.finished === !0 || u.cancelled === !0 || typeof u.error == "string"),
1217
1286
  {
@@ -1231,7 +1300,7 @@ class K extends EventTarget {
1231
1300
  throw new Error("data channel not open");
1232
1301
  if (!(e instanceof Blob))
1233
1302
  throw new TypeError("uploadAudio: expected a Blob");
1234
- const i = U(), o = new Uint8Array(await e.arrayBuffer()), n = E(o), a = Math.ceil(n.length / y) || 1;
1303
+ const i = U(), o = new Uint8Array(await e.arrayBuffer()), n = T(o), a = Math.ceil(n.length / v) || 1;
1235
1304
  s({ phase: "audio-starting", sent: 0, total: a, bytes: n.length }), this._sendCommand({
1236
1305
  type: "upload_audio_start",
1237
1306
  upload_id: i,
@@ -1239,15 +1308,15 @@ class K extends EventTarget {
1239
1308
  encoding: P(e),
1240
1309
  description: t
1241
1310
  });
1242
- for (let c = 0; c < a; c++) {
1243
- this._dc.bufferedAmount > A && await this._awaitDataChannelDrain();
1244
- const l = c * y;
1311
+ for (let l = 0; l < a; l++) {
1312
+ this._dc.bufferedAmount > E && await this._awaitDataChannelDrain();
1313
+ const c = l * v;
1245
1314
  this._sendCommand({
1246
1315
  type: "upload_audio_chunk",
1247
1316
  upload_id: i,
1248
- chunk_index: c,
1249
- chunk: n.slice(l, l + y)
1250
- }), s({ phase: "audio-upload", sent: c + 1, total: a });
1317
+ chunk_index: l,
1318
+ chunk: n.slice(c, c + v)
1319
+ }), s({ phase: "audio-upload", sent: l + 1, total: a });
1251
1320
  }
1252
1321
  return this._sendCommand({ type: "upload_audio_finish", upload_id: i }), s({ phase: "audio-uploaded", sent: a, total: a }), i;
1253
1322
  }
@@ -1318,6 +1387,62 @@ class K extends EventTarget {
1318
1387
  _sendCommand(e) {
1319
1388
  return !this._dc || this._dc.readyState !== "open" ? !1 : (this._dc.send(JSON.stringify(e)), !0);
1320
1389
  }
1390
+ /**
1391
+ * Call a JSON-RPC method on the robot/app over the DataChannel and await
1392
+ * its result. This is the one way to drive an on-robot app (start/stop it
1393
+ * via `apps.*`, or drive a running app via its own namespace, e.g.
1394
+ * `conversation.say`). Rejects on the JSON-RPC error, a closed channel, or
1395
+ * timeout.
1396
+ */
1397
+ rpcCall(e, t = {}, s = {}) {
1398
+ const i = s.timeoutMs ?? 2e4, o = `rpc-${++this._rpcCounter}`;
1399
+ return this._sendCommand({ jsonrpc: "2.0", id: o, method: e, params: t }) ? new Promise((n, a) => {
1400
+ const l = setTimeout(() => {
1401
+ this._pendingRpc.delete(o), a(new Error(`rpcCall(${e}) timed out after ${i}ms`));
1402
+ }, i);
1403
+ this._pendingRpc.set(o, {
1404
+ resolve: (c) => n(c),
1405
+ reject: a,
1406
+ timer: l
1407
+ });
1408
+ }) : Promise.reject(new Error(`rpcCall(${e}): data channel not open`));
1409
+ }
1410
+ /**
1411
+ * Subscribe to a JSON-RPC notification (one-way event) pushed by the
1412
+ * robot/app, e.g. `conversation.turn`. Returns an unsubscribe function.
1413
+ */
1414
+ onNotification(e, t) {
1415
+ let s = this._rpcListeners.get(e);
1416
+ return s || (s = /* @__PURE__ */ new Set(), this._rpcListeners.set(e, s)), s.add(t), () => {
1417
+ this._rpcListeners.get(e)?.delete(t);
1418
+ };
1419
+ }
1420
+ _rejectPendingRpc(e) {
1421
+ for (const t of this._pendingRpc.values())
1422
+ clearTimeout(t.timer), t.reject(e);
1423
+ this._pendingRpc.clear();
1424
+ }
1425
+ _handleRpcMessage(e) {
1426
+ if ("id" in e && e.id != null && ("result" in e || "error" in e)) {
1427
+ const t = this._pendingRpc.get(e.id);
1428
+ if (!t) return;
1429
+ if (this._pendingRpc.delete(e.id), clearTimeout(t.timer), "error" in e && e.error) {
1430
+ const s = e.error, i = new Error(s.message ?? "rpc error");
1431
+ i.reason = s.data?.reason, t.reject(i);
1432
+ } else
1433
+ t.resolve(e.result);
1434
+ return;
1435
+ }
1436
+ if (typeof e.method == "string") {
1437
+ const t = e.params ?? {};
1438
+ for (const s of this._rpcListeners.get(e.method) ?? [])
1439
+ try {
1440
+ s(t);
1441
+ } catch (i) {
1442
+ console.error(`onNotification(${e.method}) threw:`, i);
1443
+ }
1444
+ }
1445
+ }
1321
1446
  _checkSessionReady() {
1322
1447
  this._iceConnected && this._dcOpen && this._sessionResolve && (this._state = "streaming", this.requestState(), this._stateRefreshInterval = setInterval(() => this.requestState(), 500), this._emit("streaming", { sessionId: this._sessionId, robotId: this._selectedRobotId }), this._sessionResolve(), this._sessionResolve = null, this._sessionReject = null);
1323
1448
  }
@@ -1403,6 +1528,10 @@ class K extends EventTarget {
1403
1528
  }
1404
1529
  }
1405
1530
  _handleRobotMessage(e) {
1531
+ if (e.jsonrpc === "2.0") {
1532
+ this._handleRpcMessage(e);
1533
+ return;
1534
+ }
1406
1535
  if ("version" in e && this._versionResolve) {
1407
1536
  this._versionResolve(e.version), this._versionResolve = null;
1408
1537
  return;
@@ -1419,6 +1548,16 @@ class K extends EventTarget {
1419
1548
  this._micVolumeResolve && (this._micVolumeResolve(e.status === "error" ? null : e.volume), this._micVolumeResolve = null);
1420
1549
  return;
1421
1550
  }
1551
+ if (e.command === "get_robot_name" || e.command === "set_robot_name") {
1552
+ this._robotNameResolve && (this._robotNameResolve(
1553
+ e.status === "error" ? null : e.name ?? null
1554
+ ), this._robotNameResolve = null);
1555
+ return;
1556
+ }
1557
+ if (e.command === "delete_hf_token") {
1558
+ this._deleteHfTokenResolve && (this._deleteHfTokenResolve(e.status !== "error"), this._deleteHfTokenResolve = null);
1559
+ return;
1560
+ }
1422
1561
  if (e.command === "apply_audio_config") {
1423
1562
  this._applyAudioConfigResolve && (this._applyAudioConfigResolve(e.error ? !1 : !!e.applied), this._applyAudioConfigResolve = null);
1424
1563
  return;
@@ -1519,5 +1658,5 @@ class K extends EventTarget {
1519
1658
  }
1520
1659
  }
1521
1660
  export {
1522
- K as R
1661
+ Q as R
1523
1662
  };
@@ -1 +1 @@
1
- {"version":3,"file":"ConnectingView.d.ts","sourceRoot":"","sources":["../../src/components/ConnectingView.tsx"],"names":[],"mappings":"AA6BA,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,OAAO,CAAC;AAIjC,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,iBAAiB,CAAC;AAIzD,MAAM,WAAW,mBAAmB;IAClC,+DAA+D;IAC/D,IAAI,EAAE,iBAAiB,GAAG,IAAI,CAAC;IAC/B;;oCAEgC;IAChC,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CACzB;AAiBD,wBAAgB,cAAc,CAAC,EAC7B,IAAI,EACJ,OAAO,GACR,EAAE,mBAAmB,GAAG,GAAG,CAAC,OAAO,CA6FnC;AAkED,eAAO,MAAM,qBAAqB,qBAAa,CAAC"}
1
+ {"version":3,"file":"ConnectingView.d.ts","sourceRoot":"","sources":["../../src/components/ConnectingView.tsx"],"names":[],"mappings":"AA6BA,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,OAAO,CAAC;AAIjC,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,iBAAiB,CAAC;AAIzD,MAAM,WAAW,mBAAmB;IAClC,+DAA+D;IAC/D,IAAI,EAAE,iBAAiB,GAAG,IAAI,CAAC;IAC/B;;oCAEgC;IAChC,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CACzB;AAiBD,wBAAgB,cAAc,CAAC,EAC7B,IAAI,EACJ,OAAO,GACR,EAAE,mBAAmB,GAAG,GAAG,CAAC,OAAO,CAmGnC;AAkED,eAAO,MAAM,qBAAqB,qBAAa,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"ErrorView.d.ts","sourceRoot":"","sources":["../../src/components/ErrorView.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AACH,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,OAAO,CAAC;AAMjC,MAAM,WAAW,cAAc;IAC7B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,QAAQ,IAAI,IAAI,CAAC;IACjB,cAAc,IAAI,IAAI,CAAC;CACxB;AAED,wBAAgB,SAAS,CAAC,EACxB,KAA8B,EAC9B,OAAO,EACP,MAAM,EACN,QAAQ,EACR,cAAc,GACf,EAAE,cAAc,GAAG,GAAG,CAAC,OAAO,CA6D9B"}
1
+ {"version":3,"file":"ErrorView.d.ts","sourceRoot":"","sources":["../../src/components/ErrorView.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AACH,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,OAAO,CAAC;AAMjC,MAAM,WAAW,cAAc;IAC7B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,QAAQ,IAAI,IAAI,CAAC;IACjB,cAAc,IAAI,IAAI,CAAC;CACxB;AAED,wBAAgB,SAAS,CAAC,EACxB,KAA8B,EAC9B,OAAO,EACP,MAAM,EACN,QAAQ,EACR,cAAc,GACf,EAAE,cAAc,GAAG,GAAG,CAAC,OAAO,CA+D9B"}
@@ -1 +1 @@
1
- {"version":3,"file":"LeavingView.d.ts","sourceRoot":"","sources":["../../src/components/LeavingView.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,OAAO,CAAC;AAKjC,wBAAgB,WAAW,IAAI,GAAG,CAAC,OAAO,CA2CzC"}
1
+ {"version":3,"file":"LeavingView.d.ts","sourceRoot":"","sources":["../../src/components/LeavingView.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,OAAO,CAAC;AAKjC,wBAAgB,WAAW,IAAI,GAAG,CAAC,OAAO,CA+CzC"}