@pollen-robotics/reachy-mini-sdk 1.10.0-dev.0.main.b50d97e → 1.10.0-dev.0.main.c8367cd

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.
Files changed (128) hide show
  1. package/APP_CREATION_GUIDE.md +98 -0
  2. package/dist/lib/central-listener.d.ts +71 -0
  3. package/dist/lib/central-listener.d.ts.map +1 -0
  4. package/dist/lib/central-listener.js +277 -0
  5. package/dist/lib/central-listener.js.map +1 -0
  6. package/dist/lib/central-listener.test.d.ts +2 -0
  7. package/dist/lib/central-listener.test.d.ts.map +1 -0
  8. package/dist/lib/central-listener.test.js +378 -0
  9. package/dist/lib/central-listener.test.js.map +1 -0
  10. package/dist/lib/dc-watchdog.test.d.ts +2 -0
  11. package/dist/lib/dc-watchdog.test.d.ts.map +1 -0
  12. package/dist/lib/dc-watchdog.test.js +154 -0
  13. package/dist/lib/dc-watchdog.test.js.map +1 -0
  14. package/dist/lib/ensure-awake.test.d.ts +2 -0
  15. package/dist/lib/ensure-awake.test.d.ts.map +1 -0
  16. package/dist/lib/ensure-awake.test.js +117 -0
  17. package/dist/lib/ensure-awake.test.js.map +1 -0
  18. package/dist/lib/ice-grace.test.d.ts +2 -0
  19. package/dist/lib/ice-grace.test.d.ts.map +1 -0
  20. package/dist/lib/ice-grace.test.js +211 -0
  21. package/dist/lib/ice-grace.test.js.map +1 -0
  22. package/dist/lib/imu.test.d.ts +2 -0
  23. package/dist/lib/imu.test.d.ts.map +1 -0
  24. package/dist/lib/imu.test.js +78 -0
  25. package/dist/lib/imu.test.js.map +1 -0
  26. package/dist/lib/logger.d.ts +38 -0
  27. package/dist/lib/logger.d.ts.map +1 -0
  28. package/dist/lib/logger.js +81 -0
  29. package/dist/lib/logger.js.map +1 -0
  30. package/dist/lib/pending-replies.d.ts +127 -0
  31. package/dist/lib/pending-replies.d.ts.map +1 -0
  32. package/dist/lib/pending-replies.js +255 -0
  33. package/dist/lib/pending-replies.js.map +1 -0
  34. package/dist/lib/pending-replies.test.d.ts +2 -0
  35. package/dist/lib/pending-replies.test.d.ts.map +1 -0
  36. package/dist/lib/pending-replies.test.js +217 -0
  37. package/dist/lib/pending-replies.test.js.map +1 -0
  38. package/dist/lib/reachy-mini.d.ts +202 -88
  39. package/dist/lib/reachy-mini.d.ts.map +1 -1
  40. package/dist/lib/reachy-mini.js +651 -566
  41. package/dist/lib/reachy-mini.js.map +1 -1
  42. package/dist/lib/redial.test.d.ts +2 -0
  43. package/dist/lib/redial.test.d.ts.map +1 -0
  44. package/dist/lib/redial.test.js +206 -0
  45. package/dist/lib/redial.test.js.map +1 -0
  46. package/dist/lib/request.test.d.ts +2 -0
  47. package/dist/lib/request.test.d.ts.map +1 -0
  48. package/dist/lib/request.test.js +90 -0
  49. package/dist/lib/request.test.js.map +1 -0
  50. package/dist/lib/session-supervisor.d.ts +201 -0
  51. package/dist/lib/session-supervisor.d.ts.map +1 -0
  52. package/dist/lib/session-supervisor.js +520 -0
  53. package/dist/lib/session-supervisor.js.map +1 -0
  54. package/dist/lib/token-store.d.ts +50 -0
  55. package/dist/lib/token-store.d.ts.map +1 -0
  56. package/dist/lib/token-store.js +120 -0
  57. package/dist/lib/token-store.js.map +1 -0
  58. package/dist/lib/token-store.test.d.ts +2 -0
  59. package/dist/lib/token-store.test.d.ts.map +1 -0
  60. package/dist/lib/token-store.test.js +82 -0
  61. package/dist/lib/token-store.test.js.map +1 -0
  62. package/dist/lib/types.d.ts +280 -6
  63. package/dist/lib/types.d.ts.map +1 -1
  64. package/dist/lib/url-helpers.d.ts.map +1 -1
  65. package/dist/lib/url-helpers.js +3 -1
  66. package/dist/lib/url-helpers.js.map +1 -1
  67. package/dist/lib/version.d.ts +2 -0
  68. package/dist/lib/version.d.ts.map +1 -0
  69. package/dist/lib/version.js +5 -0
  70. package/dist/lib/version.js.map +1 -0
  71. package/dist/reachy-mini-sdk.d.ts +15 -1
  72. package/dist/reachy-mini-sdk.d.ts.map +1 -1
  73. package/dist/reachy-mini-sdk.js +12 -0
  74. package/dist/reachy-mini-sdk.js.map +1 -1
  75. package/host/dist/ReachyHost.d.ts.map +1 -1
  76. package/host/dist/chunks/centralRest-uXNMidlL.js +2315 -0
  77. package/host/dist/chunks/index-Beu4GNzi.js +821 -0
  78. package/host/dist/chunks/{mountHost-DjE5zE9R.js → mountHost-BTTF4Uu9.js} +24933 -27488
  79. package/host/dist/components/ConnectingView.d.ts.map +1 -1
  80. package/host/dist/components/DaemonUpdateGate.d.ts +65 -0
  81. package/host/dist/components/DaemonUpdateGate.d.ts.map +1 -0
  82. package/host/dist/components/ErrorView.d.ts.map +1 -1
  83. package/host/dist/components/HostErrorBoundary.d.ts +30 -0
  84. package/host/dist/components/HostErrorBoundary.d.ts.map +1 -0
  85. package/host/dist/components/LeavingView.d.ts.map +1 -1
  86. package/host/dist/components/PickerView.d.ts +16 -14
  87. package/host/dist/components/PickerView.d.ts.map +1 -1
  88. package/host/dist/components/PostOAuthSplash.d.ts +14 -9
  89. package/host/dist/components/PostOAuthSplash.d.ts.map +1 -1
  90. package/host/dist/components/ReachyHostShell.d.ts.map +1 -1
  91. package/host/dist/components/SignInView.d.ts.map +1 -1
  92. package/host/dist/components/TopBar.d.ts +5 -6
  93. package/host/dist/components/TopBar.d.ts.map +1 -1
  94. package/host/dist/components/WelcomeBackOverlay.d.ts.map +1 -1
  95. package/host/dist/embed/index.d.ts +68 -5
  96. package/host/dist/embed/index.d.ts.map +1 -1
  97. package/host/dist/entry/auto.js +2 -2
  98. package/host/dist/entry/embed.d.ts +5 -4
  99. package/host/dist/entry/embed.d.ts.map +1 -1
  100. package/host/dist/entry/embed.js +5 -4
  101. package/host/dist/hooks/useHostBridge.d.ts +35 -1
  102. package/host/dist/hooks/useHostBridge.d.ts.map +1 -1
  103. package/host/dist/hooks/useLatestDaemonVersion.d.ts +7 -0
  104. package/host/dist/hooks/useLatestDaemonVersion.d.ts.map +1 -0
  105. package/host/dist/hooks/useOAuth.d.ts +18 -0
  106. package/host/dist/hooks/useOAuth.d.ts.map +1 -1
  107. package/host/dist/hooks/useRobots.d.ts +0 -2
  108. package/host/dist/hooks/useRobots.d.ts.map +1 -1
  109. package/host/dist/index.d.ts +2 -2
  110. package/host/dist/index.d.ts.map +1 -1
  111. package/host/dist/index.js +11 -10
  112. package/host/dist/lib/daemonRelease.d.ts +84 -0
  113. package/host/dist/lib/daemonRelease.d.ts.map +1 -0
  114. package/host/dist/lib/protocol.d.ts +141 -7
  115. package/host/dist/lib/protocol.d.ts.map +1 -1
  116. package/host/dist/lib/rebootWatch.d.ts +51 -0
  117. package/host/dist/lib/rebootWatch.d.ts.map +1 -0
  118. package/host/dist/lib/sdk-types.d.ts +1 -1
  119. package/host/dist/lib/sdk-types.d.ts.map +1 -1
  120. package/host/dist/lib/sdkStaleness.d.ts +78 -0
  121. package/host/dist/lib/sdkStaleness.d.ts.map +1 -0
  122. package/host/dist/lib/settings.d.ts +16 -5
  123. package/host/dist/lib/settings.d.ts.map +1 -1
  124. package/package.json +11 -4
  125. package/host/dist/chunks/index-lMs2sCXG.js +0 -439
  126. package/host/dist/chunks/reachy-mini-CdsdQLIU.js +0 -1523
  127. package/host/dist/lib/centralListener.d.ts +0 -73
  128. package/host/dist/lib/centralListener.d.ts.map +0 -1
@@ -0,0 +1,120 @@
1
+ /**
2
+ * Session-scoped persistence of the HF OAuth token.
3
+ *
4
+ * The token lives in `sessionStorage` (tab-scoped, dies with the tab),
5
+ * under keys shared with the host shell and the embed bootstrap. On top
6
+ * of the OAuth expiry, reads enforce a SLIDING IDLE WINDOW: a token
7
+ * whose last successful read is older than `TOKEN_MAX_IDLE_MS` is
8
+ * treated as absent and wiped. That property replaced the host's old
9
+ * "wipe on pagehide" hygiene, which fired on every reload / navigation
10
+ * (pagehide can't tell a refresh from a tab close) and forced a
11
+ * re-login on every F5. The idle window keeps the one attack the wipe
12
+ * actually covered - a tab resurrected days later via session restore
13
+ * gets its sessionStorage back - without punishing reloads.
14
+ *
15
+ * `consumeOAuthErrorParams` supports the silent sign-in flow
16
+ * (`login({ prompt: 'none' })`): a failed silent authorization comes
17
+ * back as `?error=login_required...` query params, which must be
18
+ * stripped from the URL before the app router or a copy-pasted link
19
+ * leaks them around.
20
+ */
21
+ const KEY_TOKEN = 'hf_token';
22
+ const KEY_USER = 'hf_username';
23
+ const KEY_EXPIRES = 'hf_token_expires';
24
+ const KEY_LAST_SEEN = 'hf_token_last_seen';
25
+ /** Idle cutoff: a cached token unused for longer than this is dropped. */
26
+ export const TOKEN_MAX_IDLE_MS = 24 * 60 * 60 * 1000;
27
+ /** Persist a token bundle and stamp its last-seen time. */
28
+ export function writeStoredToken(bundle) {
29
+ try {
30
+ sessionStorage.setItem(KEY_TOKEN, bundle.token);
31
+ sessionStorage.setItem(KEY_USER, bundle.username);
32
+ sessionStorage.setItem(KEY_EXPIRES, bundle.expires);
33
+ sessionStorage.setItem(KEY_LAST_SEEN, new Date().toISOString());
34
+ }
35
+ catch {
36
+ /* ignore - private browsing / quota */
37
+ }
38
+ }
39
+ /**
40
+ * Read the cached token if it is still usable, refreshing its
41
+ * last-seen stamp (sliding window). Returns `null` - and wipes the
42
+ * store - when the token is expired or idle for too long.
43
+ *
44
+ * A missing last-seen stamp (bundle written by an older SDK, or seeded
45
+ * by the embed's fragment-credentials path) is treated as fresh and
46
+ * stamped now, so a rollout never logs anyone out.
47
+ */
48
+ export function readUsableToken(now = Date.now()) {
49
+ try {
50
+ const token = sessionStorage.getItem(KEY_TOKEN);
51
+ const username = sessionStorage.getItem(KEY_USER);
52
+ const expires = sessionStorage.getItem(KEY_EXPIRES);
53
+ if (!token || !expires)
54
+ return null;
55
+ if (new Date(expires).getTime() <= now) {
56
+ clearStoredToken();
57
+ return null;
58
+ }
59
+ const lastSeen = sessionStorage.getItem(KEY_LAST_SEEN);
60
+ if (lastSeen !== null) {
61
+ const lastSeenMs = new Date(lastSeen).getTime();
62
+ if (!Number.isFinite(lastSeenMs) || now - lastSeenMs > TOKEN_MAX_IDLE_MS) {
63
+ clearStoredToken();
64
+ return null;
65
+ }
66
+ }
67
+ sessionStorage.setItem(KEY_LAST_SEEN, new Date(now).toISOString());
68
+ return { token, username: username ?? '', expires };
69
+ }
70
+ catch {
71
+ return null;
72
+ }
73
+ }
74
+ /** Remove every token key from the store. */
75
+ export function clearStoredToken() {
76
+ try {
77
+ sessionStorage.removeItem(KEY_TOKEN);
78
+ sessionStorage.removeItem(KEY_USER);
79
+ sessionStorage.removeItem(KEY_EXPIRES);
80
+ sessionStorage.removeItem(KEY_LAST_SEEN);
81
+ }
82
+ catch {
83
+ /* ignore */
84
+ }
85
+ }
86
+ /**
87
+ * OAuth error codes a `prompt=none` silent authorization can bounce
88
+ * back with. Gating on this set keeps `consumeOAuthErrorParams` from
89
+ * swallowing an app's own unrelated `?error=` param (and rewriting
90
+ * history for it) on a page load that has nothing to do with OAuth.
91
+ */
92
+ const OAUTH_ERROR_CODES = new Set([
93
+ 'login_required',
94
+ 'consent_required',
95
+ 'interaction_required',
96
+ 'access_denied',
97
+ ]);
98
+ /**
99
+ * Detect an OAuth error return (`?error=...` from a `prompt=none`
100
+ * silent authorization), strip the OAuth params from the URL, and
101
+ * return the error code. Returns `null` when the URL carries no OAuth
102
+ * error. Other query params are preserved.
103
+ */
104
+ export function consumeOAuthErrorParams() {
105
+ try {
106
+ const url = new URL(window.location.href);
107
+ const error = url.searchParams.get('error');
108
+ if (error === null || !OAUTH_ERROR_CODES.has(error))
109
+ return null;
110
+ url.searchParams.delete('error');
111
+ url.searchParams.delete('error_description');
112
+ url.searchParams.delete('state');
113
+ window.history.replaceState(null, '', url.toString());
114
+ return error;
115
+ }
116
+ catch {
117
+ return null;
118
+ }
119
+ }
120
+ //# sourceMappingURL=token-store.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"token-store.js","sourceRoot":"","sources":["../../lib/token-store.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=token-store.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"token-store.test.d.ts","sourceRoot":"","sources":["../../lib/token-store.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,82 @@
1
+ /**
2
+ * Token-store tests: the sliding idle window that replaced the host's
3
+ * pagehide wipe, and the OAuth-error URL scrubbing used by the silent
4
+ * sign-in flow.
5
+ *
6
+ * The property that must hold: a token can NEVER be served past its
7
+ * OAuth expiry, nor after sitting unused longer than the idle window -
8
+ * but a plain reload (seconds of idle) always keeps the session.
9
+ */
10
+ import { afterEach, describe, expect, it } from 'vitest';
11
+ import { TOKEN_MAX_IDLE_MS, clearStoredToken, consumeOAuthErrorParams, readUsableToken, writeStoredToken, } from './token-store.js';
12
+ const NOW = 1_700_000_000_000;
13
+ function seed(opts) {
14
+ const expiresInMs = opts?.expiresInMs ?? 60 * 60 * 1000;
15
+ writeStoredToken({
16
+ token: 'hf_test_token',
17
+ username: 'thibaud',
18
+ expires: new Date(NOW + expiresInMs).toISOString(),
19
+ });
20
+ if (opts?.lastSeenAgoMs === null) {
21
+ sessionStorage.removeItem('hf_token_last_seen');
22
+ }
23
+ else if (opts?.lastSeenAgoMs !== undefined) {
24
+ sessionStorage.setItem('hf_token_last_seen', new Date(NOW - opts.lastSeenAgoMs).toISOString());
25
+ }
26
+ }
27
+ afterEach(() => {
28
+ clearStoredToken();
29
+ window.history.replaceState(null, '', '/');
30
+ });
31
+ describe('readUsableToken', () => {
32
+ it('round-trips a fresh token and refreshes its last-seen stamp', () => {
33
+ seed({ lastSeenAgoMs: 60_000 });
34
+ const read = readUsableToken(NOW);
35
+ expect(read).toEqual({
36
+ token: 'hf_test_token',
37
+ username: 'thibaud',
38
+ expires: new Date(NOW + 60 * 60 * 1000).toISOString(),
39
+ });
40
+ expect(sessionStorage.getItem('hf_token_last_seen')).toBe(new Date(NOW).toISOString());
41
+ });
42
+ it('returns null when nothing is stored', () => {
43
+ expect(readUsableToken(NOW)).toBeNull();
44
+ });
45
+ it('drops an expired token and wipes the store', () => {
46
+ seed({ expiresInMs: -1 });
47
+ expect(readUsableToken(NOW)).toBeNull();
48
+ expect(sessionStorage.getItem('hf_token')).toBeNull();
49
+ });
50
+ it('drops a token idle for longer than the window (session restore)', () => {
51
+ seed({ lastSeenAgoMs: TOKEN_MAX_IDLE_MS + 1 });
52
+ expect(readUsableToken(NOW)).toBeNull();
53
+ expect(sessionStorage.getItem('hf_token')).toBeNull();
54
+ });
55
+ it('keeps a token idle for less than the window (plain reload)', () => {
56
+ seed({ lastSeenAgoMs: TOKEN_MAX_IDLE_MS - 1 });
57
+ expect(readUsableToken(NOW)).not.toBeNull();
58
+ });
59
+ it('accepts and stamps a legacy bundle without a last-seen key', () => {
60
+ seed({ lastSeenAgoMs: null });
61
+ expect(readUsableToken(NOW)).not.toBeNull();
62
+ expect(sessionStorage.getItem('hf_token_last_seen')).toBe(new Date(NOW).toISOString());
63
+ });
64
+ it('drops a token whose last-seen stamp is unparsable', () => {
65
+ seed();
66
+ sessionStorage.setItem('hf_token_last_seen', 'not-a-date');
67
+ expect(readUsableToken(NOW)).toBeNull();
68
+ });
69
+ });
70
+ describe('consumeOAuthErrorParams', () => {
71
+ it('returns null and leaves the URL alone without an error param', () => {
72
+ window.history.replaceState(null, '', '/?config=abc');
73
+ expect(consumeOAuthErrorParams()).toBeNull();
74
+ expect(window.location.search).toBe('?config=abc');
75
+ });
76
+ it('consumes a silent-auth decline and scrubs only the OAuth params', () => {
77
+ window.history.replaceState(null, '', '/?config=abc&error=login_required&error_description=User+must+be+logged+in&state=xyz');
78
+ expect(consumeOAuthErrorParams()).toBe('login_required');
79
+ expect(window.location.search).toBe('?config=abc');
80
+ });
81
+ });
82
+ //# sourceMappingURL=token-store.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"token-store.test.js","sourceRoot":"","sources":["../../lib/token-store.test.ts"],"names":[],"mappings":"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"}
@@ -44,16 +44,57 @@ export interface FaceTarget {
44
44
  roll?: number | null;
45
45
  ts?: number | null;
46
46
  }
47
+ /**
48
+ * Sound Direction of Arrival from the robot's ReSpeaker microphone array.
49
+ * Streamed inside `RobotState` (wire shape), so it rides the same ~30 Hz
50
+ * pose push / `get_state` replies as the pose - no extra round-trip.
51
+ */
52
+ export interface DoA {
53
+ /**
54
+ * Angle in radians (ReSpeaker convention): `0` = left, `π/2` =
55
+ * front/back, `π` = right.
56
+ */
57
+ angle: number;
58
+ /** Whether the array currently detects speech coming from that angle. */
59
+ speech_detected: boolean;
60
+ }
61
+ /**
62
+ * One IMU reading from the robot's BMI088 (wireless version only).
63
+ * Wire shape of the daemon's `ImuDataMsg`, minus the `type` envelope.
64
+ */
65
+ export interface ImuData {
66
+ /** [x, y, z] linear acceleration, m/s². */
67
+ accelerometer: number[];
68
+ /** [x, y, z] angular velocity, rad/s. */
69
+ gyroscope: number[];
70
+ /** [w, x, y, z] orientation estimate. */
71
+ quaternion: number[];
72
+ /** Sensor die temperature, °C. */
73
+ temperature: number;
74
+ }
47
75
  export interface RobotState {
48
76
  /** Flat row-major 4×4 head pose (16 numbers). */
49
77
  head?: number[];
50
78
  /** [rightRad, leftRad]. */
51
79
  antennas?: number[];
80
+ /**
81
+ * Per-motor head joint values (7 numbers, body yaw at [0]), streamed by
82
+ * the daemon alongside `head` so clients can check the physical pose
83
+ * motor by motor (e.g. the first wake-up wizard's sleep-position check).
84
+ * The antennas need no twin field: `antennas` already is the two motor
85
+ * values. Absent on daemons that predate the field.
86
+ */
87
+ head_joint_positions?: number[];
52
88
  /** Radians. */
53
89
  body_yaw?: number;
54
90
  motor_mode?: 'enabled' | 'disabled' | 'gravity_compensation';
55
91
  is_move_running?: boolean;
56
92
  face_target?: FaceTarget;
93
+ /**
94
+ * Sound Direction of Arrival, or absent when the robot has no mic array
95
+ * (or no reading yet). Refreshed by the daemon at ~10 Hz.
96
+ */
97
+ doa?: DoA;
57
98
  }
58
99
  /** SDK constructor options. */
59
100
  export interface ReachyMiniOptions {
@@ -82,6 +123,40 @@ export interface ReachyMiniOptions {
82
123
  * per page load; the host iframe relies on this to skip the picker.
83
124
  */
84
125
  autoStartFromUrl?: boolean;
126
+ /**
127
+ * Automatic session re-dial (default `true`). When an ESTABLISHED
128
+ * session's transport dies for good (ICE `failed` past its grace
129
+ * window, stuck `disconnected`, or expired while the tab was
130
+ * backgrounded), the SDK tears the dead RTCPeerConnection down and
131
+ * re-dials the same robot through the signaling server — up to 5
132
+ * attempts with backoff (~22 s window). The app sees
133
+ * `sessionReconnecting` per attempt, then either `sessionReconnected`
134
+ * followed by a fresh `streaming` event, or a terminal
135
+ * `sessionStopped` with reason `reconnect_failed`. Failures during
136
+ * the INITIAL `startSession()` handshake still reject the promise —
137
+ * the caller owns that retry. Set to `false` to restore the previous
138
+ * fatal-`error` behaviour.
139
+ */
140
+ autoReconnect?: boolean;
141
+ }
142
+ /** Options accepted by `login()`. */
143
+ export interface LoginOptions {
144
+ /**
145
+ * OIDC prompt behaviour for the OAuth redirect.
146
+ *
147
+ * `'none'` performs a SILENT authorization: a user who is logged in
148
+ * to Hugging Face and has already authorized the app comes straight
149
+ * back with a code (no screen, no interaction); everyone else comes
150
+ * back immediately with `?error=login_required` /
151
+ * `consent_required` instead of landing on the HF login page. The
152
+ * error return is consumed and stripped by `authenticate()`, which
153
+ * then simply reports "not signed in". Supported by the HF
154
+ * authorize endpoint although absent from its discovery metadata
155
+ * (verified empirically).
156
+ *
157
+ * Omit for the regular interactive flow.
158
+ */
159
+ prompt?: 'none';
85
160
  }
86
161
  /** Options accepted by `autoConnect()`. */
87
162
  export interface AutoConnectOptions {
@@ -136,6 +211,35 @@ export interface SubscribeLogsOptions {
136
211
  }) => void;
137
212
  onError?: (error: string) => void;
138
213
  }
214
+ /** `request()` options. */
215
+ export interface RequestOptions {
216
+ /**
217
+ * Milliseconds to wait for a matching reply before fail-opening to
218
+ * `null`. Defaults to the SDK's shared command round-trip timeout (4 s).
219
+ */
220
+ timeoutMs?: number;
221
+ /**
222
+ * Override the reply matcher. Default: the first robot message whose
223
+ * `command` field equals the sent command's `type` (the daemon's
224
+ * reply convention).
225
+ */
226
+ match?: (msg: Record<string, unknown>) => boolean;
227
+ }
228
+ /** One progress event from an in-flight `startDaemonUpdate()`. */
229
+ export interface UpdateProgressEvent {
230
+ status: 'in_progress' | 'done' | 'failed';
231
+ /** A log line of the underlying update job (when `in_progress`). */
232
+ line?: string;
233
+ /** Set when `status === 'failed'`. */
234
+ error?: string;
235
+ }
236
+ /** `startDaemonUpdate()` argument. */
237
+ export interface StartDaemonUpdateOptions {
238
+ /** Install the latest pre-release instead of the latest stable. */
239
+ preRelease?: boolean;
240
+ /** Receives one event per log line of the update job. */
241
+ onProgress?: (event: UpdateProgressEvent) => void;
242
+ }
139
243
  /** Single XVF3800 parameter write. */
140
244
  export interface AudioConfigEntry {
141
245
  name: string;
@@ -247,6 +351,19 @@ export interface NetworkChangeEventDetail {
247
351
  rtt?: number;
248
352
  saveData?: boolean;
249
353
  }
354
+ /**
355
+ * One auto-reconnect attempt is starting (fires once per attempt,
356
+ * BEFORE its backoff wait). Show a "Reconnecting…" UI on the first one
357
+ * and keep it until `sessionReconnected` or a `sessionStopped` with
358
+ * reason `reconnect_failed`. Motion/RPC calls made while reconnecting
359
+ * fail exactly as they would on a stopped session.
360
+ */
361
+ export interface SessionReconnectingEventDetail {
362
+ attempt: number;
363
+ maxAttempts: number;
364
+ /** Human-readable description of what killed the transport. */
365
+ cause: string;
366
+ }
250
367
  /** Map of event names to their detail shapes. */
251
368
  export interface ReachyMiniEventMap {
252
369
  connected: CustomEvent<ConnectedEventDetail>;
@@ -263,6 +380,18 @@ export interface ReachyMiniEventMap {
263
380
  networkOnline: CustomEvent<Record<string, never>>;
264
381
  networkOffline: CustomEvent<Record<string, never>>;
265
382
  networkChange: CustomEvent<NetworkChangeEventDetail>;
383
+ sessionReconnecting: CustomEvent<SessionReconnectingEventDetail>;
384
+ /**
385
+ * Auto-reconnect succeeded; the detail is the 1-based attempt that
386
+ * got through. A fresh `streaming` event fires alongside (same
387
+ * robot, new sessionId); video re-attaches automatically through
388
+ * the `videoTrack` listener installed by `attachVideo()`. App-level
389
+ * state that lives daemon-side per session (motor mode override, a
390
+ * replaced audio sender track, …) must be re-asserted by the app.
391
+ */
392
+ sessionReconnected: CustomEvent<{
393
+ attempt: number;
394
+ }>;
266
395
  }
267
396
  /** Public surface of a ReachyMini SDK instance. */
268
397
  export interface ReachyMiniInstance extends EventTarget {
@@ -282,8 +411,24 @@ export interface ReachyMiniInstance extends EventTarget {
282
411
  readonly preselectedRobotId: string | null;
283
412
  /** `true` iff `preselectedRobotId !== null`. UX branching helper. */
284
413
  readonly isEmbedded: boolean;
414
+ /**
415
+ * Live RTCPeerConnection, or `null` between sessions. Read-only escape
416
+ * hatch for stats sampling (`getStats()`); mutating it is unsupported.
417
+ * Auto-reconnect re-dials REPLACE this object, so re-read it on every
418
+ * use — never capture it across ticks.
419
+ */
420
+ readonly peerConnection: RTCPeerConnection | null;
421
+ /**
422
+ * Build version of this SDK (npm package `version`), injected from
423
+ * package.json at build time. The JS SDK's OWN version — distinct from
424
+ * `getVersion()`, which asks the DAEMON its version over the data
425
+ * channel. `0.0.0-managed-by-ci` means an unreleased/branch build.
426
+ */
427
+ readonly sdkVersion: string;
285
428
  /** Underlying RTCPeerConnection. Apps can read it to inspect
286
- * audio / video transceivers. */
429
+ * audio / video transceivers.
430
+ * @deprecated Use `peerConnection`. Kept for compatibility with
431
+ * app bundles that predate the getter. */
287
432
  _pc: RTCPeerConnection | null;
288
433
  /** Silent placeholder MediaStream the SDK feeds the WebRTC audio
289
434
  * sender so robot-speaker output can negotiate sendrecv. Apps inject
@@ -291,7 +436,7 @@ export interface ReachyMiniInstance extends EventTarget {
291
436
  * `replaceTrack()` on the audio sender from `_pc.getSenders()`. */
292
437
  _micStream: MediaStream | null;
293
438
  authenticate(): Promise<boolean>;
294
- login(): Promise<void>;
439
+ login(options?: LoginOptions): Promise<void>;
295
440
  logout(): void;
296
441
  connect(token?: string): Promise<void>;
297
442
  /**
@@ -303,6 +448,20 @@ export interface ReachyMiniInstance extends EventTarget {
303
448
  disconnect(): void;
304
449
  startSession(robotId: string): Promise<void>;
305
450
  stopSession(): Promise<void>;
451
+ /**
452
+ * Enable/disable the automatic session re-dial at runtime (see the
453
+ * `autoReconnect` constructor option). Disabling aborts any re-dial
454
+ * already in flight. Meant for flows that EXPECT the transport to
455
+ * die, e.g. a daemon self-update reboot.
456
+ *
457
+ * Sharp edge: aborting an in-flight re-dial leaves the session torn
458
+ * down (the loop closed the transport on entry) WITHOUT emitting
459
+ * `sessionStopped` or `error` — the caller owns the terminal event.
460
+ * The daemon-update flow relies on exactly that (its own
461
+ * `sessionStopped` arrives from the reboot); any other caller must
462
+ * surface the end of the session itself.
463
+ */
464
+ setAutoReconnect(enabled: boolean): void;
306
465
  attachVideo(el: HTMLVideoElement): () => void;
307
466
  setHeadRpyDeg(roll: number, pitch: number, yaw: number): boolean;
308
467
  setAntennasDeg(right: number, left: number): boolean;
@@ -331,8 +490,60 @@ export interface ReachyMiniInstance extends EventTarget {
331
490
  }): boolean;
332
491
  /** Send an arbitrary JSON message on the data channel. */
333
492
  sendRaw(data: unknown): boolean;
493
+ /**
494
+ * Generic command round-trip for daemon commands the SDK has no typed
495
+ * wrapper for: send `command` and await the matching reply, `null` on
496
+ * the fail-open timeout (daemon predates the command). Lets an app use
497
+ * a newer daemon feature without waiting for an SDK release.
498
+ */
499
+ request(command: {
500
+ type: string;
501
+ } & Record<string, unknown>, options?: RequestOptions): Promise<Record<string, unknown> | null>;
334
502
  /** Play a sound file on the robot's speakers (basename). */
335
503
  playSound(file: string): boolean;
504
+ /**
505
+ * Play a named recorded move (motion + its bundled sound) from a HF
506
+ * dataset, daemon-side. Fire-and-forget: returns `true` once the command
507
+ * is sent on the data channel (not when playback finishes), `false` if the
508
+ * channel isn't open. `dataset` defaults to the daemon's emotions library
509
+ * (`pollen-robotics/reachy-mini-emotions-library`), already pre-downloaded
510
+ * on the robot; pass it to source a move from another repo.
511
+ * `initialGotoDuration` (seconds) eases into the move's first frame
512
+ * instead of snapping. The daemon self-guards against a concurrent move,
513
+ * so a rapid double call is a no-op.
514
+ */
515
+ playRecordedMove(moveName: string, opts?: {
516
+ dataset?: string;
517
+ initialGotoDuration?: number;
518
+ }): boolean;
519
+ /**
520
+ * Stop whatever move is currently playing on the daemon (recorded move,
521
+ * uploaded move, goto), silencing its bundled sound too. Fire-and-forget
522
+ * and idempotent: a stop with no move running is acked as a no-op, not
523
+ * an error. Returns `false` if the data channel is not open.
524
+ */
525
+ stopMove(): boolean;
526
+ /**
527
+ * Ask the daemon to pre-download a recorded-move dataset into its local
528
+ * HF cache, so a later `playRecordedMove` from that dataset starts
529
+ * instantly instead of blocking on the download. Send it right after
530
+ * connecting for any non-official dataset you plan to play (the daemon
531
+ * only preloads the official pollen-robotics libraries by itself).
532
+ * Fire-and-forget and idempotent: cache-first daemon-side, so repeats
533
+ * are cheap; on failure `playRecordedMove` still downloads on demand.
534
+ */
535
+ preloadDataset(dataset: string): boolean;
536
+ /**
537
+ * Like `preloadDataset()`, but resolves once the daemon acks the preload,
538
+ * i.e. when the dataset is actually in the local HF cache. `true` on
539
+ * success, `false` on a daemon-reported download failure, `null` on the
540
+ * fail-open timeout (slow download, or a daemon that predates the
541
+ * command). Rejects when the channel isn't open or the session tears
542
+ * down mid-flight.
543
+ */
544
+ preloadDatasetAndWait(dataset: string, options?: {
545
+ timeoutMs?: number;
546
+ }): Promise<boolean | null>;
336
547
  /** Drop incoming audio queued for the robot speaker (barge-in). */
337
548
  clearIncomingAudio(): boolean;
338
549
  /** Enable daemon-side visual head tracking. */
@@ -347,6 +558,25 @@ export interface ReachyMiniInstance extends EventTarget {
347
558
  setVolume(volume: number): Promise<number | null>;
348
559
  getMicrophoneVolume(): Promise<number | null>;
349
560
  setMicrophoneVolume(volume: number): Promise<number | null>;
561
+ /**
562
+ * Query the persisted robot display name. `null` when none is set, the
563
+ * channel isn't open, or the daemon predates the `get_robot_name` command.
564
+ */
565
+ getRobotName(): Promise<string | null>;
566
+ /**
567
+ * Query whether the first wake-up setup wizard has been completed
568
+ * (robot-wide, persisted). `null` when the channel isn't open or the
569
+ * daemon predates the command (callers should fail-open).
570
+ */
571
+ getFirstWakeUp(): Promise<boolean | null>;
572
+ /** Persist the first wake-up wizard completion flag on the robot. */
573
+ setFirstWakeUp(isCompleted: boolean): Promise<boolean | null>;
574
+ /**
575
+ * Set and persist the robot display name. Applied live by the daemon
576
+ * (status + central relay + mDNS), so it takes effect right away without a
577
+ * restart. Resolves with the stored name, or `null` on error.
578
+ */
579
+ setRobotName(name: string): Promise<string | null>;
350
580
  /**
351
581
  * Apply a batch of XVF3800 audio-board parameters on the robot.
352
582
  * Mirrors the on-robot `AudioBase.apply_audio_config()` SDK call.
@@ -366,10 +596,38 @@ export interface ReachyMiniInstance extends EventTarget {
366
596
  getVersion(): Promise<string | null>;
367
597
  /** Hardware ID (USB serial), or `null` on developer machines. */
368
598
  getHardwareId(): Promise<string | null>;
599
+ /**
600
+ * One-shot IMU reading (accelerometer, gyroscope, quaternion,
601
+ * temperature). Resolves `null` when the robot has no IMU (Lite
602
+ * version, simulation) or the daemon predates the `get_imu` command.
603
+ */
604
+ getImu(): Promise<ImuData | null>;
369
605
  /** Force a `state` event right now (background poll runs at 500 ms). */
370
606
  requestState(): boolean;
607
+ /**
608
+ * Ask the daemon to *push* the robot state (~30 Hz, delivered as `state`
609
+ * events) over the dedicated unreliable/unordered `pose` channel instead
610
+ * of polling `requestState` - immune to the Wi-Fi round-trip lag of the
611
+ * reliable control channel. Refcounted: pair every `subscribePose()` with
612
+ * exactly one `unsubscribePose()`; multiple consumers share one daemon-side
613
+ * subscription. No-op against a daemon that predates the pose channel (the
614
+ * SDK's ~2 Hz self-poll keeps `state` updated there).
615
+ */
616
+ subscribePose(): boolean;
617
+ /** Release one pose-stream consumer; sends `unsubscribe_pose` on the last. */
618
+ unsubscribePose(): boolean;
371
619
  /** Subscribe to daemon `journalctl` logs over the data channel. */
372
620
  subscribeLogs(options: SubscribeLogsOptions): () => void;
621
+ /**
622
+ * Ask the daemon to update itself from PyPI (Wireless only) and
623
+ * restart. Fire-and-ack: returns `false` only when the data channel
624
+ * isn't open. A *successful* update restarts the daemon before a
625
+ * `done` event can arrive, so treat the session teardown followed by
626
+ * a successful reconnect as the success signal; `onProgress` fires
627
+ * with `status: 'failed'` when the daemon declines the job or the
628
+ * install errors before the restart.
629
+ */
630
+ startDaemonUpdate(options?: StartDaemonUpdateOptions): boolean;
373
631
  /**
374
632
  * Motor torque mode: `enabled` (position control), `disabled`
375
633
  * (limp), `gravity_compensation` (float-by-hand). Returns
@@ -392,11 +650,22 @@ export interface ReachyMiniInstance extends EventTarget {
392
650
  * completion. Does NOT touch motor mode; caller manages it.
393
651
  */
394
652
  gotoSleep(options?: MotionAwaitOptions): Promise<void>;
395
- /** Read the awake state from the cached `motor_mode`. */
653
+ /**
654
+ * Read the awake state from the cached `motor_mode`. Gravity
655
+ * compensation counts as awake: the robot is standing, even though
656
+ * it won't follow position targets in that mode.
657
+ */
396
658
  isAwake(): boolean;
397
659
  /**
398
- * Idempotent wakeUp: noop when already awake. Resolves with
399
- * the post-call awake state. Does NOT await the trajectory.
660
+ * Idempotent bring-up to a state where the robot follows position
661
+ * targets. Plays the wake trajectory when asleep and awaits it, so
662
+ * callers can start commanding poses on resolution without fighting
663
+ * the emote; switches back to position control (no emote) when the
664
+ * robot is up but in gravity compensation; noop when already there.
665
+ *
666
+ * `timeoutMs` bounds only the initial `motor_mode` read; the wake
667
+ * trajectory gets its own internal budget. Never rejects - a robot
668
+ * that won't confirm the wake still lets the app boot.
400
669
  */
401
670
  ensureAwake(timeoutMs?: number): Promise<boolean>;
402
671
  /** Upload a recorded move (and optional audio) and play it on the daemon's local clock. */
@@ -412,7 +681,12 @@ export interface ReachyMiniInstance extends EventTarget {
412
681
  /** Cancel an in-flight `playUploadedAudio`. */
413
682
  cancelAudio(uploadId?: string | null): boolean;
414
683
  }
415
- export type ReachyMiniConstructor = new (options?: ReachyMiniOptions) => ReachyMiniInstance;
684
+ export type ReachyMiniConstructor = (new (options?: ReachyMiniOptions) => ReachyMiniInstance) & {
685
+ /** Build version of the SDK (npm package `version`), reachable without
686
+ * an instance via `ReachyMini.version`. Mirror of the instance
687
+ * `sdkVersion`. */
688
+ readonly version: string;
689
+ };
416
690
  /**
417
691
  * Internal "session reject" error shape. Surfaced by `startSession()`
418
692
  * rejections; consumers can downcast a caught `Error` to read the